aboutsummaryrefslogtreecommitdiff
path: root/code/renderer
diff options
context:
space:
mode:
Diffstat (limited to 'code/renderer')
-rw-r--r--code/renderer/qgl.h403
-rw-r--r--code/renderer/tr_animation.c658
-rw-r--r--code/renderer/tr_backend.c1285
-rw-r--r--code/renderer/tr_bsp.c1871
-rw-r--r--code/renderer/tr_cmds.c566
-rw-r--r--code/renderer/tr_curve.c626
-rw-r--r--code/renderer/tr_flares.c538
-rw-r--r--code/renderer/tr_font.c549
-rw-r--r--code/renderer/tr_image.c1606
-rw-r--r--code/renderer/tr_image_bmp.c239
-rw-r--r--code/renderer/tr_image_jpg.c497
-rw-r--r--code/renderer/tr_image_pcx.c175
-rw-r--r--code/renderer/tr_image_png.c2488
-rw-r--r--code/renderer/tr_image_tga.c320
-rw-r--r--code/renderer/tr_init.c1258
-rw-r--r--code/renderer/tr_light.c394
-rw-r--r--code/renderer/tr_local.h1720
-rw-r--r--code/renderer/tr_main.c1404
-rw-r--r--code/renderer/tr_marks.c454
-rw-r--r--code/renderer/tr_mesh.c418
-rw-r--r--code/renderer/tr_model.c1148
-rw-r--r--code/renderer/tr_noise.c93
-rw-r--r--code/renderer/tr_public.h171
-rw-r--r--code/renderer/tr_scene.c418
-rw-r--r--code/renderer/tr_shade.c1548
-rw-r--r--code/renderer/tr_shade_calc.c1231
-rw-r--r--code/renderer/tr_shader.c3075
-rw-r--r--code/renderer/tr_shadows.c374
-rw-r--r--code/renderer/tr_sky.c873
-rw-r--r--code/renderer/tr_surface.c1290
-rw-r--r--code/renderer/tr_types.h212
-rw-r--r--code/renderer/tr_world.c668
32 files changed, 28570 insertions, 0 deletions
diff --git a/code/renderer/qgl.h b/code/renderer/qgl.h
new file mode 100644
index 0000000..4213f43
--- /dev/null
+++ b/code/renderer/qgl.h
@@ -0,0 +1,403 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+/*
+** QGL.H
+*/
+
+#ifndef __QGL_H__
+#define __QGL_H__
+
+#ifndef PANDORA
+
+#ifdef USE_LOCAL_HEADERS
+# include "SDL_opengl.h"
+#else
+# include <SDL_opengl.h>
+#endif
+
+#endif // PANDORA
+
+#ifdef PANDORA
+
+#include <GLES/egl.h>
+#include <GLES/gl.h>
+
+#include "../pnd/egl_glimp.h"
+#include "../pnd/qgl.h"
+
+#define GL_TEXTURE0_ARB 0x84C0
+#define GL_TEXTURE1_ARB 0x84C1
+#define GL_TEXTURE2_ARB 0x84C2
+#define GL_TEXTURE3_ARB 0x84C3
+#define GL_RGB_S3TC 0x83A0
+#define GL_RGB4_S3TC 0x83A1
+
+#endif // PANDORA
+
+extern void (APIENTRYP qglActiveTextureARB) (GLenum texture);
+extern void (APIENTRYP qglClientActiveTextureARB) (GLenum texture);
+extern void (APIENTRYP qglMultiTexCoord2fARB) (GLenum target, GLfloat s, GLfloat t);
+
+extern void (APIENTRYP qglLockArraysEXT) (GLint first, GLsizei count);
+extern void (APIENTRYP qglUnlockArraysEXT) (void);
+
+#ifndef PANDORA
+
+//===========================================================================
+
+#define qglAccum glAccum
+#define qglAlphaFunc glAlphaFunc
+#define qglAreTexturesResident glAreTexturesResident
+#define qglArrayElement glArrayElement
+#define qglBegin glBegin
+#define qglBindTexture glBindTexture
+#define qglBitmap glBitmap
+#define qglBlendFunc glBlendFunc
+#define qglCallList glCallList
+#define qglCallLists glCallLists
+#define qglClear glClear
+#define qglClearAccum glClearAccum
+#define qglClearColor glClearColor
+#define qglClearDepth glClearDepth
+#define qglClearIndex glClearIndex
+#define qglClearStencil glClearStencil
+#define qglClipPlane glClipPlane
+#define qglColor3b glColor3b
+#define qglColor3bv glColor3bv
+#define qglColor3d glColor3d
+#define qglColor3dv glColor3dv
+#define qglColor3f glColor3f
+#define qglColor3fv glColor3fv
+#define qglColor3i glColor3i
+#define qglColor3iv glColor3iv
+#define qglColor3s glColor3s
+#define qglColor3sv glColor3sv
+#define qglColor3ub glColor3ub
+#define qglColor3ubv glColor3ubv
+#define qglColor3ui glColor3ui
+#define qglColor3uiv glColor3uiv
+#define qglColor3us glColor3us
+#define qglColor3usv glColor3usv
+#define qglColor4b glColor4b
+#define qglColor4bv glColor4bv
+#define qglColor4d glColor4d
+#define qglColor4dv glColor4dv
+#define qglColor4f glColor4f
+#define qglColor4fv glColor4fv
+#define qglColor4i glColor4i
+#define qglColor4iv glColor4iv
+#define qglColor4s glColor4s
+#define qglColor4sv glColor4sv
+#define qglColor4ub glColor4ub
+#define qglColor4ubv glColor4ubv
+#define qglColor4ui glColor4ui
+#define qglColor4uiv glColor4uiv
+#define qglColor4us glColor4us
+#define qglColor4usv glColor4usv
+#define qglColorMask glColorMask
+#define qglColorMaterial glColorMaterial
+#define qglColorPointer glColorPointer
+#define qglCopyPixels glCopyPixels
+#define qglCopyTexImage1D glCopyTexImage1D
+#define qglCopyTexImage2D glCopyTexImage2D
+#define qglCopyTexSubImage1D glCopyTexSubImage1D
+#define qglCopyTexSubImage2D glCopyTexSubImage2D
+#define qglCullFace glCullFace
+#define qglDeleteLists glDeleteLists
+#define qglDeleteTextures glDeleteTextures
+#define qglDepthFunc glDepthFunc
+#define qglDepthMask glDepthMask
+#define qglDepthRange glDepthRange
+#define qglDisable glDisable
+#define qglDisableClientState glDisableClientState
+#define qglDrawArrays glDrawArrays
+#define qglDrawBuffer glDrawBuffer
+#define qglDrawElements glDrawElements
+#define qglDrawPixels glDrawPixels
+#define qglEdgeFlag glEdgeFlag
+#define qglEdgeFlagPointer glEdgeFlagPointer
+#define qglEdgeFlagv glEdgeFlagv
+#define qglEnable glEnable
+#define qglEnableClientState glEnableClientState
+#define qglEnd glEnd
+#define qglEndList glEndList
+#define qglEvalCoord1d glEvalCoord1d
+#define qglEvalCoord1dv glEvalCoord1dv
+#define qglEvalCoord1f glEvalCoord1f
+#define qglEvalCoord1fv glEvalCoord1fv
+#define qglEvalCoord2d glEvalCoord2d
+#define qglEvalCoord2dv glEvalCoord2dv
+#define qglEvalCoord2f glEvalCoord2f
+#define qglEvalCoord2fv glEvalCoord2fv
+#define qglEvalMesh1 glEvalMesh1
+#define qglEvalMesh2 glEvalMesh2
+#define qglEvalPoint1 glEvalPoint1
+#define qglEvalPoint2 glEvalPoint2
+#define qglFeedbackBuffer glFeedbackBuffer
+#define qglFinish glFinish
+#define qglFlush glFlush
+#define qglFogf glFogf
+#define qglFogfv glFogfv
+#define qglFogi glFogi
+#define qglFogiv glFogiv
+#define qglFrontFace glFrontFace
+#define qglFrustum glFrustum
+#define qglGenLists glGenLists
+#define qglGenTextures glGenTextures
+#define qglGetBooleanv glGetBooleanv
+#define qglGetClipPlane glGetClipPlane
+#define qglGetDoublev glGetDoublev
+#define qglGetError glGetError
+#define qglGetFloatv glGetFloatv
+#define qglGetIntegerv glGetIntegerv
+#define qglGetLightfv glGetLightfv
+#define qglGetLightiv glGetLightiv
+#define qglGetMapdv glGetMapdv
+#define qglGetMapfv glGetMapfv
+#define qglGetMapiv glGetMapiv
+#define qglGetMaterialfv glGetMaterialfv
+#define qglGetMaterialiv glGetMaterialiv
+#define qglGetPixelMapfv glGetPixelMapfv
+#define qglGetPixelMapuiv glGetPixelMapuiv
+#define qglGetPixelMapusv glGetPixelMapusv
+#define qglGetPointerv glGetPointerv
+#define qglGetPolygonStipple glGetPolygonStipple
+#define qglGetString glGetString
+#define qglGetTexGendv glGetTexGendv
+#define qglGetTexGenfv glGetTexGenfv
+#define qglGetTexGeniv glGetTexGeniv
+#define qglGetTexImage glGetTexImage
+#define qglGetTexLevelParameterfv glGetTexLevelParameterfv
+#define qglGetTexLevelParameteriv glGetTexLevelParameteriv
+#define qglGetTexParameterfv glGetTexParameterfv
+#define qglGetTexParameteriv glGetTexParameteriv
+#define qglHint glHint
+#define qglIndexMask glIndexMask
+#define qglIndexPointer glIndexPointer
+#define qglIndexd glIndexd
+#define qglIndexdv glIndexdv
+#define qglIndexf glIndexf
+#define qglIndexfv glIndexfv
+#define qglIndexi glIndexi
+#define qglIndexiv glIndexiv
+#define qglIndexs glIndexs
+#define qglIndexsv glIndexsv
+#define qglIndexub glIndexub
+#define qglIndexubv glIndexubv
+#define qglInitNames glInitNames
+#define qglInterleavedArrays glInterleavedArrays
+#define qglIsEnabled glIsEnabled
+#define qglIsList glIsList
+#define qglIsTexture glIsTexture
+#define qglLightModelf glLightModelf
+#define qglLightModelfv glLightModelfv
+#define qglLightModeli glLightModeli
+#define qglLightModeliv glLightModeliv
+#define qglLightf glLightf
+#define qglLightfv glLightfv
+#define qglLighti glLighti
+#define qglLightiv glLightiv
+#define qglLineStipple glLineStipple
+#define qglLineWidth glLineWidth
+#define qglListBase glListBase
+#define qglLoadIdentity glLoadIdentity
+#define qglLoadMatrixd glLoadMatrixd
+#define qglLoadMatrixf glLoadMatrixf
+#define qglLoadName glLoadName
+#define qglLogicOp glLogicOp
+#define qglMap1d glMap1d
+#define qglMap1f glMap1f
+#define qglMap2d glMap2d
+#define qglMap2f glMap2f
+#define qglMapGrid1d glMapGrid1d
+#define qglMapGrid1f glMapGrid1f
+#define qglMapGrid2d glMapGrid2d
+#define qglMapGrid2f glMapGrid2f
+#define qglMaterialf glMaterialf
+#define qglMaterialfv glMaterialfv
+#define qglMateriali glMateriali
+#define qglMaterialiv glMaterialiv
+#define qglMatrixMode glMatrixMode
+#define qglMultMatrixd glMultMatrixd
+#define qglMultMatrixf glMultMatrixf
+#define qglNewList glNewList
+#define qglNormal3b glNormal3b
+#define qglNormal3bv glNormal3bv
+#define qglNormal3d glNormal3d
+#define qglNormal3dv glNormal3dv
+#define qglNormal3f glNormal3f
+#define qglNormal3fv glNormal3fv
+#define qglNormal3i glNormal3i
+#define qglNormal3iv glNormal3iv
+#define qglNormal3s glNormal3s
+#define qglNormal3sv glNormal3sv
+#define qglNormalPointer glNormalPointer
+#define qglOrtho glOrtho
+#define qglPassThrough glPassThrough
+#define qglPixelMapfv glPixelMapfv
+#define qglPixelMapuiv glPixelMapuiv
+#define qglPixelMapusv glPixelMapusv
+#define qglPixelStoref glPixelStoref
+#define qglPixelStorei glPixelStorei
+#define qglPixelTransferf glPixelTransferf
+#define qglPixelTransferi glPixelTransferi
+#define qglPixelZoom glPixelZoom
+#define qglPointSize glPointSize
+#define qglPolygonMode glPolygonMode
+#define qglPolygonOffset glPolygonOffset
+#define qglPolygonStipple glPolygonStipple
+#define qglPopAttrib glPopAttrib
+#define qglPopClientAttrib glPopClientAttrib
+#define qglPopMatrix glPopMatrix
+#define qglPopName glPopName
+#define qglPrioritizeTextures glPrioritizeTextures
+#define qglPushAttrib glPushAttrib
+#define qglPushClientAttrib glPushClientAttrib
+#define qglPushMatrix glPushMatrix
+#define qglPushName glPushName
+#define qglRasterPos2d glRasterPos2d
+#define qglRasterPos2dv glRasterPos2dv
+#define qglRasterPos2f glRasterPos2f
+#define qglRasterPos2fv glRasterPos2fv
+#define qglRasterPos2i glRasterPos2i
+#define qglRasterPos2iv glRasterPos2iv
+#define qglRasterPos2s glRasterPos2s
+#define qglRasterPos2sv glRasterPos2sv
+#define qglRasterPos3d glRasterPos3d
+#define qglRasterPos3dv glRasterPos3dv
+#define qglRasterPos3f glRasterPos3f
+#define qglRasterPos3fv glRasterPos3fv
+#define qglRasterPos3i glRasterPos3i
+#define qglRasterPos3iv glRasterPos3iv
+#define qglRasterPos3s glRasterPos3s
+#define qglRasterPos3sv glRasterPos3sv
+#define qglRasterPos4d glRasterPos4d
+#define qglRasterPos4dv glRasterPos4dv
+#define qglRasterPos4f glRasterPos4f
+#define qglRasterPos4fv glRasterPos4fv
+#define qglRasterPos4i glRasterPos4i
+#define qglRasterPos4iv glRasterPos4iv
+#define qglRasterPos4s glRasterPos4s
+#define qglRasterPos4sv glRasterPos4sv
+#define qglReadBuffer glReadBuffer
+#define qglReadPixels glReadPixels
+#define qglRectd glRectd
+#define qglRectdv glRectdv
+#define qglRectf glRectf
+#define qglRectfv glRectfv
+#define qglRecti glRecti
+#define qglRectiv glRectiv
+#define qglRects glRects
+#define qglRectsv glRectsv
+#define qglRenderMode glRenderMode
+#define qglRotated glRotated
+#define qglRotatef glRotatef
+#define qglScaled glScaled
+#define qglScalef glScalef
+#define qglScissor glScissor
+#define qglSelectBuffer glSelectBuffer
+#define qglShadeModel glShadeModel
+#define qglStencilFunc glStencilFunc
+#define qglStencilMask glStencilMask
+#define qglStencilOp glStencilOp
+#define qglTexCoord1d glTexCoord1d
+#define qglTexCoord1dv glTexCoord1dv
+#define qglTexCoord1f glTexCoord1f
+#define qglTexCoord1fv glTexCoord1fv
+#define qglTexCoord1i glTexCoord1i
+#define qglTexCoord1iv glTexCoord1iv
+#define qglTexCoord1s glTexCoord1s
+#define qglTexCoord1sv glTexCoord1sv
+#define qglTexCoord2d glTexCoord2d
+#define qglTexCoord2dv glTexCoord2dv
+#define qglTexCoord2f glTexCoord2f
+#define qglTexCoord2fv glTexCoord2fv
+#define qglTexCoord2i glTexCoord2i
+#define qglTexCoord2iv glTexCoord2iv
+#define qglTexCoord2s glTexCoord2s
+#define qglTexCoord2sv glTexCoord2sv
+#define qglTexCoord3d glTexCoord3d
+#define qglTexCoord3dv glTexCoord3dv
+#define qglTexCoord3f glTexCoord3f
+#define qglTexCoord3fv glTexCoord3fv
+#define qglTexCoord3i glTexCoord3i
+#define qglTexCoord3iv glTexCoord3iv
+#define qglTexCoord3s glTexCoord3s
+#define qglTexCoord3sv glTexCoord3sv
+#define qglTexCoord4d glTexCoord4d
+#define qglTexCoord4dv glTexCoord4dv
+#define qglTexCoord4f glTexCoord4f
+#define qglTexCoord4fv glTexCoord4fv
+#define qglTexCoord4i glTexCoord4i
+#define qglTexCoord4iv glTexCoord4iv
+#define qglTexCoord4s glTexCoord4s
+#define qglTexCoord4sv glTexCoord4sv
+#define qglTexCoordPointer glTexCoordPointer
+#define qglTexEnvf glTexEnvf
+#define qglTexEnvfv glTexEnvfv
+#define qglTexEnvi glTexEnvi
+#define qglTexEnviv glTexEnviv
+#define qglTexGend glTexGend
+#define qglTexGendv glTexGendv
+#define qglTexGenf glTexGenf
+#define qglTexGenfv glTexGenfv
+#define qglTexGeni glTexGeni
+#define qglTexGeniv glTexGeniv
+#define qglTexImage1D glTexImage1D
+#define qglTexImage2D glTexImage2D
+#define qglTexParameterf glTexParameterf
+#define qglTexParameterfv glTexParameterfv
+#define qglTexParameteri glTexParameteri
+#define qglTexParameteriv glTexParameteriv
+#define qglTexSubImage1D glTexSubImage1D
+#define qglTexSubImage2D glTexSubImage2D
+#define qglTranslated glTranslated
+#define qglTranslatef glTranslatef
+#define qglVertex2d glVertex2d
+#define qglVertex2dv glVertex2dv
+#define qglVertex2f glVertex2f
+#define qglVertex2fv glVertex2fv
+#define qglVertex2i glVertex2i
+#define qglVertex2iv glVertex2iv
+#define qglVertex2s glVertex2s
+#define qglVertex2sv glVertex2sv
+#define qglVertex3d glVertex3d
+#define qglVertex3dv glVertex3dv
+#define qglVertex3f glVertex3f
+#define qglVertex3fv glVertex3fv
+#define qglVertex3i glVertex3i
+#define qglVertex3iv glVertex3iv
+#define qglVertex3s glVertex3s
+#define qglVertex3sv glVertex3sv
+#define qglVertex4d glVertex4d
+#define qglVertex4dv glVertex4dv
+#define qglVertex4f glVertex4f
+#define qglVertex4fv glVertex4fv
+#define qglVertex4i glVertex4i
+#define qglVertex4iv glVertex4iv
+#define qglVertex4s glVertex4s
+#define qglVertex4sv glVertex4sv
+#define qglVertexPointer glVertexPointer
+#define qglViewport glViewport
+
+#endif
+#endif
diff --git a/code/renderer/tr_animation.c b/code/renderer/tr_animation.c
new file mode 100644
index 0000000..8f42456
--- /dev/null
+++ b/code/renderer/tr_animation.c
@@ -0,0 +1,658 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "tr_local.h"
+
+/*
+
+All bones should be an identity orientation to display the mesh exactly
+as it is specified.
+
+For all other frames, the bones represent the transformation from the
+orientation of the bone in the base frame to the orientation in this
+frame.
+
+*/
+
+/*
+==============
+R_AddAnimSurfaces
+==============
+*/
+void R_AddAnimSurfaces( trRefEntity_t *ent ) {
+ md4Header_t *header;
+ md4Surface_t *surface;
+ md4LOD_t *lod;
+ shader_t *shader;
+ int i;
+
+ header = (md4Header_t *) tr.currentModel->md4;
+ lod = (md4LOD_t *)( (byte *)header + header->ofsLODs );
+
+ surface = (md4Surface_t *)( (byte *)lod + lod->ofsSurfaces );
+ for ( i = 0 ; i < lod->numSurfaces ; i++ ) {
+ shader = R_GetShaderByHandle( surface->shaderIndex );
+ R_AddDrawSurf( (void *)surface, shader, 0 /*fogNum*/, qfalse );
+ surface = (md4Surface_t *)( (byte *)surface + surface->ofsEnd );
+ }
+}
+
+/*
+==============
+RB_SurfaceAnim
+==============
+*/
+void RB_SurfaceAnim( md4Surface_t *surface ) {
+ int i, j, k;
+ float frontlerp, backlerp;
+ int *triangles;
+ int indexes;
+ int baseIndex, baseVertex;
+ int numVerts;
+ md4Vertex_t *v;
+ md4Bone_t bones[MD4_MAX_BONES];
+ md4Bone_t *bonePtr, *bone;
+ md4Header_t *header;
+ md4Frame_t *frame;
+ md4Frame_t *oldFrame;
+ int frameSize;
+
+
+ if ( backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame ) {
+ backlerp = 0;
+ frontlerp = 1;
+ } else {
+ backlerp = backEnd.currentEntity->e.backlerp;
+ frontlerp = 1.0f - backlerp;
+ }
+ header = (md4Header_t *)((byte *)surface + surface->ofsHeader);
+
+ frameSize = (size_t)( &((md4Frame_t *)0)->bones[ header->numBones ] );
+
+ frame = (md4Frame_t *)((byte *)header + header->ofsFrames +
+ backEnd.currentEntity->e.frame * frameSize );
+ oldFrame = (md4Frame_t *)((byte *)header + header->ofsFrames +
+ backEnd.currentEntity->e.oldframe * frameSize );
+
+ RB_CheckOverflow( surface->numVerts, surface->numTriangles * 3 );
+
+ triangles = (int *) ((byte *)surface + surface->ofsTriangles);
+ indexes = surface->numTriangles * 3;
+ baseIndex = tess.numIndexes;
+ baseVertex = tess.numVertexes;
+ for (j = 0 ; j < indexes ; j++) {
+ tess.indexes[baseIndex + j] = baseIndex + triangles[j];
+ }
+ tess.numIndexes += indexes;
+
+ //
+ // lerp all the needed bones
+ //
+ if ( !backlerp ) {
+ // no lerping needed
+ bonePtr = frame->bones;
+ } else {
+ bonePtr = bones;
+ for ( i = 0 ; i < header->numBones*12 ; i++ ) {
+ ((float *)bonePtr)[i] = frontlerp * ((float *)frame->bones)[i]
+ + backlerp * ((float *)oldFrame->bones)[i];
+ }
+ }
+
+ //
+ // deform the vertexes by the lerped bones
+ //
+ numVerts = surface->numVerts;
+ // FIXME
+ // This makes TFC's skeletons work. Shouldn't be necessary anymore, but left
+ // in for reference.
+ //v = (md4Vertex_t *) ((byte *)surface + surface->ofsVerts + 12);
+ v = (md4Vertex_t *) ((byte *)surface + surface->ofsVerts);
+ for ( j = 0; j < numVerts; j++ ) {
+ vec3_t tempVert, tempNormal;
+ md4Weight_t *w;
+
+ VectorClear( tempVert );
+ VectorClear( tempNormal );
+ w = v->weights;
+ for ( k = 0 ; k < v->numWeights ; k++, w++ ) {
+ bone = bonePtr + w->boneIndex;
+
+ tempVert[0] += w->boneWeight * ( DotProduct( bone->matrix[0], w->offset ) + bone->matrix[0][3] );
+ tempVert[1] += w->boneWeight * ( DotProduct( bone->matrix[1], w->offset ) + bone->matrix[1][3] );
+ tempVert[2] += w->boneWeight * ( DotProduct( bone->matrix[2], w->offset ) + bone->matrix[2][3] );
+
+ tempNormal[0] += w->boneWeight * DotProduct( bone->matrix[0], v->normal );
+ tempNormal[1] += w->boneWeight * DotProduct( bone->matrix[1], v->normal );
+ tempNormal[2] += w->boneWeight * DotProduct( bone->matrix[2], v->normal );
+ }
+
+ tess.xyz[baseVertex + j][0] = tempVert[0];
+ tess.xyz[baseVertex + j][1] = tempVert[1];
+ tess.xyz[baseVertex + j][2] = tempVert[2];
+
+ tess.normal[baseVertex + j][0] = tempNormal[0];
+ tess.normal[baseVertex + j][1] = tempNormal[1];
+ tess.normal[baseVertex + j][2] = tempNormal[2];
+
+ tess.texCoords[baseVertex + j][0][0] = v->texCoords[0];
+ tess.texCoords[baseVertex + j][0][1] = v->texCoords[1];
+
+ // FIXME
+ // This makes TFC's skeletons work. Shouldn't be necessary anymore, but left
+ // in for reference.
+ //v = (md4Vertex_t *)( ( byte * )&v->weights[v->numWeights] + 12 );
+ v = (md4Vertex_t *)&v->weights[v->numWeights];
+ }
+
+ tess.numVertexes += surface->numVerts;
+}
+
+
+#ifdef RAVENMD4
+
+// copied and adapted from tr_mesh.c
+
+/*
+=============
+R_MDRCullModel
+=============
+*/
+
+static int R_MDRCullModel( mdrHeader_t *header, trRefEntity_t *ent ) {
+ vec3_t bounds[2];
+ mdrFrame_t *oldFrame, *newFrame;
+ int i, frameSize;
+
+ frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] );
+
+ // compute frame pointers
+ newFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.frame);
+ oldFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.oldframe);
+
+ // cull bounding sphere ONLY if this is not an upscaled entity
+ if ( !ent->e.nonNormalizedAxes )
+ {
+ if ( ent->e.frame == ent->e.oldframe )
+ {
+ switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) )
+ {
+ // Ummm... yeah yeah I know we don't really have an md3 here.. but we pretend
+ // we do. After all, the purpose of md4s are not that different, are they?
+
+ case CULL_OUT:
+ tr.pc.c_sphere_cull_md3_out++;
+ return CULL_OUT;
+
+ case CULL_IN:
+ tr.pc.c_sphere_cull_md3_in++;
+ return CULL_IN;
+
+ case CULL_CLIP:
+ tr.pc.c_sphere_cull_md3_clip++;
+ break;
+ }
+ }
+ else
+ {
+ int sphereCull, sphereCullB;
+
+ sphereCull = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius );
+ if ( newFrame == oldFrame ) {
+ sphereCullB = sphereCull;
+ } else {
+ sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius );
+ }
+
+ if ( sphereCull == sphereCullB )
+ {
+ if ( sphereCull == CULL_OUT )
+ {
+ tr.pc.c_sphere_cull_md3_out++;
+ return CULL_OUT;
+ }
+ else if ( sphereCull == CULL_IN )
+ {
+ tr.pc.c_sphere_cull_md3_in++;
+ return CULL_IN;
+ }
+ else
+ {
+ tr.pc.c_sphere_cull_md3_clip++;
+ }
+ }
+ }
+ }
+
+ // calculate a bounding box in the current coordinate system
+ for (i = 0 ; i < 3 ; i++) {
+ bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i];
+ bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i];
+ }
+
+ switch ( R_CullLocalBox( bounds ) )
+ {
+ case CULL_IN:
+ tr.pc.c_box_cull_md3_in++;
+ return CULL_IN;
+ case CULL_CLIP:
+ tr.pc.c_box_cull_md3_clip++;
+ return CULL_CLIP;
+ case CULL_OUT:
+ default:
+ tr.pc.c_box_cull_md3_out++;
+ return CULL_OUT;
+ }
+}
+
+/*
+=================
+R_MDRComputeFogNum
+
+=================
+*/
+
+int R_MDRComputeFogNum( mdrHeader_t *header, trRefEntity_t *ent ) {
+ int i, j;
+ fog_t *fog;
+ mdrFrame_t *mdrFrame;
+ vec3_t localOrigin;
+ int frameSize;
+
+ if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) {
+ return 0;
+ }
+
+ frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] );
+
+ // FIXME: non-normalized axis issues
+ mdrFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.frame);
+ VectorAdd( ent->e.origin, mdrFrame->localOrigin, localOrigin );
+ for ( i = 1 ; i < tr.world->numfogs ; i++ ) {
+ fog = &tr.world->fogs[i];
+ for ( j = 0 ; j < 3 ; j++ ) {
+ if ( localOrigin[j] - mdrFrame->radius >= fog->bounds[1][j] ) {
+ break;
+ }
+ if ( localOrigin[j] + mdrFrame->radius <= fog->bounds[0][j] ) {
+ break;
+ }
+ }
+ if ( j == 3 ) {
+ return i;
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+==============
+R_MDRAddAnimSurfaces
+==============
+*/
+
+// much stuff in there is just copied from R_AddMd3Surfaces in tr_mesh.c
+
+void R_MDRAddAnimSurfaces( trRefEntity_t *ent ) {
+ mdrHeader_t *header;
+ mdrSurface_t *surface;
+ mdrLOD_t *lod;
+ shader_t *shader;
+ skin_t *skin;
+ int i, j;
+ int lodnum = 0;
+ int fogNum = 0;
+ int cull;
+ qboolean personalModel;
+
+ header = (mdrHeader_t *) tr.currentModel->md4;
+
+ personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal;
+
+ if ( ent->e.renderfx & RF_WRAP_FRAMES )
+ {
+ ent->e.frame %= header->numFrames;
+ ent->e.oldframe %= header->numFrames;
+ }
+
+ //
+ // Validate the frames so there is no chance of a crash.
+ // This will write directly into the entity structure, so
+ // when the surfaces are rendered, they don't need to be
+ // range checked again.
+ //
+ if ((ent->e.frame >= header->numFrames)
+ || (ent->e.frame < 0)
+ || (ent->e.oldframe >= header->numFrames)
+ || (ent->e.oldframe < 0) )
+ {
+ ri.Printf( PRINT_DEVELOPER, "R_MDRAddAnimSurfaces: no such frame %d to %d for '%s'\n",
+ ent->e.oldframe, ent->e.frame, tr.currentModel->name );
+ ent->e.frame = 0;
+ ent->e.oldframe = 0;
+ }
+
+ //
+ // cull the entire model if merged bounding box of both frames
+ // is outside the view frustum.
+ //
+ cull = R_MDRCullModel (header, ent);
+ if ( cull == CULL_OUT ) {
+ return;
+ }
+
+ // figure out the current LOD of the model we're rendering, and set the lod pointer respectively.
+ lodnum = R_ComputeLOD(ent);
+ // check whether this model has as that many LODs at all. If not, try the closest thing we got.
+ if(header->numLODs <= 0)
+ return;
+ if(header->numLODs <= lodnum)
+ lodnum = header->numLODs - 1;
+
+ lod = (mdrLOD_t *)( (byte *)header + header->ofsLODs);
+ for(i = 0; i < lodnum; i++)
+ {
+ lod = (mdrLOD_t *) ((byte *) lod + lod->ofsEnd);
+ }
+
+ // set up lighting
+ if ( !personalModel || r_shadows->integer > 1 )
+ {
+ R_SetupEntityLighting( &tr.refdef, ent );
+ }
+
+ // fogNum?
+ fogNum = R_MDRComputeFogNum( header, ent );
+
+ surface = (mdrSurface_t *)( (byte *)lod + lod->ofsSurfaces );
+
+ for ( i = 0 ; i < lod->numSurfaces ; i++ )
+ {
+
+ if(ent->e.customShader)
+ shader = R_GetShaderByHandle(ent->e.customShader);
+ else if(ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins)
+ {
+ skin = R_GetSkinByHandle(ent->e.customSkin);
+ shader = tr.defaultShader;
+
+ for(j = 0; j < skin->numSurfaces; j++)
+ {
+ if (!strcmp(skin->surfaces[j]->name, surface->name))
+ {
+ shader = skin->surfaces[j]->shader;
+ break;
+ }
+ }
+ }
+ else if(surface->shaderIndex > 0)
+ shader = R_GetShaderByHandle( surface->shaderIndex );
+ else
+ shader = tr.defaultShader;
+
+ // we will add shadows even if the main object isn't visible in the view
+
+ // stencil shadows can't do personal models unless I polyhedron clip
+ if ( !personalModel
+ && r_shadows->integer == 2
+ && fogNum == 0
+ && !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) )
+ && shader->sort == SS_OPAQUE )
+ {
+ R_AddDrawSurf( (void *)surface, tr.shadowShader, 0, qfalse );
+ }
+
+ // projection shadows work fine with personal models
+ if ( r_shadows->integer == 3
+ && fogNum == 0
+ && (ent->e.renderfx & RF_SHADOW_PLANE )
+ && shader->sort == SS_OPAQUE )
+ {
+ R_AddDrawSurf( (void *)surface, tr.projectionShadowShader, 0, qfalse );
+ }
+
+ if (!personalModel)
+ R_AddDrawSurf( (void *)surface, shader, fogNum, qfalse );
+
+ surface = (mdrSurface_t *)( (byte *)surface + surface->ofsEnd );
+ }
+}
+
+/*
+==============
+RB_MDRSurfaceAnim
+==============
+*/
+void RB_MDRSurfaceAnim( md4Surface_t *surface )
+{
+ int i, j, k;
+ float frontlerp, backlerp;
+ int *triangles;
+ int indexes;
+ int baseIndex, baseVertex;
+ int numVerts;
+ mdrVertex_t *v;
+ mdrHeader_t *header;
+ mdrFrame_t *frame;
+ mdrFrame_t *oldFrame;
+ mdrBone_t bones[MD4_MAX_BONES], *bonePtr, *bone;
+
+ int frameSize;
+
+ // don't lerp if lerping off, or this is the only frame, or the last frame...
+ //
+ if (backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame)
+ {
+ backlerp = 0; // if backlerp is 0, lerping is off and frontlerp is never used
+ frontlerp = 1;
+ }
+ else
+ {
+ backlerp = backEnd.currentEntity->e.backlerp;
+ frontlerp = 1.0f - backlerp;
+ }
+
+ header = (mdrHeader_t *)((byte *)surface + surface->ofsHeader);
+
+ frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] );
+
+ frame = (mdrFrame_t *)((byte *)header + header->ofsFrames +
+ backEnd.currentEntity->e.frame * frameSize );
+ oldFrame = (mdrFrame_t *)((byte *)header + header->ofsFrames +
+ backEnd.currentEntity->e.oldframe * frameSize );
+
+ RB_CheckOverflow( surface->numVerts, surface->numTriangles );
+
+ triangles = (int *) ((byte *)surface + surface->ofsTriangles);
+ indexes = surface->numTriangles * 3;
+ baseIndex = tess.numIndexes;
+ baseVertex = tess.numVertexes;
+
+ // Set up all triangles.
+ for (j = 0 ; j < indexes ; j++)
+ {
+ tess.indexes[baseIndex + j] = baseVertex + triangles[j];
+ }
+ tess.numIndexes += indexes;
+
+ //
+ // lerp all the needed bones
+ //
+ if ( !backlerp )
+ {
+ // no lerping needed
+ bonePtr = frame->bones;
+ }
+ else
+ {
+ bonePtr = bones;
+
+ for ( i = 0 ; i < header->numBones*12 ; i++ )
+ {
+ ((float *)bonePtr)[i] = frontlerp * ((float *)frame->bones)[i] + backlerp * ((float *)oldFrame->bones)[i];
+ }
+ }
+
+ //
+ // deform the vertexes by the lerped bones
+ //
+ numVerts = surface->numVerts;
+ v = (mdrVertex_t *) ((byte *)surface + surface->ofsVerts);
+ for ( j = 0; j < numVerts; j++ )
+ {
+ vec3_t tempVert, tempNormal;
+ mdrWeight_t *w;
+
+ VectorClear( tempVert );
+ VectorClear( tempNormal );
+ w = v->weights;
+ for ( k = 0 ; k < v->numWeights ; k++, w++ )
+ {
+ bone = bonePtr + w->boneIndex;
+
+ tempVert[0] += w->boneWeight * ( DotProduct( bone->matrix[0], w->offset ) + bone->matrix[0][3] );
+ tempVert[1] += w->boneWeight * ( DotProduct( bone->matrix[1], w->offset ) + bone->matrix[1][3] );
+ tempVert[2] += w->boneWeight * ( DotProduct( bone->matrix[2], w->offset ) + bone->matrix[2][3] );
+
+ tempNormal[0] += w->boneWeight * DotProduct( bone->matrix[0], v->normal );
+ tempNormal[1] += w->boneWeight * DotProduct( bone->matrix[1], v->normal );
+ tempNormal[2] += w->boneWeight * DotProduct( bone->matrix[2], v->normal );
+ }
+
+ tess.xyz[baseVertex + j][0] = tempVert[0];
+ tess.xyz[baseVertex + j][1] = tempVert[1];
+ tess.xyz[baseVertex + j][2] = tempVert[2];
+
+ tess.normal[baseVertex + j][0] = tempNormal[0];
+ tess.normal[baseVertex + j][1] = tempNormal[1];
+ tess.normal[baseVertex + j][2] = tempNormal[2];
+
+ tess.texCoords[baseVertex + j][0][0] = v->texCoords[0];
+ tess.texCoords[baseVertex + j][0][1] = v->texCoords[1];
+
+ v = (mdrVertex_t *)&v->weights[v->numWeights];
+ }
+
+ tess.numVertexes += surface->numVerts;
+}
+
+
+#define MC_MASK_X ((1<<(MC_BITS_X))-1)
+#define MC_MASK_Y ((1<<(MC_BITS_Y))-1)
+#define MC_MASK_Z ((1<<(MC_BITS_Z))-1)
+#define MC_MASK_VECT ((1<<(MC_BITS_VECT))-1)
+
+#define MC_SCALE_VECT (1.0f/(float)((1<<(MC_BITS_VECT-1))-2))
+
+#define MC_POS_X (0)
+#define MC_SHIFT_X (0)
+
+#define MC_POS_Y ((((MC_BITS_X))/8))
+#define MC_SHIFT_Y ((((MC_BITS_X)%8)))
+
+#define MC_POS_Z ((((MC_BITS_X+MC_BITS_Y))/8))
+#define MC_SHIFT_Z ((((MC_BITS_X+MC_BITS_Y)%8)))
+
+#define MC_POS_V11 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z))/8))
+#define MC_SHIFT_V11 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z)%8)))
+
+#define MC_POS_V12 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT))/8))
+#define MC_SHIFT_V12 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT)%8)))
+
+#define MC_POS_V13 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*2))/8))
+#define MC_SHIFT_V13 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*2)%8)))
+
+#define MC_POS_V21 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*3))/8))
+#define MC_SHIFT_V21 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*3)%8)))
+
+#define MC_POS_V22 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*4))/8))
+#define MC_SHIFT_V22 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*4)%8)))
+
+#define MC_POS_V23 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*5))/8))
+#define MC_SHIFT_V23 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*5)%8)))
+
+#define MC_POS_V31 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*6))/8))
+#define MC_SHIFT_V31 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*6)%8)))
+
+#define MC_POS_V32 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*7))/8))
+#define MC_SHIFT_V32 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*7)%8)))
+
+#define MC_POS_V33 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*8))/8))
+#define MC_SHIFT_V33 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*8)%8)))
+
+void MC_UnCompress(float mat[3][4],const unsigned char * comp)
+{
+ int val;
+
+ val=(int)((unsigned short *)(comp))[0];
+ val-=1<<(MC_BITS_X-1);
+ mat[0][3]=((float)(val))*MC_SCALE_X;
+
+ val=(int)((unsigned short *)(comp))[1];
+ val-=1<<(MC_BITS_Y-1);
+ mat[1][3]=((float)(val))*MC_SCALE_Y;
+
+ val=(int)((unsigned short *)(comp))[2];
+ val-=1<<(MC_BITS_Z-1);
+ mat[2][3]=((float)(val))*MC_SCALE_Z;
+
+ val=(int)((unsigned short *)(comp))[3];
+ val-=1<<(MC_BITS_VECT-1);
+ mat[0][0]=((float)(val))*MC_SCALE_VECT;
+
+ val=(int)((unsigned short *)(comp))[4];
+ val-=1<<(MC_BITS_VECT-1);
+ mat[0][1]=((float)(val))*MC_SCALE_VECT;
+
+ val=(int)((unsigned short *)(comp))[5];
+ val-=1<<(MC_BITS_VECT-1);
+ mat[0][2]=((float)(val))*MC_SCALE_VECT;
+
+
+ val=(int)((unsigned short *)(comp))[6];
+ val-=1<<(MC_BITS_VECT-1);
+ mat[1][0]=((float)(val))*MC_SCALE_VECT;
+
+ val=(int)((unsigned short *)(comp))[7];
+ val-=1<<(MC_BITS_VECT-1);
+ mat[1][1]=((float)(val))*MC_SCALE_VECT;
+
+ val=(int)((unsigned short *)(comp))[8];
+ val-=1<<(MC_BITS_VECT-1);
+ mat[1][2]=((float)(val))*MC_SCALE_VECT;
+
+
+ val=(int)((unsigned short *)(comp))[9];
+ val-=1<<(MC_BITS_VECT-1);
+ mat[2][0]=((float)(val))*MC_SCALE_VECT;
+
+ val=(int)((unsigned short *)(comp))[10];
+ val-=1<<(MC_BITS_VECT-1);
+ mat[2][1]=((float)(val))*MC_SCALE_VECT;
+
+ val=(int)((unsigned short *)(comp))[11];
+ val-=1<<(MC_BITS_VECT-1);
+ mat[2][2]=((float)(val))*MC_SCALE_VECT;
+}
+#endif
diff --git a/code/renderer/tr_backend.c b/code/renderer/tr_backend.c
new file mode 100644
index 0000000..5d90f94
--- /dev/null
+++ b/code/renderer/tr_backend.c
@@ -0,0 +1,1285 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#include "tr_local.h"
+
+backEndData_t *backEndData[SMP_FRAMES];
+backEndState_t backEnd;
+
+
+static float s_flipMatrix[16] = {
+ // convert from our coordinate system (looking down X)
+ // to OpenGL's coordinate system (looking down -Z)
+ 0, 0, -1, 0,
+ -1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 0, 1
+};
+
+
+/*
+** GL_Bind
+*/
+void GL_Bind( image_t *image ) {
+ int texnum;
+
+ if ( !image ) {
+ ri.Printf( PRINT_WARNING, "GL_Bind: NULL image\n" );
+ texnum = tr.defaultImage->texnum;
+ } else {
+ texnum = image->texnum;
+ }
+
+ if ( r_nobind->integer && tr.dlightImage ) { // performance evaluation option
+ texnum = tr.dlightImage->texnum;
+ }
+
+ if ( glState.currenttextures[glState.currenttmu] != texnum ) {
+ image->frameUsed = tr.frameCount;
+ glState.currenttextures[glState.currenttmu] = texnum;
+ qglBindTexture (GL_TEXTURE_2D, texnum);
+ }
+}
+
+/*
+** GL_SelectTexture
+*/
+void GL_SelectTexture( int unit )
+{
+ if ( glState.currenttmu == unit )
+ {
+ return;
+ }
+
+ if ( unit == 0 )
+ {
+ qglActiveTextureARB( GL_TEXTURE0_ARB );
+ GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE0_ARB )\n" );
+ qglClientActiveTextureARB( GL_TEXTURE0_ARB );
+ GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE0_ARB )\n" );
+ }
+ else if ( unit == 1 )
+ {
+ qglActiveTextureARB( GL_TEXTURE1_ARB );
+ GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE1_ARB )\n" );
+ qglClientActiveTextureARB( GL_TEXTURE1_ARB );
+ GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE1_ARB )\n" );
+ } else {
+ ri.Error( ERR_DROP, "GL_SelectTexture: unit = %i", unit );
+ }
+
+ glState.currenttmu = unit;
+}
+
+
+/*
+** GL_BindMultitexture
+*/
+void GL_BindMultitexture( image_t *image0, GLuint env0, image_t *image1, GLuint env1 ) {
+ int texnum0, texnum1;
+
+ texnum0 = image0->texnum;
+ texnum1 = image1->texnum;
+
+ if ( r_nobind->integer && tr.dlightImage ) { // performance evaluation option
+ texnum0 = texnum1 = tr.dlightImage->texnum;
+ }
+
+ if ( glState.currenttextures[1] != texnum1 ) {
+ GL_SelectTexture( 1 );
+ image1->frameUsed = tr.frameCount;
+ glState.currenttextures[1] = texnum1;
+ qglBindTexture( GL_TEXTURE_2D, texnum1 );
+ }
+ if ( glState.currenttextures[0] != texnum0 ) {
+ GL_SelectTexture( 0 );
+ image0->frameUsed = tr.frameCount;
+ glState.currenttextures[0] = texnum0;
+ qglBindTexture( GL_TEXTURE_2D, texnum0 );
+ }
+}
+
+
+/*
+** GL_Cull
+*/
+void GL_Cull( int cullType ) {
+ if ( glState.faceCulling == cullType ) {
+ return;
+ }
+
+ glState.faceCulling = cullType;
+
+ if ( cullType == CT_TWO_SIDED )
+ {
+ qglDisable( GL_CULL_FACE );
+ }
+ else
+ {
+ qglEnable( GL_CULL_FACE );
+
+ if ( cullType == CT_BACK_SIDED )
+ {
+ if ( backEnd.viewParms.isMirror )
+ {
+ qglCullFace( GL_FRONT );
+ }
+ else
+ {
+ qglCullFace( GL_BACK );
+ }
+ }
+ else
+ {
+ if ( backEnd.viewParms.isMirror )
+ {
+ qglCullFace( GL_BACK );
+ }
+ else
+ {
+ qglCullFace( GL_FRONT );
+ }
+ }
+ }
+}
+
+/*
+** GL_TexEnv
+*/
+void GL_TexEnv( int env )
+{
+ if ( env == glState.texEnv[glState.currenttmu] )
+ {
+ return;
+ }
+
+ glState.texEnv[glState.currenttmu] = env;
+
+
+ switch ( env )
+ {
+ case GL_MODULATE:
+ qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
+ break;
+ case GL_REPLACE:
+ qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
+ break;
+ case GL_DECAL:
+ qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL );
+ break;
+ case GL_ADD:
+ qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD );
+ break;
+ default:
+ ri.Error( ERR_DROP, "GL_TexEnv: invalid env '%d' passed\n", env );
+ break;
+ }
+}
+
+/*
+** GL_State
+**
+** This routine is responsible for setting the most commonly changed state
+** in Q3.
+*/
+void GL_State( unsigned long stateBits )
+{
+ unsigned long diff = stateBits ^ glState.glStateBits;
+
+ if ( !diff )
+ {
+ return;
+ }
+
+ //
+ // check depthFunc bits
+ //
+ if ( diff & GLS_DEPTHFUNC_EQUAL )
+ {
+ if ( stateBits & GLS_DEPTHFUNC_EQUAL )
+ {
+ qglDepthFunc( GL_EQUAL );
+ }
+ else
+ {
+ qglDepthFunc( GL_LEQUAL );
+ }
+ }
+
+ //
+ // check blend bits
+ //
+ if ( diff & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) )
+ {
+ GLenum srcFactor, dstFactor;
+
+ if ( stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) )
+ {
+ switch ( stateBits & GLS_SRCBLEND_BITS )
+ {
+ case GLS_SRCBLEND_ZERO:
+ srcFactor = GL_ZERO;
+ break;
+ case GLS_SRCBLEND_ONE:
+ srcFactor = GL_ONE;
+ break;
+ case GLS_SRCBLEND_DST_COLOR:
+ srcFactor = GL_DST_COLOR;
+ break;
+ case GLS_SRCBLEND_ONE_MINUS_DST_COLOR:
+ srcFactor = GL_ONE_MINUS_DST_COLOR;
+ break;
+ case GLS_SRCBLEND_SRC_ALPHA:
+ srcFactor = GL_SRC_ALPHA;
+ break;
+ case GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA:
+ srcFactor = GL_ONE_MINUS_SRC_ALPHA;
+ break;
+ case GLS_SRCBLEND_DST_ALPHA:
+ srcFactor = GL_DST_ALPHA;
+ break;
+ case GLS_SRCBLEND_ONE_MINUS_DST_ALPHA:
+ srcFactor = GL_ONE_MINUS_DST_ALPHA;
+ break;
+ case GLS_SRCBLEND_ALPHA_SATURATE:
+ srcFactor = GL_SRC_ALPHA_SATURATE;
+ break;
+ default:
+ srcFactor = GL_ONE; // to get warning to shut up
+ ri.Error( ERR_DROP, "GL_State: invalid src blend state bits\n" );
+ break;
+ }
+
+ switch ( stateBits & GLS_DSTBLEND_BITS )
+ {
+ case GLS_DSTBLEND_ZERO:
+ dstFactor = GL_ZERO;
+ break;
+ case GLS_DSTBLEND_ONE:
+ dstFactor = GL_ONE;
+ break;
+ case GLS_DSTBLEND_SRC_COLOR:
+ dstFactor = GL_SRC_COLOR;
+ break;
+ case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR:
+ dstFactor = GL_ONE_MINUS_SRC_COLOR;
+ break;
+ case GLS_DSTBLEND_SRC_ALPHA:
+ dstFactor = GL_SRC_ALPHA;
+ break;
+ case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA:
+ dstFactor = GL_ONE_MINUS_SRC_ALPHA;
+ break;
+ case GLS_DSTBLEND_DST_ALPHA:
+ dstFactor = GL_DST_ALPHA;
+ break;
+ case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA:
+ dstFactor = GL_ONE_MINUS_DST_ALPHA;
+ break;
+ default:
+ dstFactor = GL_ONE; // to get warning to shut up
+ ri.Error( ERR_DROP, "GL_State: invalid dst blend state bits\n" );
+ break;
+ }
+
+ qglEnable( GL_BLEND );
+ qglBlendFunc( srcFactor, dstFactor );
+ }
+ else
+ {
+ qglDisable( GL_BLEND );
+ }
+ }
+
+ //
+ // check depthmask
+ //
+ if ( diff & GLS_DEPTHMASK_TRUE )
+ {
+ if ( stateBits & GLS_DEPTHMASK_TRUE )
+ {
+ qglDepthMask( GL_TRUE );
+ }
+ else
+ {
+ qglDepthMask( GL_FALSE );
+ }
+ }
+
+ //
+ // fill/line mode
+ //
+#ifndef PANDORA
+ if ( diff & GLS_POLYMODE_LINE )
+ {
+ if ( stateBits & GLS_POLYMODE_LINE )
+ {
+ qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
+ }
+ else
+ {
+ qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
+ }
+ }
+#endif
+ //
+ // depthtest
+ //
+ if ( diff & GLS_DEPTHTEST_DISABLE )
+ {
+ if ( stateBits & GLS_DEPTHTEST_DISABLE )
+ {
+ qglDisable( GL_DEPTH_TEST );
+ }
+ else
+ {
+ qglEnable( GL_DEPTH_TEST );
+ }
+ }
+
+ //
+ // alpha test
+ //
+ if ( diff & GLS_ATEST_BITS )
+ {
+ switch ( stateBits & GLS_ATEST_BITS )
+ {
+ case 0:
+ qglDisable( GL_ALPHA_TEST );
+ break;
+ case GLS_ATEST_GT_0:
+ qglEnable( GL_ALPHA_TEST );
+ qglAlphaFunc( GL_GREATER, 0.0f );
+ break;
+ case GLS_ATEST_LT_80:
+ qglEnable( GL_ALPHA_TEST );
+ qglAlphaFunc( GL_LESS, 0.5f );
+ break;
+ case GLS_ATEST_GE_80:
+ qglEnable( GL_ALPHA_TEST );
+ qglAlphaFunc( GL_GEQUAL, 0.5f );
+ break;
+ default:
+ assert( 0 );
+ break;
+ }
+ }
+
+ glState.glStateBits = stateBits;
+}
+
+
+
+/*
+================
+RB_Hyperspace
+
+A player has predicted a teleport, but hasn't arrived yet
+================
+*/
+static void RB_Hyperspace( void ) {
+ float c;
+
+ if ( !backEnd.isHyperspace ) {
+ // do initialization shit
+ }
+
+ c = ( backEnd.refdef.time & 255 ) / 255.0f;
+ qglClearColor( c, c, c, 1 );
+ qglClear( GL_COLOR_BUFFER_BIT );
+
+ backEnd.isHyperspace = qtrue;
+}
+
+
+static void SetViewportAndScissor( void ) {
+ qglMatrixMode(GL_PROJECTION);
+ qglLoadMatrixf( backEnd.viewParms.projectionMatrix );
+ qglMatrixMode(GL_MODELVIEW);
+
+ // set the window clipping
+ qglViewport( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY,
+ backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight );
+ qglScissor( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY,
+ backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight );
+}
+
+/*
+=================
+RB_BeginDrawingView
+
+Any mirrored or portaled views have already been drawn, so prepare
+to actually render the visible surfaces for this view
+=================
+*/
+void RB_BeginDrawingView (void) {
+ int clearBits = 0;
+
+ // sync with gl if needed
+ if ( r_finish->integer == 1 && !glState.finishCalled ) {
+ qglFinish ();
+ glState.finishCalled = qtrue;
+ }
+ if ( r_finish->integer == 0 ) {
+ glState.finishCalled = qtrue;
+ }
+
+ // we will need to change the projection matrix before drawing
+ // 2D images again
+ backEnd.projection2D = qfalse;
+
+ //
+ // set the modelview matrix for the viewer
+ //
+ SetViewportAndScissor();
+
+ // ensures that depth writes are enabled for the depth clear
+ GL_State( GLS_DEFAULT );
+ // clear relevant buffers
+ clearBits = GL_DEPTH_BUFFER_BIT;
+
+ if ( r_measureOverdraw->integer || r_shadows->integer == 2 )
+ {
+ clearBits |= GL_STENCIL_BUFFER_BIT;
+ }
+ if ( r_fastsky->integer && !( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) )
+ {
+ clearBits |= GL_COLOR_BUFFER_BIT; // FIXME: only if sky shaders have been used
+#ifdef _DEBUG
+ qglClearColor( 0.8f, 0.7f, 0.4f, 1.0f ); // FIXME: get color of sky
+#else
+ qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); // FIXME: get color of sky
+#endif
+ }
+ qglClear( clearBits );
+
+ if ( ( backEnd.refdef.rdflags & RDF_HYPERSPACE ) )
+ {
+ RB_Hyperspace();
+ return;
+ }
+ else
+ {
+ backEnd.isHyperspace = qfalse;
+ }
+
+ glState.faceCulling = -1; // force face culling to set next time
+
+ // we will only draw a sun if there was sky rendered in this view
+ backEnd.skyRenderedThisView = qfalse;
+
+ // clip to the plane of the portal
+ if ( backEnd.viewParms.isPortal ) {
+ float plane[4];
+#ifdef PANDORA
+ float plane2[4];
+#else
+ double plane2[4];
+#endif
+ plane[0] = backEnd.viewParms.portalPlane.normal[0];
+ plane[1] = backEnd.viewParms.portalPlane.normal[1];
+ plane[2] = backEnd.viewParms.portalPlane.normal[2];
+ plane[3] = backEnd.viewParms.portalPlane.dist;
+
+ plane2[0] = DotProduct (backEnd.viewParms.or.axis[0], plane);
+ plane2[1] = DotProduct (backEnd.viewParms.or.axis[1], plane);
+ plane2[2] = DotProduct (backEnd.viewParms.or.axis[2], plane);
+ plane2[3] = DotProduct (plane, backEnd.viewParms.or.origin) - plane[3];
+
+ qglLoadMatrixf( s_flipMatrix );
+#ifdef PANDORA
+ qglClipPlanef (GL_CLIP_PLANE0, plane2);
+#else
+ qglClipPlane (GL_CLIP_PLANE0, plane2);
+#endif
+ qglEnable (GL_CLIP_PLANE0);
+ } else {
+ qglDisable (GL_CLIP_PLANE0);
+ }
+}
+
+
+#define MAC_EVENT_PUMP_MSEC 5
+
+/*
+==================
+RB_RenderDrawSurfList
+==================
+*/
+void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) {
+ shader_t *shader, *oldShader;
+ int fogNum, oldFogNum;
+ int entityNum, oldEntityNum;
+ int dlighted, oldDlighted;
+ qboolean depthRange, oldDepthRange, isCrosshair, wasCrosshair;
+ int i;
+ drawSurf_t *drawSurf;
+ int oldSort;
+ float originalTime;
+
+ // save original time for entity shader offsets
+ originalTime = backEnd.refdef.floatTime;
+
+ // clear the z buffer, set the modelview, etc
+ RB_BeginDrawingView ();
+
+ // draw everything
+ oldEntityNum = -1;
+ backEnd.currentEntity = &tr.worldEntity;
+ oldShader = NULL;
+ oldFogNum = -1;
+ oldDepthRange = qfalse;
+ wasCrosshair = qfalse;
+ oldDlighted = qfalse;
+ oldSort = -1;
+ depthRange = qfalse;
+
+ backEnd.pc.c_surfaces += numDrawSurfs;
+
+ for (i = 0, drawSurf = drawSurfs ; i < numDrawSurfs ; i++, drawSurf++) {
+ if ( drawSurf->sort == oldSort ) {
+ // fast path, same as previous sort
+ rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface );
+ continue;
+ }
+ oldSort = drawSurf->sort;
+ R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted );
+
+ //
+ // change the tess parameters if needed
+ // a "entityMergable" shader is a shader that can have surfaces from seperate
+ // entities merged into a single batch, like smoke and blood puff sprites
+ if (shader != oldShader || fogNum != oldFogNum || dlighted != oldDlighted
+ || ( entityNum != oldEntityNum && !shader->entityMergable ) ) {
+ if (oldShader != NULL) {
+ RB_EndSurface();
+ }
+ RB_BeginSurface( shader, fogNum );
+ oldShader = shader;
+ oldFogNum = fogNum;
+ oldDlighted = dlighted;
+ }
+
+ //
+ // change the modelview matrix if needed
+ //
+ if ( entityNum != oldEntityNum ) {
+ depthRange = isCrosshair = qfalse;
+
+ if ( entityNum != ENTITYNUM_WORLD ) {
+ backEnd.currentEntity = &backEnd.refdef.entities[entityNum];
+ backEnd.refdef.floatTime = originalTime - backEnd.currentEntity->e.shaderTime;
+ // we have to reset the shaderTime as well otherwise image animations start
+ // from the wrong frame
+ tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset;
+
+ // set up the transformation matrix
+ R_RotateForEntity( backEnd.currentEntity, &backEnd.viewParms, &backEnd.or );
+
+ // set up the dynamic lighting if needed
+ if ( backEnd.currentEntity->needDlights ) {
+ R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.or );
+ }
+
+ if(backEnd.currentEntity->e.renderfx & RF_DEPTHHACK)
+ {
+ // hack the depth range to prevent view model from poking into walls
+ depthRange = qtrue;
+
+ if(backEnd.currentEntity->e.renderfx & RF_CROSSHAIR)
+ isCrosshair = qtrue;
+ }
+ } else {
+ backEnd.currentEntity = &tr.worldEntity;
+ backEnd.refdef.floatTime = originalTime;
+ backEnd.or = backEnd.viewParms.world;
+ // we have to reset the shaderTime as well otherwise image animations on
+ // the world (like water) continue with the wrong frame
+ tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset;
+ R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.or );
+ }
+
+ qglLoadMatrixf( backEnd.or.modelMatrix );
+
+ //
+ // change depthrange. Also change projection matrix so first person weapon does not look like coming
+ // out of the screen.
+ //
+ if (oldDepthRange != depthRange || wasCrosshair != isCrosshair)
+ {
+ if (depthRange)
+ {
+ if(backEnd.viewParms.stereoFrame != STEREO_CENTER)
+ {
+ if(isCrosshair)
+ {
+ if(oldDepthRange)
+ {
+ // was not a crosshair but now is, change back proj matrix
+ qglMatrixMode(GL_PROJECTION);
+ qglLoadMatrixf(backEnd.viewParms.projectionMatrix);
+ qglMatrixMode(GL_MODELVIEW);
+ }
+ }
+ else
+ {
+ viewParms_t temp = backEnd.viewParms;
+
+ R_SetupProjection(&temp, r_znear->value, qfalse);
+
+ qglMatrixMode(GL_PROJECTION);
+ qglLoadMatrixf(temp.projectionMatrix);
+ qglMatrixMode(GL_MODELVIEW);
+ }
+ }
+
+ if(!oldDepthRange)
+#ifdef PANDORA
+ qglDepthRangef (0, 0.3);
+#else
+ qglDepthRange (0, 0.3);
+#endif
+ }
+ else
+ {
+ if(!wasCrosshair && backEnd.viewParms.stereoFrame != STEREO_CENTER)
+ {
+ qglMatrixMode(GL_PROJECTION);
+ qglLoadMatrixf(backEnd.viewParms.projectionMatrix);
+ qglMatrixMode(GL_MODELVIEW);
+ }
+#ifdef PANDORA
+ qglDepthRangef (0, 1);
+#else
+ qglDepthRange (0, 1);
+#endif
+ }
+
+ oldDepthRange = depthRange;
+ wasCrosshair = isCrosshair;
+ }
+
+ oldEntityNum = entityNum;
+ }
+
+ // add the triangles for this surface
+ rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface );
+ }
+
+ backEnd.refdef.floatTime = originalTime;
+
+ // draw the contents of the last shader batch
+ if (oldShader != NULL) {
+ RB_EndSurface();
+ }
+
+ // go back to the world modelview matrix
+ qglLoadMatrixf( backEnd.viewParms.world.modelMatrix );
+ if ( depthRange ) {
+#ifdef PANDORA
+ qglDepthRangef (0, 1);
+#else
+ qglDepthRange (0, 1);
+#endif
+ }
+
+#if 0
+ RB_DrawSun();
+#endif
+ // darken down any stencil shadows
+ RB_ShadowFinish();
+
+ // add light flares on lights that aren't obscured
+ RB_RenderFlares();
+}
+
+
+/*
+============================================================================
+
+RENDER BACK END THREAD FUNCTIONS
+
+============================================================================
+*/
+
+/*
+================
+RB_SetGL2D
+
+================
+*/
+void RB_SetGL2D (void) {
+ backEnd.projection2D = qtrue;
+
+ // set 2D virtual screen size
+ qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
+ qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
+ qglMatrixMode(GL_PROJECTION);
+ qglLoadIdentity ();
+#ifdef PANDORA
+ qglOrthof (0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, 1);
+#else
+ qglOrtho (0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, 1);
+#endif
+ qglMatrixMode(GL_MODELVIEW);
+ qglLoadIdentity ();
+
+ GL_State( GLS_DEPTHTEST_DISABLE |
+ GLS_SRCBLEND_SRC_ALPHA |
+ GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA );
+
+ qglDisable( GL_CULL_FACE );
+ qglDisable( GL_CLIP_PLANE0 );
+
+ // set time for 2D shaders
+ backEnd.refdef.time = ri.Milliseconds();
+ backEnd.refdef.floatTime = backEnd.refdef.time * 0.001f;
+}
+
+
+/*
+=============
+RE_StretchRaw
+
+FIXME: not exactly backend
+Stretches a raw 32 bit power of 2 bitmap image over the given screen rectangle.
+Used for cinematics.
+=============
+*/
+void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty) {
+ int i, j;
+ int start, end;
+#ifdef PANDORA
+ vec2_t texcoords[4];
+ vec2_t verts[4];
+ glIndex_t indicies[6] = { 0, 1, 2, 0, 3, 2 };
+#endif
+ if ( !tr.registered ) {
+ return;
+ }
+ R_SyncRenderThread();
+
+ // we definately want to sync every frame for the cinematics
+ qglFinish();
+
+ start = end = 0;
+ if ( r_speeds->integer ) {
+ start = ri.Milliseconds();
+ }
+
+ // make sure rows and cols are powers of 2
+ for ( i = 0 ; ( 1 << i ) < cols ; i++ ) {
+ }
+ for ( j = 0 ; ( 1 << j ) < rows ; j++ ) {
+ }
+ if ( ( 1 << i ) != cols || ( 1 << j ) != rows) {
+ ri.Error (ERR_DROP, "Draw_StretchRaw: size not a power of 2: %i by %i", cols, rows);
+ }
+
+ GL_Bind( tr.scratchImage[client] );
+
+ // if the scratchImage isn't in the format we want, specify it as a new texture
+ if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) {
+ tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols;
+ tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = rows;
+#ifdef PANDORA
+ qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
+#else
+ qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
+#endif
+ qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
+ qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+ qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
+ qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
+ } else {
+ if (dirty) {
+ // otherwise, just subimage upload it so that drivers can tell we are going to be changing
+ // it and don't try and do a texture compression
+ qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data );
+ }
+ }
+
+ if ( r_speeds->integer ) {
+ end = ri.Milliseconds();
+ ri.Printf( PRINT_ALL, "qglTexSubImage2D %i, %i: %i msec\n", cols, rows, end - start );
+ }
+
+ RB_SetGL2D();
+
+#ifdef PANDORA
+ glColor4f( tr.identityLight, tr.identityLight, tr.identityLight, 1.0f );
+
+ verts[0][0] = x; verts[0][1] = y;
+ verts[1][0] = x+w; verts[1][1] = y;
+ verts[2][0] = x+w; verts[2][1] = y+h;
+ verts[3][0] = x; verts[3][1] = y+h;
+
+ texcoords[0][0] = 0.5f/cols; texcoords[0][1] = 0.5f/rows;
+ texcoords[1][0] = (cols-0.5f)/cols; texcoords[1][1] = 0.5f/rows;
+ texcoords[2][0] = (cols-0.5f)/cols; texcoords[2][1] = (rows-0.5f)/rows;
+ texcoords[3][0] = 0.5f/cols; texcoords[3][1] = (rows-0.5f)/rows;
+
+ qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
+ qglTexCoordPointer( 2, GL_FLOAT, 0, texcoords );
+ qglVertexPointer ( 2, GL_FLOAT, 0, verts );
+ qglDrawElements( GL_TRIANGLE_STRIP, 6, GL_INDEX_TYPE, indicies );
+ qglDisableClientState( GL_TEXTURE_COORD_ARRAY );
+#else
+ qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight );
+
+ qglBegin (GL_QUADS);
+ qglTexCoord2f ( 0.5f / cols, 0.5f / rows );
+ qglVertex2f (x, y);
+ qglTexCoord2f ( ( cols - 0.5f ) / cols , 0.5f / rows );
+ qglVertex2f (x+w, y);
+ qglTexCoord2f ( ( cols - 0.5f ) / cols, ( rows - 0.5f ) / rows );
+ qglVertex2f (x+w, y+h);
+ qglTexCoord2f ( 0.5f / cols, ( rows - 0.5f ) / rows );
+ qglVertex2f (x, y+h);
+ qglEnd ();
+#endif
+}
+
+void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty) {
+
+ GL_Bind( tr.scratchImage[client] );
+
+ // if the scratchImage isn't in the format we want, specify it as a new texture
+ if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) {
+ tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols;
+ tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = rows;
+#ifdef PANDORA
+ qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
+#else
+ qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
+#endif
+ qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
+ qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+ qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
+ qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
+ } else {
+ if (dirty) {
+ // otherwise, just subimage upload it so that drivers can tell we are going to be changing
+ // it and don't try and do a texture compression
+ qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data );
+ }
+ }
+}
+
+
+/*
+=============
+RB_SetColor
+
+=============
+*/
+const void *RB_SetColor( const void *data ) {
+ const setColorCommand_t *cmd;
+
+ cmd = (const setColorCommand_t *)data;
+
+ backEnd.color2D[0] = cmd->color[0] * 255;
+ backEnd.color2D[1] = cmd->color[1] * 255;
+ backEnd.color2D[2] = cmd->color[2] * 255;
+ backEnd.color2D[3] = cmd->color[3] * 255;
+
+ return (const void *)(cmd + 1);
+}
+
+/*
+=============
+RB_StretchPic
+=============
+*/
+const void *RB_StretchPic ( const void *data ) {
+ const stretchPicCommand_t *cmd;
+ shader_t *shader;
+ int numVerts, numIndexes;
+
+ cmd = (const stretchPicCommand_t *)data;
+
+ if ( !backEnd.projection2D ) {
+ RB_SetGL2D();
+ }
+
+ shader = cmd->shader;
+ if ( shader != tess.shader ) {
+ if ( tess.numIndexes ) {
+ RB_EndSurface();
+ }
+ backEnd.currentEntity = &backEnd.entity2D;
+ RB_BeginSurface( shader, 0 );
+ }
+
+ RB_CHECKOVERFLOW( 4, 6 );
+ numVerts = tess.numVertexes;
+ numIndexes = tess.numIndexes;
+
+ tess.numVertexes += 4;
+ tess.numIndexes += 6;
+
+ tess.indexes[ numIndexes ] = numVerts + 3;
+ tess.indexes[ numIndexes + 1 ] = numVerts + 0;
+ tess.indexes[ numIndexes + 2 ] = numVerts + 2;
+ tess.indexes[ numIndexes + 3 ] = numVerts + 2;
+ tess.indexes[ numIndexes + 4 ] = numVerts + 0;
+ tess.indexes[ numIndexes + 5 ] = numVerts + 1;
+
+ *(int *)tess.vertexColors[ numVerts ] =
+ *(int *)tess.vertexColors[ numVerts + 1 ] =
+ *(int *)tess.vertexColors[ numVerts + 2 ] =
+ *(int *)tess.vertexColors[ numVerts + 3 ] = *(int *)backEnd.color2D;
+
+ tess.xyz[ numVerts ][0] = cmd->x;
+ tess.xyz[ numVerts ][1] = cmd->y;
+ tess.xyz[ numVerts ][2] = 0;
+
+ tess.texCoords[ numVerts ][0][0] = cmd->s1;
+ tess.texCoords[ numVerts ][0][1] = cmd->t1;
+
+ tess.xyz[ numVerts + 1 ][0] = cmd->x + cmd->w;
+ tess.xyz[ numVerts + 1 ][1] = cmd->y;
+ tess.xyz[ numVerts + 1 ][2] = 0;
+
+ tess.texCoords[ numVerts + 1 ][0][0] = cmd->s2;
+ tess.texCoords[ numVerts + 1 ][0][1] = cmd->t1;
+
+ tess.xyz[ numVerts + 2 ][0] = cmd->x + cmd->w;
+ tess.xyz[ numVerts + 2 ][1] = cmd->y + cmd->h;
+ tess.xyz[ numVerts + 2 ][2] = 0;
+
+ tess.texCoords[ numVerts + 2 ][0][0] = cmd->s2;
+ tess.texCoords[ numVerts + 2 ][0][1] = cmd->t2;
+
+ tess.xyz[ numVerts + 3 ][0] = cmd->x;
+ tess.xyz[ numVerts + 3 ][1] = cmd->y + cmd->h;
+ tess.xyz[ numVerts + 3 ][2] = 0;
+
+ tess.texCoords[ numVerts + 3 ][0][0] = cmd->s1;
+ tess.texCoords[ numVerts + 3 ][0][1] = cmd->t2;
+
+ return (const void *)(cmd + 1);
+}
+
+
+/*
+=============
+RB_DrawSurfs
+
+=============
+*/
+const void *RB_DrawSurfs( const void *data ) {
+ const drawSurfsCommand_t *cmd;
+
+ // finish any 2D drawing if needed
+ if ( tess.numIndexes ) {
+ RB_EndSurface();
+ }
+
+ cmd = (const drawSurfsCommand_t *)data;
+
+ backEnd.refdef = cmd->refdef;
+ backEnd.viewParms = cmd->viewParms;
+
+ RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs );
+
+ return (const void *)(cmd + 1);
+}
+
+
+/*
+=============
+RB_DrawBuffer
+
+=============
+*/
+const void *RB_DrawBuffer( const void *data ) {
+ const drawBufferCommand_t *cmd;
+
+ cmd = (const drawBufferCommand_t *)data;
+
+ qglDrawBuffer( cmd->buffer );
+
+ // clear screen for debugging
+ if ( r_clear->integer ) {
+ qglClearColor( 1, 0, 0.5, 1 );
+ qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
+ }
+
+ return (const void *)(cmd + 1);
+}
+
+/*
+===============
+RB_ShowImages
+
+Draw all the images to the screen, on top of whatever
+was there. This is used to test for texture thrashing.
+
+Also called by RE_EndRegistration
+===============
+*/
+void RB_ShowImages( void ) {
+ int i;
+ image_t *image;
+ float x, y, w, h;
+ int start, end;
+#ifdef PANDORA
+ vec2_t texcoords[4] = { {0.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 1.0f} };
+ vec2_t verts[4];
+ glIndex_t indicies[6] = { 0, 1, 2, 0, 3, 2 };
+#endif
+
+ if ( !backEnd.projection2D ) {
+ RB_SetGL2D();
+ }
+
+ qglClear( GL_COLOR_BUFFER_BIT );
+
+ qglFinish();
+
+ start = ri.Milliseconds();
+
+#ifdef PANDORA
+ qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
+#endif
+ for ( i=0 ; i<tr.numImages ; i++ ) {
+ image = tr.images[i];
+
+ w = glConfig.vidWidth / 20;
+ h = glConfig.vidHeight / 15;
+ x = i % 20 * w;
+ y = i / 20 * h;
+
+ // show in proportional size in mode 2
+ if ( r_showImages->integer == 2 ) {
+ w *= image->uploadWidth / 512.0f;
+ h *= image->uploadHeight / 512.0f;
+ }
+#ifdef PANDORA
+ verts[0][0] = x; verts[0][1] = y;
+ verts[1][0] = x+w; verts[1][1] = y;
+ verts[2][0] = x+w; verts[2][1] = y+h;
+ verts[3][0] = x; verts[3][1] = y+h;
+
+ qglTexCoordPointer( 2, GL_FLOAT, 0, texcoords );
+ qglVertexPointer ( 2, GL_FLOAT, 0, verts );
+ qglDrawElements( GL_TRIANGLE_STRIP, 6, GL_INDEX_TYPE, indicies );
+#else
+ GL_Bind( image );
+ qglBegin (GL_QUADS);
+ qglTexCoord2f( 0, 0 );
+ qglVertex2f( x, y );
+ qglTexCoord2f( 1, 0 );
+ qglVertex2f( x + w, y );
+ qglTexCoord2f( 1, 1 );
+ qglVertex2f( x + w, y + h );
+ qglTexCoord2f( 0, 1 );
+ qglVertex2f( x, y + h );
+ qglEnd();
+#endif
+ }
+#ifdef PANDORA
+ qglDisableClientState( GL_TEXTURE_COORD_ARRAY );
+#endif
+ qglFinish();
+
+ end = ri.Milliseconds();
+ ri.Printf( PRINT_ALL, "%i msec to draw all images\n", end - start );
+
+}
+
+/*
+=============
+RB_ColorMask
+
+=============
+*/
+const void *RB_ColorMask(const void *data)
+{
+ const colorMaskCommand_t *cmd = data;
+
+ qglColorMask(cmd->rgba[0], cmd->rgba[1], cmd->rgba[2], cmd->rgba[3]);
+
+ return (const void *)(cmd + 1);
+}
+
+/*
+=============
+RB_ClearDepth
+
+=============
+*/
+const void *RB_ClearDepth(const void *data)
+{
+ const clearDepthCommand_t *cmd = data;
+
+ if(tess.numIndexes)
+ RB_EndSurface();
+
+ // texture swapping test
+ if (r_showImages->integer)
+ RB_ShowImages();
+
+ qglClear(GL_DEPTH_BUFFER_BIT);
+
+ return (const void *)(cmd + 1);
+}
+
+/*
+=============
+RB_SwapBuffers
+
+=============
+*/
+const void *RB_SwapBuffers( const void *data ) {
+ const swapBuffersCommand_t *cmd;
+
+ // finish any 2D drawing if needed
+ if ( tess.numIndexes ) {
+ RB_EndSurface();
+ }
+
+ // texture swapping test
+ if ( r_showImages->integer ) {
+ RB_ShowImages();
+ }
+
+ cmd = (const swapBuffersCommand_t *)data;
+
+#ifndef PANDORA
+ // we measure overdraw by reading back the stencil buffer and
+ // counting up the number of increments that have happened
+ if ( r_measureOverdraw->integer ) {
+ int i;
+ long sum = 0;
+ unsigned char *stencilReadback;
+
+ stencilReadback = ri.Hunk_AllocateTempMemory( glConfig.vidWidth * glConfig.vidHeight );
+ qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback );
+
+ for ( i = 0; i < glConfig.vidWidth * glConfig.vidHeight; i++ ) {
+ sum += stencilReadback[i];
+ }
+
+ backEnd.pc.c_overDraw += sum;
+ ri.Hunk_FreeTempMemory( stencilReadback );
+ }
+#endif
+
+ if ( !glState.finishCalled ) {
+ qglFinish();
+ }
+
+ GLimp_LogComment( "***************** RB_SwapBuffers *****************\n\n\n" );
+
+ GLimp_EndFrame();
+
+ backEnd.projection2D = qfalse;
+
+ return (const void *)(cmd + 1);
+}
+
+/*
+====================
+RB_ExecuteRenderCommands
+
+This function will be called synchronously if running without
+smp extensions, or asynchronously by another thread.
+====================
+*/
+void RB_ExecuteRenderCommands( const void *data ) {
+ int t1, t2;
+
+ t1 = ri.Milliseconds ();
+
+ if ( !r_smp->integer || data == backEndData[0]->commands.cmds ) {
+ backEnd.smpFrame = 0;
+ } else {
+ backEnd.smpFrame = 1;
+ }
+
+ while ( 1 ) {
+ switch ( *(const int *)data ) {
+ case RC_SET_COLOR:
+ data = RB_SetColor( data );
+ break;
+ case RC_STRETCH_PIC:
+ data = RB_StretchPic( data );
+ break;
+ case RC_DRAW_SURFS:
+ data = RB_DrawSurfs( data );
+ break;
+ case RC_DRAW_BUFFER:
+ data = RB_DrawBuffer( data );
+ break;
+ case RC_SWAP_BUFFERS:
+ data = RB_SwapBuffers( data );
+ break;
+ case RC_SCREENSHOT:
+ data = RB_TakeScreenshotCmd( data );
+ break;
+ case RC_VIDEOFRAME:
+ data = RB_TakeVideoFrameCmd( data );
+ break;
+ case RC_COLORMASK:
+ data = RB_ColorMask(data);
+ break;
+ case RC_CLEARDEPTH:
+ data = RB_ClearDepth(data);
+ break;
+ case RC_END_OF_LIST:
+ default:
+ // stop rendering on this thread
+ t2 = ri.Milliseconds ();
+ backEnd.pc.msec = t2 - t1;
+ return;
+ }
+ }
+
+}
+
+
+/*
+================
+RB_RenderThread
+================
+*/
+void RB_RenderThread( void ) {
+ const void *data;
+
+ // wait for either a rendering command or a quit command
+ while ( 1 ) {
+ // sleep until we have work to do
+ data = GLimp_RendererSleep();
+
+ if ( !data ) {
+ return; // all done, renderer is shutting down
+ }
+
+ renderThreadActive = qtrue;
+
+ RB_ExecuteRenderCommands( data );
+
+ renderThreadActive = qfalse;
+ }
+}
+
diff --git a/code/renderer/tr_bsp.c b/code/renderer/tr_bsp.c
new file mode 100644
index 0000000..8e0031b
--- /dev/null
+++ b/code/renderer/tr_bsp.c
@@ -0,0 +1,1871 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// tr_map.c
+
+#include "tr_local.h"
+
+/*
+
+Loads and prepares a map file for scene rendering.
+
+A single entry point:
+
+void RE_LoadWorldMap( const char *name );
+
+*/
+
+static world_t s_worldData;
+static byte *fileBase;
+
+int c_subdivisions;
+int c_gridVerts;
+
+//===============================================================================
+
+static void HSVtoRGB( float h, float s, float v, float rgb[3] )
+{
+ int i;
+ float f;
+ float p, q, t;
+
+ h *= 5;
+
+ i = floor( h );
+ f = h - i;
+
+ p = v * ( 1 - s );
+ q = v * ( 1 - s * f );
+ t = v * ( 1 - s * ( 1 - f ) );
+
+ switch ( i )
+ {
+ case 0:
+ rgb[0] = v;
+ rgb[1] = t;
+ rgb[2] = p;
+ break;
+ case 1:
+ rgb[0] = q;
+ rgb[1] = v;
+ rgb[2] = p;
+ break;
+ case 2:
+ rgb[0] = p;
+ rgb[1] = v;
+ rgb[2] = t;
+ break;
+ case 3:
+ rgb[0] = p;
+ rgb[1] = q;
+ rgb[2] = v;
+ break;
+ case 4:
+ rgb[0] = t;
+ rgb[1] = p;
+ rgb[2] = v;
+ break;
+ case 5:
+ rgb[0] = v;
+ rgb[1] = p;
+ rgb[2] = q;
+ break;
+ }
+}
+
+/*
+===============
+R_ColorShiftLightingBytes
+
+===============
+*/
+static void R_ColorShiftLightingBytes( byte in[4], byte out[4] ) {
+ int shift, r, g, b;
+
+ // shift the color data based on overbright range
+ shift = r_mapOverBrightBits->integer - tr.overbrightBits;
+
+ // shift the data based on overbright range
+ r = in[0] << shift;
+ g = in[1] << shift;
+ b = in[2] << shift;
+
+ // normalize by color instead of saturating to white
+ if ( ( r | g | b ) > 255 ) {
+ int max;
+
+ max = r > g ? r : g;
+ max = max > b ? max : b;
+ r = r * 255 / max;
+ g = g * 255 / max;
+ b = b * 255 / max;
+ }
+
+ out[0] = r;
+ out[1] = g;
+ out[2] = b;
+ out[3] = in[3];
+}
+
+/*
+===============
+R_LoadLightmaps
+
+===============
+*/
+#define LIGHTMAP_SIZE 128
+static void R_LoadLightmaps( lump_t *l ) {
+ byte *buf, *buf_p;
+ int len;
+ byte image[LIGHTMAP_SIZE*LIGHTMAP_SIZE*4];
+ int i, j;
+ float maxIntensity = 0;
+ double sumIntensity = 0;
+
+ len = l->filelen;
+ if ( !len ) {
+ return;
+ }
+ buf = fileBase + l->fileofs;
+
+ // we are about to upload textures
+ R_SyncRenderThread();
+
+ // create all the lightmaps
+ tr.numLightmaps = len / (LIGHTMAP_SIZE * LIGHTMAP_SIZE * 3);
+ if ( tr.numLightmaps == 1 ) {
+ //FIXME: HACK: maps with only one lightmap turn up fullbright for some reason.
+ //this avoids this, but isn't the correct solution.
+ tr.numLightmaps++;
+ } else if ( tr.numLightmaps >= MAX_LIGHTMAPS ) { // 20051020 misantropia
+ ri.Printf( PRINT_WARNING, "WARNING: number of lightmaps > MAX_LIGHTMAPS\n" );
+ tr.numLightmaps = MAX_LIGHTMAPS;
+ }
+
+ // if we are in r_vertexLight mode, we don't need the lightmaps at all
+ if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) {
+ return;
+ }
+
+ for ( i = 0 ; i < tr.numLightmaps ; i++ ) {
+ // expand the 24 bit on-disk to 32 bit
+ buf_p = buf + i * LIGHTMAP_SIZE*LIGHTMAP_SIZE * 3;
+
+ if ( r_lightmap->integer == 2 )
+ { // color code by intensity as development tool (FIXME: check range)
+ for ( j = 0; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; j++ )
+ {
+ float r = buf_p[j*3+0];
+ float g = buf_p[j*3+1];
+ float b = buf_p[j*3+2];
+ float intensity;
+ float out[3] = {0.0, 0.0, 0.0};
+
+ intensity = 0.33f * r + 0.685f * g + 0.063f * b;
+
+ if ( intensity > 255 )
+ intensity = 1.0f;
+ else
+ intensity /= 255.0f;
+
+ if ( intensity > maxIntensity )
+ maxIntensity = intensity;
+
+ HSVtoRGB( intensity, 1.00, 0.50, out );
+
+ image[j*4+0] = out[0] * 255;
+ image[j*4+1] = out[1] * 255;
+ image[j*4+2] = out[2] * 255;
+ image[j*4+3] = 255;
+
+ sumIntensity += intensity;
+ }
+ } else {
+ for ( j = 0 ; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; j++ ) {
+ R_ColorShiftLightingBytes( &buf_p[j*3], &image[j*4] );
+ image[j*4+3] = 255;
+ }
+ }
+ tr.lightmaps[i] = R_CreateImage( va("*lightmap%d",i), image,
+ LIGHTMAP_SIZE, LIGHTMAP_SIZE, qfalse, qfalse, GL_CLAMP_TO_EDGE );
+ }
+
+ if ( r_lightmap->integer == 2 ) {
+ ri.Printf( PRINT_ALL, "Brightest lightmap value: %d\n", ( int ) ( maxIntensity * 255 ) );
+ }
+}
+
+
+/*
+=================
+RE_SetWorldVisData
+
+This is called by the clipmodel subsystem so we can share the 1.8 megs of
+space in big maps...
+=================
+*/
+void RE_SetWorldVisData( const byte *vis ) {
+ tr.externalVisData = vis;
+}
+
+
+/*
+=================
+R_LoadVisibility
+=================
+*/
+static void R_LoadVisibility( lump_t *l ) {
+ int len;
+ byte *buf;
+
+ len = ( s_worldData.numClusters + 63 ) & ~63;
+ s_worldData.novis = ri.Hunk_Alloc( len, h_low );
+ Com_Memset( s_worldData.novis, 0xff, len );
+
+ len = l->filelen;
+ if ( !len ) {
+ return;
+ }
+ buf = fileBase + l->fileofs;
+
+ s_worldData.numClusters = LittleLong( ((int *)buf)[0] );
+ s_worldData.clusterBytes = LittleLong( ((int *)buf)[1] );
+
+ // CM_Load should have given us the vis data to share, so
+ // we don't need to allocate another copy
+ if ( tr.externalVisData ) {
+ s_worldData.vis = tr.externalVisData;
+ } else {
+ byte *dest;
+
+ dest = ri.Hunk_Alloc( len - 8, h_low );
+ Com_Memcpy( dest, buf + 8, len - 8 );
+ s_worldData.vis = dest;
+ }
+}
+
+//===============================================================================
+
+
+/*
+===============
+ShaderForShaderNum
+===============
+*/
+static shader_t *ShaderForShaderNum( int shaderNum, int lightmapNum ) {
+ shader_t *shader;
+ dshader_t *dsh;
+
+ shaderNum = LittleLong( shaderNum );
+ if ( shaderNum < 0 || shaderNum >= s_worldData.numShaders ) {
+ ri.Error( ERR_DROP, "ShaderForShaderNum: bad num %i", shaderNum );
+ }
+ dsh = &s_worldData.shaders[ shaderNum ];
+
+ if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) {
+ lightmapNum = LIGHTMAP_BY_VERTEX;
+ }
+
+ if ( r_fullbright->integer ) {
+ lightmapNum = LIGHTMAP_WHITEIMAGE;
+ }
+
+ shader = R_FindShader( dsh->shader, lightmapNum, qtrue );
+
+ // if the shader had errors, just use default shader
+ if ( shader->defaultShader ) {
+ return tr.defaultShader;
+ }
+
+ return shader;
+}
+
+/*
+===============
+ParseFace
+===============
+*/
+static void ParseFace( dsurface_t *ds, drawVert_t *verts, msurface_t *surf, int *indexes ) {
+ int i, j;
+ srfSurfaceFace_t *cv;
+ int numPoints, numIndexes;
+ int lightmapNum;
+ int sfaceSize, ofsIndexes;
+
+ lightmapNum = LittleLong( ds->lightmapNum );
+
+ // get fog volume
+ surf->fogIndex = LittleLong( ds->fogNum ) + 1;
+
+ // get shader value
+ surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum );
+ if ( r_singleShader->integer && !surf->shader->isSky ) {
+ surf->shader = tr.defaultShader;
+ }
+
+ numPoints = LittleLong( ds->numVerts );
+ if (numPoints > MAX_FACE_POINTS) {
+ ri.Printf( PRINT_WARNING, "WARNING: MAX_FACE_POINTS exceeded: %i\n", numPoints);
+ numPoints = MAX_FACE_POINTS;
+ surf->shader = tr.defaultShader;
+ }
+
+ numIndexes = LittleLong( ds->numIndexes );
+
+ // create the srfSurfaceFace_t
+ sfaceSize = ( size_t ) &((srfSurfaceFace_t *)0)->points[numPoints];
+ ofsIndexes = sfaceSize;
+ sfaceSize += sizeof( int ) * numIndexes;
+
+ cv = ri.Hunk_Alloc( sfaceSize, h_low );
+ cv->surfaceType = SF_FACE;
+ cv->numPoints = numPoints;
+ cv->numIndices = numIndexes;
+ cv->ofsIndices = ofsIndexes;
+
+ verts += LittleLong( ds->firstVert );
+ for ( i = 0 ; i < numPoints ; i++ ) {
+ for ( j = 0 ; j < 3 ; j++ ) {
+ cv->points[i][j] = LittleFloat( verts[i].xyz[j] );
+ }
+ for ( j = 0 ; j < 2 ; j++ ) {
+ cv->points[i][3+j] = LittleFloat( verts[i].st[j] );
+ cv->points[i][5+j] = LittleFloat( verts[i].lightmap[j] );
+ }
+ R_ColorShiftLightingBytes( verts[i].color, (byte *)&cv->points[i][7] );
+ }
+
+ indexes += LittleLong( ds->firstIndex );
+ for ( i = 0 ; i < numIndexes ; i++ ) {
+ ((int *)((byte *)cv + cv->ofsIndices ))[i] = LittleLong( indexes[ i ] );
+ }
+
+ // take the plane information from the lightmap vector
+ for ( i = 0 ; i < 3 ; i++ ) {
+ cv->plane.normal[i] = LittleFloat( ds->lightmapVecs[2][i] );
+ }
+ cv->plane.dist = DotProduct( cv->points[0], cv->plane.normal );
+ SetPlaneSignbits( &cv->plane );
+ cv->plane.type = PlaneTypeForNormal( cv->plane.normal );
+
+ surf->data = (surfaceType_t *)cv;
+}
+
+
+/*
+===============
+ParseMesh
+===============
+*/
+static void ParseMesh ( dsurface_t *ds, drawVert_t *verts, msurface_t *surf ) {
+ srfGridMesh_t *grid;
+ int i, j;
+ int width, height, numPoints;
+ drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE];
+ int lightmapNum;
+ vec3_t bounds[2];
+ vec3_t tmpVec;
+ static surfaceType_t skipData = SF_SKIP;
+
+ lightmapNum = LittleLong( ds->lightmapNum );
+
+ // get fog volume
+ surf->fogIndex = LittleLong( ds->fogNum ) + 1;
+
+ // get shader value
+ surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum );
+ if ( r_singleShader->integer && !surf->shader->isSky ) {
+ surf->shader = tr.defaultShader;
+ }
+
+ // we may have a nodraw surface, because they might still need to
+ // be around for movement clipping
+ if ( s_worldData.shaders[ LittleLong( ds->shaderNum ) ].surfaceFlags & SURF_NODRAW ) {
+ surf->data = &skipData;
+ return;
+ }
+
+ width = LittleLong( ds->patchWidth );
+ height = LittleLong( ds->patchHeight );
+
+ verts += LittleLong( ds->firstVert );
+ numPoints = width * height;
+ for ( i = 0 ; i < numPoints ; i++ ) {
+ for ( j = 0 ; j < 3 ; j++ ) {
+ points[i].xyz[j] = LittleFloat( verts[i].xyz[j] );
+ points[i].normal[j] = LittleFloat( verts[i].normal[j] );
+ }
+ for ( j = 0 ; j < 2 ; j++ ) {
+ points[i].st[j] = LittleFloat( verts[i].st[j] );
+ points[i].lightmap[j] = LittleFloat( verts[i].lightmap[j] );
+ }
+ R_ColorShiftLightingBytes( verts[i].color, points[i].color );
+ }
+
+ // pre-tesseleate
+ grid = R_SubdividePatchToGrid( width, height, points );
+ surf->data = (surfaceType_t *)grid;
+
+ // copy the level of detail origin, which is the center
+ // of the group of all curves that must subdivide the same
+ // to avoid cracking
+ for ( i = 0 ; i < 3 ; i++ ) {
+ bounds[0][i] = LittleFloat( ds->lightmapVecs[0][i] );
+ bounds[1][i] = LittleFloat( ds->lightmapVecs[1][i] );
+ }
+ VectorAdd( bounds[0], bounds[1], bounds[1] );
+ VectorScale( bounds[1], 0.5f, grid->lodOrigin );
+ VectorSubtract( bounds[0], grid->lodOrigin, tmpVec );
+ grid->lodRadius = VectorLength( tmpVec );
+}
+
+/*
+===============
+ParseTriSurf
+===============
+*/
+static void ParseTriSurf( dsurface_t *ds, drawVert_t *verts, msurface_t *surf, int *indexes ) {
+ srfTriangles_t *tri;
+ int i, j;
+ int numVerts, numIndexes;
+
+ // get fog volume
+ surf->fogIndex = LittleLong( ds->fogNum ) + 1;
+
+ // get shader
+ surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX );
+ if ( r_singleShader->integer && !surf->shader->isSky ) {
+ surf->shader = tr.defaultShader;
+ }
+
+ numVerts = LittleLong( ds->numVerts );
+ numIndexes = LittleLong( ds->numIndexes );
+
+ tri = ri.Hunk_Alloc( sizeof( *tri ) + numVerts * sizeof( tri->verts[0] )
+ + numIndexes * sizeof( tri->indexes[0] ), h_low );
+ tri->surfaceType = SF_TRIANGLES;
+ tri->numVerts = numVerts;
+ tri->numIndexes = numIndexes;
+ tri->verts = (drawVert_t *)(tri + 1);
+ tri->indexes = (int *)(tri->verts + tri->numVerts );
+
+ surf->data = (surfaceType_t *)tri;
+
+ // copy vertexes
+ ClearBounds( tri->bounds[0], tri->bounds[1] );
+ verts += LittleLong( ds->firstVert );
+ for ( i = 0 ; i < numVerts ; i++ ) {
+ for ( j = 0 ; j < 3 ; j++ ) {
+ tri->verts[i].xyz[j] = LittleFloat( verts[i].xyz[j] );
+ tri->verts[i].normal[j] = LittleFloat( verts[i].normal[j] );
+ }
+ AddPointToBounds( tri->verts[i].xyz, tri->bounds[0], tri->bounds[1] );
+ for ( j = 0 ; j < 2 ; j++ ) {
+ tri->verts[i].st[j] = LittleFloat( verts[i].st[j] );
+ tri->verts[i].lightmap[j] = LittleFloat( verts[i].lightmap[j] );
+ }
+
+ R_ColorShiftLightingBytes( verts[i].color, tri->verts[i].color );
+ }
+
+ // copy indexes
+ indexes += LittleLong( ds->firstIndex );
+ for ( i = 0 ; i < numIndexes ; i++ ) {
+ tri->indexes[i] = LittleLong( indexes[i] );
+ if ( tri->indexes[i] < 0 || tri->indexes[i] >= numVerts ) {
+ ri.Error( ERR_DROP, "Bad index in triangle surface" );
+ }
+ }
+}
+
+/*
+===============
+ParseFlare
+===============
+*/
+static void ParseFlare( dsurface_t *ds, drawVert_t *verts, msurface_t *surf, int *indexes ) {
+ srfFlare_t *flare;
+ int i;
+
+ // get fog volume
+ surf->fogIndex = LittleLong( ds->fogNum ) + 1;
+
+ // get shader
+ surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX );
+ if ( r_singleShader->integer && !surf->shader->isSky ) {
+ surf->shader = tr.defaultShader;
+ }
+
+ flare = ri.Hunk_Alloc( sizeof( *flare ), h_low );
+ flare->surfaceType = SF_FLARE;
+
+ surf->data = (surfaceType_t *)flare;
+
+ for ( i = 0 ; i < 3 ; i++ ) {
+ flare->origin[i] = LittleFloat( ds->lightmapOrigin[i] );
+ flare->color[i] = LittleFloat( ds->lightmapVecs[0][i] );
+ flare->normal[i] = LittleFloat( ds->lightmapVecs[2][i] );
+ }
+}
+
+
+/*
+=================
+R_MergedWidthPoints
+
+returns true if there are grid points merged on a width edge
+=================
+*/
+int R_MergedWidthPoints(srfGridMesh_t *grid, int offset) {
+ int i, j;
+
+ for (i = 1; i < grid->width-1; i++) {
+ for (j = i + 1; j < grid->width-1; j++) {
+ if ( fabs(grid->verts[i + offset].xyz[0] - grid->verts[j + offset].xyz[0]) > .1) continue;
+ if ( fabs(grid->verts[i + offset].xyz[1] - grid->verts[j + offset].xyz[1]) > .1) continue;
+ if ( fabs(grid->verts[i + offset].xyz[2] - grid->verts[j + offset].xyz[2]) > .1) continue;
+ return qtrue;
+ }
+ }
+ return qfalse;
+}
+
+/*
+=================
+R_MergedHeightPoints
+
+returns true if there are grid points merged on a height edge
+=================
+*/
+int R_MergedHeightPoints(srfGridMesh_t *grid, int offset) {
+ int i, j;
+
+ for (i = 1; i < grid->height-1; i++) {
+ for (j = i + 1; j < grid->height-1; j++) {
+ if ( fabs(grid->verts[grid->width * i + offset].xyz[0] - grid->verts[grid->width * j + offset].xyz[0]) > .1) continue;
+ if ( fabs(grid->verts[grid->width * i + offset].xyz[1] - grid->verts[grid->width * j + offset].xyz[1]) > .1) continue;
+ if ( fabs(grid->verts[grid->width * i + offset].xyz[2] - grid->verts[grid->width * j + offset].xyz[2]) > .1) continue;
+ return qtrue;
+ }
+ }
+ return qfalse;
+}
+
+/*
+=================
+R_FixSharedVertexLodError_r
+
+NOTE: never sync LoD through grid edges with merged points!
+
+FIXME: write generalized version that also avoids cracks between a patch and one that meets half way?
+=================
+*/
+void R_FixSharedVertexLodError_r( int start, srfGridMesh_t *grid1 ) {
+ int j, k, l, m, n, offset1, offset2, touch;
+ srfGridMesh_t *grid2;
+
+ for ( j = start; j < s_worldData.numsurfaces; j++ ) {
+ //
+ grid2 = (srfGridMesh_t *) s_worldData.surfaces[j].data;
+ // if this surface is not a grid
+ if ( grid2->surfaceType != SF_GRID ) continue;
+ // if the LOD errors are already fixed for this patch
+ if ( grid2->lodFixed == 2 ) continue;
+ // grids in the same LOD group should have the exact same lod radius
+ if ( grid1->lodRadius != grid2->lodRadius ) continue;
+ // grids in the same LOD group should have the exact same lod origin
+ if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue;
+ if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue;
+ if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue;
+ //
+ touch = qfalse;
+ for (n = 0; n < 2; n++) {
+ //
+ if (n) offset1 = (grid1->height-1) * grid1->width;
+ else offset1 = 0;
+ if (R_MergedWidthPoints(grid1, offset1)) continue;
+ for (k = 1; k < grid1->width-1; k++) {
+ for (m = 0; m < 2; m++) {
+
+ if (m) offset2 = (grid2->height-1) * grid2->width;
+ else offset2 = 0;
+ if (R_MergedWidthPoints(grid2, offset2)) continue;
+ for ( l = 1; l < grid2->width-1; l++) {
+ //
+ if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue;
+ if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue;
+ if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue;
+ // ok the points are equal and should have the same lod error
+ grid2->widthLodError[l] = grid1->widthLodError[k];
+ touch = qtrue;
+ }
+ }
+ for (m = 0; m < 2; m++) {
+
+ if (m) offset2 = grid2->width-1;
+ else offset2 = 0;
+ if (R_MergedHeightPoints(grid2, offset2)) continue;
+ for ( l = 1; l < grid2->height-1; l++) {
+ //
+ if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue;
+ if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue;
+ if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue;
+ // ok the points are equal and should have the same lod error
+ grid2->heightLodError[l] = grid1->widthLodError[k];
+ touch = qtrue;
+ }
+ }
+ }
+ }
+ for (n = 0; n < 2; n++) {
+ //
+ if (n) offset1 = grid1->width-1;
+ else offset1 = 0;
+ if (R_MergedHeightPoints(grid1, offset1)) continue;
+ for (k = 1; k < grid1->height-1; k++) {
+ for (m = 0; m < 2; m++) {
+
+ if (m) offset2 = (grid2->height-1) * grid2->width;
+ else offset2 = 0;
+ if (R_MergedWidthPoints(grid2, offset2)) continue;
+ for ( l = 1; l < grid2->width-1; l++) {
+ //
+ if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue;
+ if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue;
+ if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue;
+ // ok the points are equal and should have the same lod error
+ grid2->widthLodError[l] = grid1->heightLodError[k];
+ touch = qtrue;
+ }
+ }
+ for (m = 0; m < 2; m++) {
+
+ if (m) offset2 = grid2->width-1;
+ else offset2 = 0;
+ if (R_MergedHeightPoints(grid2, offset2)) continue;
+ for ( l = 1; l < grid2->height-1; l++) {
+ //
+ if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue;
+ if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue;
+ if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue;
+ // ok the points are equal and should have the same lod error
+ grid2->heightLodError[l] = grid1->heightLodError[k];
+ touch = qtrue;
+ }
+ }
+ }
+ }
+ if (touch) {
+ grid2->lodFixed = 2;
+ R_FixSharedVertexLodError_r ( start, grid2 );
+ //NOTE: this would be correct but makes things really slow
+ //grid2->lodFixed = 1;
+ }
+ }
+}
+
+/*
+=================
+R_FixSharedVertexLodError
+
+This function assumes that all patches in one group are nicely stitched together for the highest LoD.
+If this is not the case this function will still do its job but won't fix the highest LoD cracks.
+=================
+*/
+void R_FixSharedVertexLodError( void ) {
+ int i;
+ srfGridMesh_t *grid1;
+
+ for ( i = 0; i < s_worldData.numsurfaces; i++ ) {
+ //
+ grid1 = (srfGridMesh_t *) s_worldData.surfaces[i].data;
+ // if this surface is not a grid
+ if ( grid1->surfaceType != SF_GRID )
+ continue;
+ //
+ if ( grid1->lodFixed )
+ continue;
+ //
+ grid1->lodFixed = 2;
+ // recursively fix other patches in the same LOD group
+ R_FixSharedVertexLodError_r( i + 1, grid1);
+ }
+}
+
+
+/*
+===============
+R_StitchPatches
+===============
+*/
+int R_StitchPatches( int grid1num, int grid2num ) {
+ float *v1, *v2;
+ srfGridMesh_t *grid1, *grid2;
+ int k, l, m, n, offset1, offset2, row, column;
+
+ grid1 = (srfGridMesh_t *) s_worldData.surfaces[grid1num].data;
+ grid2 = (srfGridMesh_t *) s_worldData.surfaces[grid2num].data;
+ for (n = 0; n < 2; n++) {
+ //
+ if (n) offset1 = (grid1->height-1) * grid1->width;
+ else offset1 = 0;
+ if (R_MergedWidthPoints(grid1, offset1))
+ continue;
+ for (k = 0; k < grid1->width-2; k += 2) {
+
+ for (m = 0; m < 2; m++) {
+
+ if ( grid2->width >= MAX_GRID_SIZE )
+ break;
+ if (m) offset2 = (grid2->height-1) * grid2->width;
+ else offset2 = 0;
+ for ( l = 0; l < grid2->width-1; l++) {
+ //
+ v1 = grid1->verts[k + offset1].xyz;
+ v2 = grid2->verts[l + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+
+ v1 = grid1->verts[k + 2 + offset1].xyz;
+ v2 = grid2->verts[l + 1 + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+ //
+ v1 = grid2->verts[l + offset2].xyz;
+ v2 = grid2->verts[l + 1 + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) < .01 &&
+ fabs(v1[1] - v2[1]) < .01 &&
+ fabs(v1[2] - v2[2]) < .01)
+ continue;
+ //
+ //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+ // insert column into grid2 right after after column l
+ if (m) row = grid2->height-1;
+ else row = 0;
+ grid2 = R_GridInsertColumn( grid2, l+1, row,
+ grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]);
+ grid2->lodStitched = qfalse;
+ s_worldData.surfaces[grid2num].data = (void *) grid2;
+ return qtrue;
+ }
+ }
+ for (m = 0; m < 2; m++) {
+
+ if (grid2->height >= MAX_GRID_SIZE)
+ break;
+ if (m) offset2 = grid2->width-1;
+ else offset2 = 0;
+ for ( l = 0; l < grid2->height-1; l++) {
+ //
+ v1 = grid1->verts[k + offset1].xyz;
+ v2 = grid2->verts[grid2->width * l + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+
+ v1 = grid1->verts[k + 2 + offset1].xyz;
+ v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+ //
+ v1 = grid2->verts[grid2->width * l + offset2].xyz;
+ v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) < .01 &&
+ fabs(v1[1] - v2[1]) < .01 &&
+ fabs(v1[2] - v2[2]) < .01)
+ continue;
+ //
+ //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+ // insert row into grid2 right after after row l
+ if (m) column = grid2->width-1;
+ else column = 0;
+ grid2 = R_GridInsertRow( grid2, l+1, column,
+ grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]);
+ grid2->lodStitched = qfalse;
+ s_worldData.surfaces[grid2num].data = (void *) grid2;
+ return qtrue;
+ }
+ }
+ }
+ }
+ for (n = 0; n < 2; n++) {
+ //
+ if (n) offset1 = grid1->width-1;
+ else offset1 = 0;
+ if (R_MergedHeightPoints(grid1, offset1))
+ continue;
+ for (k = 0; k < grid1->height-2; k += 2) {
+ for (m = 0; m < 2; m++) {
+
+ if ( grid2->width >= MAX_GRID_SIZE )
+ break;
+ if (m) offset2 = (grid2->height-1) * grid2->width;
+ else offset2 = 0;
+ for ( l = 0; l < grid2->width-1; l++) {
+ //
+ v1 = grid1->verts[grid1->width * k + offset1].xyz;
+ v2 = grid2->verts[l + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+
+ v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz;
+ v2 = grid2->verts[l + 1 + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+ //
+ v1 = grid2->verts[l + offset2].xyz;
+ v2 = grid2->verts[(l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) < .01 &&
+ fabs(v1[1] - v2[1]) < .01 &&
+ fabs(v1[2] - v2[2]) < .01)
+ continue;
+ //
+ //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+ // insert column into grid2 right after after column l
+ if (m) row = grid2->height-1;
+ else row = 0;
+ grid2 = R_GridInsertColumn( grid2, l+1, row,
+ grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]);
+ grid2->lodStitched = qfalse;
+ s_worldData.surfaces[grid2num].data = (void *) grid2;
+ return qtrue;
+ }
+ }
+ for (m = 0; m < 2; m++) {
+
+ if (grid2->height >= MAX_GRID_SIZE)
+ break;
+ if (m) offset2 = grid2->width-1;
+ else offset2 = 0;
+ for ( l = 0; l < grid2->height-1; l++) {
+ //
+ v1 = grid1->verts[grid1->width * k + offset1].xyz;
+ v2 = grid2->verts[grid2->width * l + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+
+ v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz;
+ v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+ //
+ v1 = grid2->verts[grid2->width * l + offset2].xyz;
+ v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) < .01 &&
+ fabs(v1[1] - v2[1]) < .01 &&
+ fabs(v1[2] - v2[2]) < .01)
+ continue;
+ //
+ //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+ // insert row into grid2 right after after row l
+ if (m) column = grid2->width-1;
+ else column = 0;
+ grid2 = R_GridInsertRow( grid2, l+1, column,
+ grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]);
+ grid2->lodStitched = qfalse;
+ s_worldData.surfaces[grid2num].data = (void *) grid2;
+ return qtrue;
+ }
+ }
+ }
+ }
+ for (n = 0; n < 2; n++) {
+ //
+ if (n) offset1 = (grid1->height-1) * grid1->width;
+ else offset1 = 0;
+ if (R_MergedWidthPoints(grid1, offset1))
+ continue;
+ for (k = grid1->width-1; k > 1; k -= 2) {
+
+ for (m = 0; m < 2; m++) {
+
+ if ( grid2->width >= MAX_GRID_SIZE )
+ break;
+ if (m) offset2 = (grid2->height-1) * grid2->width;
+ else offset2 = 0;
+ for ( l = 0; l < grid2->width-1; l++) {
+ //
+ v1 = grid1->verts[k + offset1].xyz;
+ v2 = grid2->verts[l + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+
+ v1 = grid1->verts[k - 2 + offset1].xyz;
+ v2 = grid2->verts[l + 1 + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+ //
+ v1 = grid2->verts[l + offset2].xyz;
+ v2 = grid2->verts[(l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) < .01 &&
+ fabs(v1[1] - v2[1]) < .01 &&
+ fabs(v1[2] - v2[2]) < .01)
+ continue;
+ //
+ //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+ // insert column into grid2 right after after column l
+ if (m) row = grid2->height-1;
+ else row = 0;
+ grid2 = R_GridInsertColumn( grid2, l+1, row,
+ grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]);
+ grid2->lodStitched = qfalse;
+ s_worldData.surfaces[grid2num].data = (void *) grid2;
+ return qtrue;
+ }
+ }
+ for (m = 0; m < 2; m++) {
+
+ if (grid2->height >= MAX_GRID_SIZE)
+ break;
+ if (m) offset2 = grid2->width-1;
+ else offset2 = 0;
+ for ( l = 0; l < grid2->height-1; l++) {
+ //
+ v1 = grid1->verts[k + offset1].xyz;
+ v2 = grid2->verts[grid2->width * l + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+
+ v1 = grid1->verts[k - 2 + offset1].xyz;
+ v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+ //
+ v1 = grid2->verts[grid2->width * l + offset2].xyz;
+ v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) < .01 &&
+ fabs(v1[1] - v2[1]) < .01 &&
+ fabs(v1[2] - v2[2]) < .01)
+ continue;
+ //
+ //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+ // insert row into grid2 right after after row l
+ if (m) column = grid2->width-1;
+ else column = 0;
+ grid2 = R_GridInsertRow( grid2, l+1, column,
+ grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]);
+ if (!grid2)
+ break;
+ grid2->lodStitched = qfalse;
+ s_worldData.surfaces[grid2num].data = (void *) grid2;
+ return qtrue;
+ }
+ }
+ }
+ }
+ for (n = 0; n < 2; n++) {
+ //
+ if (n) offset1 = grid1->width-1;
+ else offset1 = 0;
+ if (R_MergedHeightPoints(grid1, offset1))
+ continue;
+ for (k = grid1->height-1; k > 1; k -= 2) {
+ for (m = 0; m < 2; m++) {
+
+ if ( grid2->width >= MAX_GRID_SIZE )
+ break;
+ if (m) offset2 = (grid2->height-1) * grid2->width;
+ else offset2 = 0;
+ for ( l = 0; l < grid2->width-1; l++) {
+ //
+ v1 = grid1->verts[grid1->width * k + offset1].xyz;
+ v2 = grid2->verts[l + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+
+ v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz;
+ v2 = grid2->verts[l + 1 + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+ //
+ v1 = grid2->verts[l + offset2].xyz;
+ v2 = grid2->verts[(l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) < .01 &&
+ fabs(v1[1] - v2[1]) < .01 &&
+ fabs(v1[2] - v2[2]) < .01)
+ continue;
+ //
+ //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+ // insert column into grid2 right after after column l
+ if (m) row = grid2->height-1;
+ else row = 0;
+ grid2 = R_GridInsertColumn( grid2, l+1, row,
+ grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]);
+ grid2->lodStitched = qfalse;
+ s_worldData.surfaces[grid2num].data = (void *) grid2;
+ return qtrue;
+ }
+ }
+ for (m = 0; m < 2; m++) {
+
+ if (grid2->height >= MAX_GRID_SIZE)
+ break;
+ if (m) offset2 = grid2->width-1;
+ else offset2 = 0;
+ for ( l = 0; l < grid2->height-1; l++) {
+ //
+ v1 = grid1->verts[grid1->width * k + offset1].xyz;
+ v2 = grid2->verts[grid2->width * l + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+
+ v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz;
+ v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) > .1)
+ continue;
+ if ( fabs(v1[1] - v2[1]) > .1)
+ continue;
+ if ( fabs(v1[2] - v2[2]) > .1)
+ continue;
+ //
+ v1 = grid2->verts[grid2->width * l + offset2].xyz;
+ v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz;
+ if ( fabs(v1[0] - v2[0]) < .01 &&
+ fabs(v1[1] - v2[1]) < .01 &&
+ fabs(v1[2] - v2[2]) < .01)
+ continue;
+ //
+ //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" );
+ // insert row into grid2 right after after row l
+ if (m) column = grid2->width-1;
+ else column = 0;
+ grid2 = R_GridInsertRow( grid2, l+1, column,
+ grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]);
+ grid2->lodStitched = qfalse;
+ s_worldData.surfaces[grid2num].data = (void *) grid2;
+ return qtrue;
+ }
+ }
+ }
+ }
+ return qfalse;
+}
+
+/*
+===============
+R_TryStitchPatch
+
+This function will try to stitch patches in the same LoD group together for the highest LoD.
+
+Only single missing vertice cracks will be fixed.
+
+Vertices will be joined at the patch side a crack is first found, at the other side
+of the patch (on the same row or column) the vertices will not be joined and cracks
+might still appear at that side.
+===============
+*/
+int R_TryStitchingPatch( int grid1num ) {
+ int j, numstitches;
+ srfGridMesh_t *grid1, *grid2;
+
+ numstitches = 0;
+ grid1 = (srfGridMesh_t *) s_worldData.surfaces[grid1num].data;
+ for ( j = 0; j < s_worldData.numsurfaces; j++ ) {
+ //
+ grid2 = (srfGridMesh_t *) s_worldData.surfaces[j].data;
+ // if this surface is not a grid
+ if ( grid2->surfaceType != SF_GRID ) continue;
+ // grids in the same LOD group should have the exact same lod radius
+ if ( grid1->lodRadius != grid2->lodRadius ) continue;
+ // grids in the same LOD group should have the exact same lod origin
+ if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue;
+ if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue;
+ if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue;
+ //
+ while (R_StitchPatches(grid1num, j))
+ {
+ numstitches++;
+ }
+ }
+ return numstitches;
+}
+
+/*
+===============
+R_StitchAllPatches
+===============
+*/
+void R_StitchAllPatches( void ) {
+ int i, stitched, numstitches;
+ srfGridMesh_t *grid1;
+
+ numstitches = 0;
+ do
+ {
+ stitched = qfalse;
+ for ( i = 0; i < s_worldData.numsurfaces; i++ ) {
+ //
+ grid1 = (srfGridMesh_t *) s_worldData.surfaces[i].data;
+ // if this surface is not a grid
+ if ( grid1->surfaceType != SF_GRID )
+ continue;
+ //
+ if ( grid1->lodStitched )
+ continue;
+ //
+ grid1->lodStitched = qtrue;
+ stitched = qtrue;
+ //
+ numstitches += R_TryStitchingPatch( i );
+ }
+ }
+ while (stitched);
+ ri.Printf( PRINT_ALL, "stitched %d LoD cracks\n", numstitches );
+}
+
+/*
+===============
+R_MovePatchSurfacesToHunk
+===============
+*/
+void R_MovePatchSurfacesToHunk(void) {
+ int i, size;
+ srfGridMesh_t *grid, *hunkgrid;
+
+ for ( i = 0; i < s_worldData.numsurfaces; i++ ) {
+ //
+ grid = (srfGridMesh_t *) s_worldData.surfaces[i].data;
+ // if this surface is not a grid
+ if ( grid->surfaceType != SF_GRID )
+ continue;
+ //
+ size = (grid->width * grid->height - 1) * sizeof( drawVert_t ) + sizeof( *grid );
+ hunkgrid = ri.Hunk_Alloc( size, h_low );
+ Com_Memcpy(hunkgrid, grid, size);
+
+ hunkgrid->widthLodError = ri.Hunk_Alloc( grid->width * 4, h_low );
+ Com_Memcpy( hunkgrid->widthLodError, grid->widthLodError, grid->width * 4 );
+
+ hunkgrid->heightLodError = ri.Hunk_Alloc( grid->height * 4, h_low );
+ Com_Memcpy( hunkgrid->heightLodError, grid->heightLodError, grid->height * 4 );
+
+ R_FreeSurfaceGridMesh( grid );
+
+ s_worldData.surfaces[i].data = (void *) hunkgrid;
+ }
+}
+
+/*
+===============
+R_LoadSurfaces
+===============
+*/
+static void R_LoadSurfaces( lump_t *surfs, lump_t *verts, lump_t *indexLump ) {
+ dsurface_t *in;
+ msurface_t *out;
+ drawVert_t *dv;
+ int *indexes;
+ int count;
+ int numFaces, numMeshes, numTriSurfs, numFlares;
+ int i;
+
+ numFaces = 0;
+ numMeshes = 0;
+ numTriSurfs = 0;
+ numFlares = 0;
+
+ in = (void *)(fileBase + surfs->fileofs);
+ if (surfs->filelen % sizeof(*in))
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ count = surfs->filelen / sizeof(*in);
+
+ dv = (void *)(fileBase + verts->fileofs);
+ if (verts->filelen % sizeof(*dv))
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+
+ indexes = (void *)(fileBase + indexLump->fileofs);
+ if ( indexLump->filelen % sizeof(*indexes))
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+
+ out = ri.Hunk_Alloc ( count * sizeof(*out), h_low );
+
+ s_worldData.surfaces = out;
+ s_worldData.numsurfaces = count;
+
+ for ( i = 0 ; i < count ; i++, in++, out++ ) {
+ switch ( LittleLong( in->surfaceType ) ) {
+ case MST_PATCH:
+ ParseMesh ( in, dv, out );
+ numMeshes++;
+ break;
+ case MST_TRIANGLE_SOUP:
+ ParseTriSurf( in, dv, out, indexes );
+ numTriSurfs++;
+ break;
+ case MST_PLANAR:
+ ParseFace( in, dv, out, indexes );
+ numFaces++;
+ break;
+ case MST_FLARE:
+ ParseFlare( in, dv, out, indexes );
+ numFlares++;
+ break;
+ default:
+ ri.Error( ERR_DROP, "Bad surfaceType" );
+ }
+ }
+
+#ifdef PATCH_STITCHING
+ R_StitchAllPatches();
+#endif
+
+ R_FixSharedVertexLodError();
+
+#ifdef PATCH_STITCHING
+ R_MovePatchSurfacesToHunk();
+#endif
+
+ ri.Printf( PRINT_ALL, "...loaded %d faces, %i meshes, %i trisurfs, %i flares\n",
+ numFaces, numMeshes, numTriSurfs, numFlares );
+}
+
+
+
+/*
+=================
+R_LoadSubmodels
+=================
+*/
+static void R_LoadSubmodels( lump_t *l ) {
+ dmodel_t *in;
+ bmodel_t *out;
+ int i, j, count;
+
+ in = (void *)(fileBase + l->fileofs);
+ if (l->filelen % sizeof(*in))
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ count = l->filelen / sizeof(*in);
+
+ s_worldData.bmodels = out = ri.Hunk_Alloc( count * sizeof(*out), h_low );
+
+ for ( i=0 ; i<count ; i++, in++, out++ ) {
+ model_t *model;
+
+ model = R_AllocModel();
+
+ assert( model != NULL ); // this should never happen
+ if ( model == NULL ) {
+ ri.Error(ERR_DROP, "R_LoadSubmodels: R_AllocModel() failed");
+ }
+
+ model->type = MOD_BRUSH;
+ model->bmodel = out;
+ Com_sprintf( model->name, sizeof( model->name ), "*%d", i );
+
+ for (j=0 ; j<3 ; j++) {
+ out->bounds[0][j] = LittleFloat (in->mins[j]);
+ out->bounds[1][j] = LittleFloat (in->maxs[j]);
+ }
+
+ out->firstSurface = s_worldData.surfaces + LittleLong( in->firstSurface );
+ out->numSurfaces = LittleLong( in->numSurfaces );
+ }
+}
+
+
+
+//==================================================================
+
+/*
+=================
+R_SetParent
+=================
+*/
+static void R_SetParent (mnode_t *node, mnode_t *parent)
+{
+ node->parent = parent;
+ if (node->contents != -1)
+ return;
+ R_SetParent (node->children[0], node);
+ R_SetParent (node->children[1], node);
+}
+
+/*
+=================
+R_LoadNodesAndLeafs
+=================
+*/
+static void R_LoadNodesAndLeafs (lump_t *nodeLump, lump_t *leafLump) {
+ int i, j, p;
+ dnode_t *in;
+ dleaf_t *inLeaf;
+ mnode_t *out;
+ int numNodes, numLeafs;
+
+ in = (void *)(fileBase + nodeLump->fileofs);
+ if (nodeLump->filelen % sizeof(dnode_t) ||
+ leafLump->filelen % sizeof(dleaf_t) ) {
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ }
+ numNodes = nodeLump->filelen / sizeof(dnode_t);
+ numLeafs = leafLump->filelen / sizeof(dleaf_t);
+
+ out = ri.Hunk_Alloc ( (numNodes + numLeafs) * sizeof(*out), h_low);
+
+ s_worldData.nodes = out;
+ s_worldData.numnodes = numNodes + numLeafs;
+ s_worldData.numDecisionNodes = numNodes;
+
+ // load nodes
+ for ( i=0 ; i<numNodes; i++, in++, out++)
+ {
+ for (j=0 ; j<3 ; j++)
+ {
+ out->mins[j] = LittleLong (in->mins[j]);
+ out->maxs[j] = LittleLong (in->maxs[j]);
+ }
+
+ p = LittleLong(in->planeNum);
+ out->plane = s_worldData.planes + p;
+
+ out->contents = CONTENTS_NODE; // differentiate from leafs
+
+ for (j=0 ; j<2 ; j++)
+ {
+ p = LittleLong (in->children[j]);
+ if (p >= 0)
+ out->children[j] = s_worldData.nodes + p;
+ else
+ out->children[j] = s_worldData.nodes + numNodes + (-1 - p);
+ }
+ }
+
+ // load leafs
+ inLeaf = (void *)(fileBase + leafLump->fileofs);
+ for ( i=0 ; i<numLeafs ; i++, inLeaf++, out++)
+ {
+ for (j=0 ; j<3 ; j++)
+ {
+ out->mins[j] = LittleLong (inLeaf->mins[j]);
+ out->maxs[j] = LittleLong (inLeaf->maxs[j]);
+ }
+
+ out->cluster = LittleLong(inLeaf->cluster);
+ out->area = LittleLong(inLeaf->area);
+
+ if ( out->cluster >= s_worldData.numClusters ) {
+ s_worldData.numClusters = out->cluster + 1;
+ }
+
+ out->firstmarksurface = s_worldData.marksurfaces +
+ LittleLong(inLeaf->firstLeafSurface);
+ out->nummarksurfaces = LittleLong(inLeaf->numLeafSurfaces);
+ }
+
+ // chain decendants
+ R_SetParent (s_worldData.nodes, NULL);
+}
+
+//=============================================================================
+
+/*
+=================
+R_LoadShaders
+=================
+*/
+static void R_LoadShaders( lump_t *l ) {
+ int i, count;
+ dshader_t *in, *out;
+
+ in = (void *)(fileBase + l->fileofs);
+ if (l->filelen % sizeof(*in))
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ count = l->filelen / sizeof(*in);
+ out = ri.Hunk_Alloc ( count*sizeof(*out), h_low );
+
+ s_worldData.shaders = out;
+ s_worldData.numShaders = count;
+
+ Com_Memcpy( out, in, count*sizeof(*out) );
+
+ for ( i=0 ; i<count ; i++ ) {
+ out[i].surfaceFlags = LittleLong( out[i].surfaceFlags );
+ out[i].contentFlags = LittleLong( out[i].contentFlags );
+ }
+}
+
+
+/*
+=================
+R_LoadMarksurfaces
+=================
+*/
+static void R_LoadMarksurfaces (lump_t *l)
+{
+ int i, j, count;
+ int *in;
+ msurface_t **out;
+
+ in = (void *)(fileBase + l->fileofs);
+ if (l->filelen % sizeof(*in))
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ count = l->filelen / sizeof(*in);
+ out = ri.Hunk_Alloc ( count*sizeof(*out), h_low);
+
+ s_worldData.marksurfaces = out;
+ s_worldData.nummarksurfaces = count;
+
+ for ( i=0 ; i<count ; i++)
+ {
+ j = LittleLong(in[i]);
+ out[i] = s_worldData.surfaces + j;
+ }
+}
+
+
+/*
+=================
+R_LoadPlanes
+=================
+*/
+static void R_LoadPlanes( lump_t *l ) {
+ int i, j;
+ cplane_t *out;
+ dplane_t *in;
+ int count;
+ int bits;
+
+ in = (void *)(fileBase + l->fileofs);
+ if (l->filelen % sizeof(*in))
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ count = l->filelen / sizeof(*in);
+ out = ri.Hunk_Alloc ( count*2*sizeof(*out), h_low);
+
+ s_worldData.planes = out;
+ s_worldData.numplanes = count;
+
+ for ( i=0 ; i<count ; i++, in++, out++) {
+ bits = 0;
+ for (j=0 ; j<3 ; j++) {
+ out->normal[j] = LittleFloat (in->normal[j]);
+ if (out->normal[j] < 0) {
+ bits |= 1<<j;
+ }
+ }
+
+ out->dist = LittleFloat (in->dist);
+ out->type = PlaneTypeForNormal( out->normal );
+ out->signbits = bits;
+ }
+}
+
+/*
+=================
+R_LoadFogs
+
+=================
+*/
+static void R_LoadFogs( lump_t *l, lump_t *brushesLump, lump_t *sidesLump ) {
+ int i;
+ fog_t *out;
+ dfog_t *fogs;
+ dbrush_t *brushes, *brush;
+ dbrushside_t *sides;
+ int count, brushesCount, sidesCount;
+ int sideNum;
+ int planeNum;
+ shader_t *shader;
+ float d;
+ int firstSide;
+
+ fogs = (void *)(fileBase + l->fileofs);
+ if (l->filelen % sizeof(*fogs)) {
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ }
+ count = l->filelen / sizeof(*fogs);
+
+ // create fog strucutres for them
+ s_worldData.numfogs = count + 1;
+ s_worldData.fogs = ri.Hunk_Alloc ( s_worldData.numfogs*sizeof(*out), h_low);
+ out = s_worldData.fogs + 1;
+
+ if ( !count ) {
+ return;
+ }
+
+ brushes = (void *)(fileBase + brushesLump->fileofs);
+ if (brushesLump->filelen % sizeof(*brushes)) {
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ }
+ brushesCount = brushesLump->filelen / sizeof(*brushes);
+
+ sides = (void *)(fileBase + sidesLump->fileofs);
+ if (sidesLump->filelen % sizeof(*sides)) {
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ }
+ sidesCount = sidesLump->filelen / sizeof(*sides);
+
+ for ( i=0 ; i<count ; i++, fogs++) {
+ out->originalBrushNumber = LittleLong( fogs->brushNum );
+
+ if ( (unsigned)out->originalBrushNumber >= brushesCount ) {
+ ri.Error( ERR_DROP, "fog brushNumber out of range" );
+ }
+ brush = brushes + out->originalBrushNumber;
+
+ firstSide = LittleLong( brush->firstSide );
+
+ if ( (unsigned)firstSide > sidesCount - 6 ) {
+ ri.Error( ERR_DROP, "fog brush sideNumber out of range" );
+ }
+
+ // brushes are always sorted with the axial sides first
+ sideNum = firstSide + 0;
+ planeNum = LittleLong( sides[ sideNum ].planeNum );
+ out->bounds[0][0] = -s_worldData.planes[ planeNum ].dist;
+
+ sideNum = firstSide + 1;
+ planeNum = LittleLong( sides[ sideNum ].planeNum );
+ out->bounds[1][0] = s_worldData.planes[ planeNum ].dist;
+
+ sideNum = firstSide + 2;
+ planeNum = LittleLong( sides[ sideNum ].planeNum );
+ out->bounds[0][1] = -s_worldData.planes[ planeNum ].dist;
+
+ sideNum = firstSide + 3;
+ planeNum = LittleLong( sides[ sideNum ].planeNum );
+ out->bounds[1][1] = s_worldData.planes[ planeNum ].dist;
+
+ sideNum = firstSide + 4;
+ planeNum = LittleLong( sides[ sideNum ].planeNum );
+ out->bounds[0][2] = -s_worldData.planes[ planeNum ].dist;
+
+ sideNum = firstSide + 5;
+ planeNum = LittleLong( sides[ sideNum ].planeNum );
+ out->bounds[1][2] = s_worldData.planes[ planeNum ].dist;
+
+ // get information from the shader for fog parameters
+ shader = R_FindShader( fogs->shader, LIGHTMAP_NONE, qtrue );
+
+ out->parms = shader->fogParms;
+
+ out->colorInt = ColorBytes4 ( shader->fogParms.color[0] * tr.identityLight,
+ shader->fogParms.color[1] * tr.identityLight,
+ shader->fogParms.color[2] * tr.identityLight, 1.0 );
+
+ d = shader->fogParms.depthForOpaque < 1 ? 1 : shader->fogParms.depthForOpaque;
+ out->tcScale = 1.0f / ( d * 8 );
+
+ // set the gradient vector
+ sideNum = LittleLong( fogs->visibleSide );
+
+ if ( sideNum == -1 ) {
+ out->hasSurface = qfalse;
+ } else {
+ out->hasSurface = qtrue;
+ planeNum = LittleLong( sides[ firstSide + sideNum ].planeNum );
+ VectorSubtract( vec3_origin, s_worldData.planes[ planeNum ].normal, out->surface );
+ out->surface[3] = -s_worldData.planes[ planeNum ].dist;
+ }
+
+ out++;
+ }
+
+}
+
+
+/*
+================
+R_LoadLightGrid
+
+================
+*/
+void R_LoadLightGrid( lump_t *l ) {
+ int i;
+ vec3_t maxs;
+ int numGridPoints;
+ world_t *w;
+ float *wMins, *wMaxs;
+
+ w = &s_worldData;
+
+ w->lightGridInverseSize[0] = 1.0f / w->lightGridSize[0];
+ w->lightGridInverseSize[1] = 1.0f / w->lightGridSize[1];
+ w->lightGridInverseSize[2] = 1.0f / w->lightGridSize[2];
+
+ wMins = w->bmodels[0].bounds[0];
+ wMaxs = w->bmodels[0].bounds[1];
+
+ for ( i = 0 ; i < 3 ; i++ ) {
+ w->lightGridOrigin[i] = w->lightGridSize[i] * ceil( wMins[i] / w->lightGridSize[i] );
+ maxs[i] = w->lightGridSize[i] * floor( wMaxs[i] / w->lightGridSize[i] );
+ w->lightGridBounds[i] = (maxs[i] - w->lightGridOrigin[i])/w->lightGridSize[i] + 1;
+ }
+
+ numGridPoints = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2];
+
+ if ( l->filelen != numGridPoints * 8 ) {
+ ri.Printf( PRINT_WARNING, "WARNING: light grid mismatch\n" );
+ w->lightGridData = NULL;
+ return;
+ }
+
+ w->lightGridData = ri.Hunk_Alloc( l->filelen, h_low );
+ Com_Memcpy( w->lightGridData, (void *)(fileBase + l->fileofs), l->filelen );
+
+ // deal with overbright bits
+ for ( i = 0 ; i < numGridPoints ; i++ ) {
+ R_ColorShiftLightingBytes( &w->lightGridData[i*8], &w->lightGridData[i*8] );
+ R_ColorShiftLightingBytes( &w->lightGridData[i*8+3], &w->lightGridData[i*8+3] );
+ }
+}
+
+/*
+================
+R_LoadEntities
+================
+*/
+void R_LoadEntities( lump_t *l ) {
+ char *p, *token, *s;
+ char keyname[MAX_TOKEN_CHARS];
+ char value[MAX_TOKEN_CHARS];
+ world_t *w;
+
+ w = &s_worldData;
+ w->lightGridSize[0] = 64;
+ w->lightGridSize[1] = 64;
+ w->lightGridSize[2] = 128;
+
+ p = (char *)(fileBase + l->fileofs);
+
+ // store for reference by the cgame
+ w->entityString = ri.Hunk_Alloc( l->filelen + 1, h_low );
+ strcpy( w->entityString, p );
+ w->entityParsePoint = w->entityString;
+
+ token = COM_ParseExt( &p, qtrue );
+ if (!*token || *token != '{') {
+ return;
+ }
+
+ // only parse the world spawn
+ while ( 1 ) {
+ // parse key
+ token = COM_ParseExt( &p, qtrue );
+
+ if ( !*token || *token == '}' ) {
+ break;
+ }
+ Q_strncpyz(keyname, token, sizeof(keyname));
+
+ // parse value
+ token = COM_ParseExt( &p, qtrue );
+
+ if ( !*token || *token == '}' ) {
+ break;
+ }
+ Q_strncpyz(value, token, sizeof(value));
+
+ // check for remapping of shaders for vertex lighting
+ s = "vertexremapshader";
+ if (!Q_strncmp(keyname, s, strlen(s)) ) {
+ s = strchr(value, ';');
+ if (!s) {
+ ri.Printf( PRINT_WARNING, "WARNING: no semi colon in vertexshaderremap '%s'\n", value );
+ break;
+ }
+ *s++ = 0;
+ if (r_vertexLight->integer) {
+ R_RemapShader(value, s, "0");
+ }
+ continue;
+ }
+ // check for remapping of shaders
+ s = "remapshader";
+ if (!Q_strncmp(keyname, s, strlen(s)) ) {
+ s = strchr(value, ';');
+ if (!s) {
+ ri.Printf( PRINT_WARNING, "WARNING: no semi colon in shaderremap '%s'\n", value );
+ break;
+ }
+ *s++ = 0;
+ R_RemapShader(value, s, "0");
+ continue;
+ }
+ // check for a different grid size
+ if (!Q_stricmp(keyname, "gridsize")) {
+ sscanf(value, "%f %f %f", &w->lightGridSize[0], &w->lightGridSize[1], &w->lightGridSize[2] );
+ continue;
+ }
+ }
+}
+
+/*
+=================
+R_GetEntityToken
+=================
+*/
+qboolean R_GetEntityToken( char *buffer, int size ) {
+ const char *s;
+
+ s = COM_Parse( &s_worldData.entityParsePoint );
+ Q_strncpyz( buffer, s, size );
+ if ( !s_worldData.entityParsePoint || !s[0] ) {
+ s_worldData.entityParsePoint = s_worldData.entityString;
+ return qfalse;
+ } else {
+ return qtrue;
+ }
+}
+
+/*
+=================
+RE_LoadWorldMap
+
+Called directly from cgame
+=================
+*/
+void RE_LoadWorldMap( const char *name ) {
+ int i;
+ dheader_t *header;
+ union {
+ byte *b;
+ void *v;
+ } buffer;
+ byte *startMarker;
+
+ if ( tr.worldMapLoaded ) {
+ ri.Error( ERR_DROP, "ERROR: attempted to redundantly load world map\n" );
+ }
+
+ // set default sun direction to be used if it isn't
+ // overridden by a shader
+ tr.sunDirection[0] = 0.45f;
+ tr.sunDirection[1] = 0.3f;
+ tr.sunDirection[2] = 0.9f;
+
+ VectorNormalize( tr.sunDirection );
+
+ tr.worldMapLoaded = qtrue;
+
+ // load it
+ ri.FS_ReadFile( name, &buffer.v );
+ if ( !buffer.b ) {
+ ri.Error (ERR_DROP, "RE_LoadWorldMap: %s not found", name);
+ }
+
+ // clear tr.world so if the level fails to load, the next
+ // try will not look at the partially loaded version
+ tr.world = NULL;
+
+ Com_Memset( &s_worldData, 0, sizeof( s_worldData ) );
+ Q_strncpyz( s_worldData.name, name, sizeof( s_worldData.name ) );
+
+ Q_strncpyz( s_worldData.baseName, COM_SkipPath( s_worldData.name ), sizeof( s_worldData.name ) );
+ COM_StripExtension(s_worldData.baseName, s_worldData.baseName, sizeof(s_worldData.baseName));
+
+ startMarker = ri.Hunk_Alloc(0, h_low);
+ c_gridVerts = 0;
+
+ header = (dheader_t *)buffer.b;
+ fileBase = (byte *)header;
+
+ i = LittleLong (header->version);
+ if ( i != BSP_VERSION ) {
+ ri.Error (ERR_DROP, "RE_LoadWorldMap: %s has wrong version number (%i should be %i)",
+ name, i, BSP_VERSION);
+ }
+
+ // swap all the lumps
+ for (i=0 ; i<sizeof(dheader_t)/4 ; i++) {
+ ((int *)header)[i] = LittleLong ( ((int *)header)[i]);
+ }
+
+ // load into heap
+ R_LoadShaders( &header->lumps[LUMP_SHADERS] );
+ R_LoadLightmaps( &header->lumps[LUMP_LIGHTMAPS] );
+ R_LoadPlanes (&header->lumps[LUMP_PLANES]);
+ R_LoadFogs( &header->lumps[LUMP_FOGS], &header->lumps[LUMP_BRUSHES], &header->lumps[LUMP_BRUSHSIDES] );
+ R_LoadSurfaces( &header->lumps[LUMP_SURFACES], &header->lumps[LUMP_DRAWVERTS], &header->lumps[LUMP_DRAWINDEXES] );
+ R_LoadMarksurfaces (&header->lumps[LUMP_LEAFSURFACES]);
+ R_LoadNodesAndLeafs (&header->lumps[LUMP_NODES], &header->lumps[LUMP_LEAFS]);
+ R_LoadSubmodels (&header->lumps[LUMP_MODELS]);
+ R_LoadVisibility( &header->lumps[LUMP_VISIBILITY] );
+ R_LoadEntities( &header->lumps[LUMP_ENTITIES] );
+ R_LoadLightGrid( &header->lumps[LUMP_LIGHTGRID] );
+
+ s_worldData.dataSize = (byte *)ri.Hunk_Alloc(0, h_low) - startMarker;
+
+ // only set tr.world now that we know the entire level has loaded properly
+ tr.world = &s_worldData;
+
+ ri.FS_FreeFile( buffer.v );
+}
+
diff --git a/code/renderer/tr_cmds.c b/code/renderer/tr_cmds.c
new file mode 100644
index 0000000..5a7f3e6
--- /dev/null
+++ b/code/renderer/tr_cmds.c
@@ -0,0 +1,566 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#include "tr_local.h"
+
+volatile renderCommandList_t *renderCommandList;
+
+volatile qboolean renderThreadActive;
+
+
+/*
+=====================
+R_PerformanceCounters
+=====================
+*/
+void R_PerformanceCounters( void ) {
+ if ( !r_speeds->integer ) {
+ // clear the counters even if we aren't printing
+ Com_Memset( &tr.pc, 0, sizeof( tr.pc ) );
+ Com_Memset( &backEnd.pc, 0, sizeof( backEnd.pc ) );
+ return;
+ }
+
+ if (r_speeds->integer == 1) {
+ ri.Printf (PRINT_ALL, "%i/%i shaders/surfs %i leafs %i verts %i/%i tris %.2f mtex %.2f dc\n",
+ backEnd.pc.c_shaders, backEnd.pc.c_surfaces, tr.pc.c_leafs, backEnd.pc.c_vertexes,
+ backEnd.pc.c_indexes/3, backEnd.pc.c_totalIndexes/3,
+ R_SumOfUsedImages()/(1000000.0f), backEnd.pc.c_overDraw / (float)(glConfig.vidWidth * glConfig.vidHeight) );
+ } else if (r_speeds->integer == 2) {
+ ri.Printf (PRINT_ALL, "(patch) %i sin %i sclip %i sout %i bin %i bclip %i bout\n",
+ tr.pc.c_sphere_cull_patch_in, tr.pc.c_sphere_cull_patch_clip, tr.pc.c_sphere_cull_patch_out,
+ tr.pc.c_box_cull_patch_in, tr.pc.c_box_cull_patch_clip, tr.pc.c_box_cull_patch_out );
+ ri.Printf (PRINT_ALL, "(md3) %i sin %i sclip %i sout %i bin %i bclip %i bout\n",
+ tr.pc.c_sphere_cull_md3_in, tr.pc.c_sphere_cull_md3_clip, tr.pc.c_sphere_cull_md3_out,
+ tr.pc.c_box_cull_md3_in, tr.pc.c_box_cull_md3_clip, tr.pc.c_box_cull_md3_out );
+ } else if (r_speeds->integer == 3) {
+ ri.Printf (PRINT_ALL, "viewcluster: %i\n", tr.viewCluster );
+ } else if (r_speeds->integer == 4) {
+ if ( backEnd.pc.c_dlightVertexes ) {
+ ri.Printf (PRINT_ALL, "dlight srf:%i culled:%i verts:%i tris:%i\n",
+ tr.pc.c_dlightSurfaces, tr.pc.c_dlightSurfacesCulled,
+ backEnd.pc.c_dlightVertexes, backEnd.pc.c_dlightIndexes / 3 );
+ }
+ }
+ else if (r_speeds->integer == 5 )
+ {
+ ri.Printf( PRINT_ALL, "zFar: %.0f\n", tr.viewParms.zFar );
+ }
+ else if (r_speeds->integer == 6 )
+ {
+ ri.Printf( PRINT_ALL, "flare adds:%i tests:%i renders:%i\n",
+ backEnd.pc.c_flareAdds, backEnd.pc.c_flareTests, backEnd.pc.c_flareRenders );
+ }
+
+ Com_Memset( &tr.pc, 0, sizeof( tr.pc ) );
+ Com_Memset( &backEnd.pc, 0, sizeof( backEnd.pc ) );
+}
+
+
+/*
+====================
+R_InitCommandBuffers
+====================
+*/
+void R_InitCommandBuffers( void ) {
+ glConfig.smpActive = qfalse;
+ if ( r_smp->integer ) {
+ ri.Printf( PRINT_ALL, "Trying SMP acceleration...\n" );
+ if ( GLimp_SpawnRenderThread( RB_RenderThread ) ) {
+ ri.Printf( PRINT_ALL, "...succeeded.\n" );
+ glConfig.smpActive = qtrue;
+ } else {
+ ri.Printf( PRINT_ALL, "...failed.\n" );
+ }
+ }
+}
+
+/*
+====================
+R_ShutdownCommandBuffers
+====================
+*/
+void R_ShutdownCommandBuffers( void ) {
+ // kill the rendering thread
+ if ( glConfig.smpActive ) {
+ GLimp_WakeRenderer( NULL );
+ glConfig.smpActive = qfalse;
+ }
+}
+
+/*
+====================
+R_IssueRenderCommands
+====================
+*/
+int c_blockedOnRender;
+int c_blockedOnMain;
+
+void R_IssueRenderCommands( qboolean runPerformanceCounters ) {
+ renderCommandList_t *cmdList;
+
+ cmdList = &backEndData[tr.smpFrame]->commands;
+ assert(cmdList);
+ // add an end-of-list command
+ *(int *)(cmdList->cmds + cmdList->used) = RC_END_OF_LIST;
+
+ // clear it out, in case this is a sync and not a buffer flip
+ cmdList->used = 0;
+
+ if ( glConfig.smpActive ) {
+ // if the render thread is not idle, wait for it
+ if ( renderThreadActive ) {
+ c_blockedOnRender++;
+ if ( r_showSmp->integer ) {
+ ri.Printf( PRINT_ALL, "R" );
+ }
+ } else {
+ c_blockedOnMain++;
+ if ( r_showSmp->integer ) {
+ ri.Printf( PRINT_ALL, "." );
+ }
+ }
+
+ // sleep until the renderer has completed
+ GLimp_FrontEndSleep();
+ }
+
+ // at this point, the back end thread is idle, so it is ok
+ // to look at it's performance counters
+ if ( runPerformanceCounters ) {
+ R_PerformanceCounters();
+ }
+
+ // actually start the commands going
+ if ( !r_skipBackEnd->integer ) {
+ // let it start on the new batch
+ if ( !glConfig.smpActive ) {
+ RB_ExecuteRenderCommands( cmdList->cmds );
+ } else {
+ GLimp_WakeRenderer( cmdList );
+ }
+ }
+}
+
+
+/*
+====================
+R_SyncRenderThread
+
+Issue any pending commands and wait for them to complete.
+After exiting, the render thread will have completed its work
+and will remain idle and the main thread is free to issue
+OpenGL calls until R_IssueRenderCommands is called.
+====================
+*/
+void R_SyncRenderThread( void ) {
+ if ( !tr.registered ) {
+ return;
+ }
+ R_IssueRenderCommands( qfalse );
+
+ if ( !glConfig.smpActive ) {
+ return;
+ }
+ GLimp_FrontEndSleep();
+}
+
+/*
+============
+R_GetCommandBuffer
+
+make sure there is enough command space, waiting on the
+render thread if needed.
+============
+*/
+void *R_GetCommandBuffer( int bytes ) {
+ renderCommandList_t *cmdList;
+
+ cmdList = &backEndData[tr.smpFrame]->commands;
+
+ // always leave room for the end of list command
+ if ( cmdList->used + bytes + 4 > MAX_RENDER_COMMANDS ) {
+ if ( bytes > MAX_RENDER_COMMANDS - 4 ) {
+ ri.Error( ERR_FATAL, "R_GetCommandBuffer: bad size %i", bytes );
+ }
+ // if we run out of room, just start dropping commands
+ return NULL;
+ }
+
+ cmdList->used += bytes;
+
+ return cmdList->cmds + cmdList->used - bytes;
+}
+
+
+/*
+=============
+R_AddDrawSurfCmd
+
+=============
+*/
+void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs ) {
+ drawSurfsCommand_t *cmd;
+
+ cmd = R_GetCommandBuffer( sizeof( *cmd ) );
+ if ( !cmd ) {
+ return;
+ }
+ cmd->commandId = RC_DRAW_SURFS;
+
+ cmd->drawSurfs = drawSurfs;
+ cmd->numDrawSurfs = numDrawSurfs;
+
+ cmd->refdef = tr.refdef;
+ cmd->viewParms = tr.viewParms;
+}
+
+
+/*
+=============
+RE_SetColor
+
+Passing NULL will set the color to white
+=============
+*/
+void RE_SetColor( const float *rgba ) {
+ setColorCommand_t *cmd;
+
+ if ( !tr.registered ) {
+ return;
+ }
+ cmd = R_GetCommandBuffer( sizeof( *cmd ) );
+ if ( !cmd ) {
+ return;
+ }
+ cmd->commandId = RC_SET_COLOR;
+ if ( !rgba ) {
+ static float colorWhite[4] = { 1, 1, 1, 1 };
+
+ rgba = colorWhite;
+ }
+
+ cmd->color[0] = rgba[0];
+ cmd->color[1] = rgba[1];
+ cmd->color[2] = rgba[2];
+ cmd->color[3] = rgba[3];
+}
+
+
+/*
+=============
+RE_StretchPic
+=============
+*/
+void RE_StretchPic ( float x, float y, float w, float h,
+ float s1, float t1, float s2, float t2, qhandle_t hShader ) {
+ stretchPicCommand_t *cmd;
+
+ if (!tr.registered) {
+ return;
+ }
+ cmd = R_GetCommandBuffer( sizeof( *cmd ) );
+ if ( !cmd ) {
+ return;
+ }
+ cmd->commandId = RC_STRETCH_PIC;
+ cmd->shader = R_GetShaderByHandle( hShader );
+ cmd->x = x;
+ cmd->y = y;
+ cmd->w = w;
+ cmd->h = h;
+ cmd->s1 = s1;
+ cmd->t1 = t1;
+ cmd->s2 = s2;
+ cmd->t2 = t2;
+}
+
+#define MODE_RED_CYAN 1
+#define MODE_RED_BLUE 2
+#define MODE_RED_GREEN 3
+#define MODE_MAX MODE_RED_GREEN
+
+void R_SetColorMode(GLboolean *rgba, stereoFrame_t stereoFrame, int colormode)
+{
+ rgba[0] = rgba[1] = rgba[2] = rgba[3] = GL_TRUE;
+
+ if(colormode > MODE_MAX)
+ {
+ if(stereoFrame == STEREO_LEFT)
+ stereoFrame = STEREO_RIGHT;
+ else if(stereoFrame == STEREO_RIGHT)
+ stereoFrame = STEREO_LEFT;
+
+ colormode -= MODE_MAX;
+ }
+
+ if(stereoFrame == STEREO_LEFT)
+ rgba[1] = rgba[2] = GL_FALSE;
+ else if(stereoFrame == STEREO_RIGHT)
+ {
+ rgba[0] = GL_FALSE;
+
+ if(colormode == MODE_RED_BLUE)
+ rgba[1] = GL_FALSE;
+ else if(colormode == MODE_RED_GREEN)
+ rgba[2] = GL_FALSE;
+ }
+}
+
+
+/*
+====================
+RE_BeginFrame
+
+If running in stereo, RE_BeginFrame will be called twice
+for each RE_EndFrame
+====================
+*/
+void RE_BeginFrame( stereoFrame_t stereoFrame ) {
+ drawBufferCommand_t *cmd = NULL;
+ colorMaskCommand_t *colcmd = NULL;
+
+ if ( !tr.registered ) {
+ return;
+ }
+ glState.finishCalled = qfalse;
+
+ tr.frameCount++;
+ tr.frameSceneNum = 0;
+
+ //
+ // do overdraw measurement
+ //
+ if ( r_measureOverdraw->integer )
+ {
+ if ( glConfig.stencilBits < 4 )
+ {
+ ri.Printf( PRINT_ALL, "Warning: not enough stencil bits to measure overdraw: %d\n", glConfig.stencilBits );
+ ri.Cvar_Set( "r_measureOverdraw", "0" );
+ r_measureOverdraw->modified = qfalse;
+ }
+ else if ( r_shadows->integer == 2 )
+ {
+ ri.Printf( PRINT_ALL, "Warning: stencil shadows and overdraw measurement are mutually exclusive\n" );
+ ri.Cvar_Set( "r_measureOverdraw", "0" );
+ r_measureOverdraw->modified = qfalse;
+ }
+ else
+ {
+ R_SyncRenderThread();
+ qglEnable( GL_STENCIL_TEST );
+ qglStencilMask( ~0U );
+ qglClearStencil( 0U );
+ qglStencilFunc( GL_ALWAYS, 0U, ~0U );
+ qglStencilOp( GL_KEEP, GL_INCR, GL_INCR );
+ }
+ r_measureOverdraw->modified = qfalse;
+ }
+ else
+ {
+ // this is only reached if it was on and is now off
+ if ( r_measureOverdraw->modified ) {
+ R_SyncRenderThread();
+ qglDisable( GL_STENCIL_TEST );
+ }
+ r_measureOverdraw->modified = qfalse;
+ }
+
+ //
+ // texturemode stuff
+ //
+ if ( r_textureMode->modified ) {
+ R_SyncRenderThread();
+ GL_TextureMode( r_textureMode->string );
+ r_textureMode->modified = qfalse;
+ }
+
+ //
+ // gamma stuff
+ //
+ if ( r_gamma->modified ) {
+ r_gamma->modified = qfalse;
+
+ R_SyncRenderThread();
+ R_SetColorMappings();
+ }
+
+ // check for errors
+ if ( !r_ignoreGLErrors->integer )
+ {
+ int err;
+
+ R_SyncRenderThread();
+ if ((err = qglGetError()) != GL_NO_ERROR)
+ ri.Error(ERR_FATAL, "RE_BeginFrame() - glGetError() failed (0x%x)!\n", err);
+ }
+#ifndef PANDORA
+ if (glConfig.stereoEnabled) {
+ if( !(cmd = R_GetCommandBuffer(sizeof(*cmd))) )
+ return;
+
+ cmd->commandId = RC_DRAW_BUFFER;
+
+ if ( stereoFrame == STEREO_LEFT ) {
+ cmd->buffer = (int)GL_BACK_LEFT;
+ } else if ( stereoFrame == STEREO_RIGHT ) {
+ cmd->buffer = (int)GL_BACK_RIGHT;
+ } else {
+ ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", stereoFrame );
+ }
+ }
+ else
+#endif
+ {
+ if(r_anaglyphMode->integer)
+ {
+ if(r_anaglyphMode->modified)
+ {
+ // clear both, front and backbuffer.
+ qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ qglClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+
+ qglDrawBuffer(GL_FRONT);
+ qglClear(GL_COLOR_BUFFER_BIT);
+ qglDrawBuffer(GL_BACK);
+ qglClear(GL_COLOR_BUFFER_BIT);
+
+ r_anaglyphMode->modified = qfalse;
+ }
+
+ if(stereoFrame == STEREO_LEFT)
+ {
+ if( !(cmd = R_GetCommandBuffer(sizeof(*cmd))) )
+ return;
+
+ if( !(colcmd = R_GetCommandBuffer(sizeof(*colcmd))) )
+ return;
+ }
+ else if(stereoFrame == STEREO_RIGHT)
+ {
+ clearDepthCommand_t *cldcmd;
+
+ if( !(cldcmd = R_GetCommandBuffer(sizeof(*cldcmd))) )
+ return;
+
+ cldcmd->commandId = RC_CLEARDEPTH;
+
+ if( !(colcmd = R_GetCommandBuffer(sizeof(*colcmd))) )
+ return;
+ }
+ else
+ ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", stereoFrame );
+
+ R_SetColorMode(colcmd->rgba, stereoFrame, r_anaglyphMode->integer);
+ colcmd->commandId = RC_COLORMASK;
+ }
+ else
+ {
+ if(stereoFrame != STEREO_CENTER)
+ ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is disabled, but stereoFrame was %i", stereoFrame );
+
+ if( !(cmd = R_GetCommandBuffer(sizeof(*cmd))) )
+ return;
+ }
+
+ if(cmd)
+ {
+ cmd->commandId = RC_DRAW_BUFFER;
+
+ if(r_anaglyphMode->modified)
+ {
+ qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ r_anaglyphMode->modified = qfalse;
+ }
+
+ if (!Q_stricmp(r_drawBuffer->string, "GL_FRONT"))
+ cmd->buffer = (int)GL_FRONT;
+ else
+ cmd->buffer = (int)GL_BACK;
+ }
+ }
+
+ tr.refdef.stereoFrame = stereoFrame;
+}
+
+
+/*
+=============
+RE_EndFrame
+
+Returns the number of msec spent in the back end
+=============
+*/
+void RE_EndFrame( int *frontEndMsec, int *backEndMsec ) {
+ swapBuffersCommand_t *cmd;
+
+ if ( !tr.registered ) {
+ return;
+ }
+ cmd = R_GetCommandBuffer( sizeof( *cmd ) );
+ if ( !cmd ) {
+ return;
+ }
+ cmd->commandId = RC_SWAP_BUFFERS;
+
+ R_IssueRenderCommands( qtrue );
+
+ // use the other buffers next frame, because another CPU
+ // may still be rendering into the current ones
+ R_ToggleSmpFrame();
+
+ if ( frontEndMsec ) {
+ *frontEndMsec = tr.frontEndMsec;
+ }
+ tr.frontEndMsec = 0;
+ if ( backEndMsec ) {
+ *backEndMsec = backEnd.pc.msec;
+ }
+ backEnd.pc.msec = 0;
+}
+
+/*
+=============
+RE_TakeVideoFrame
+=============
+*/
+void RE_TakeVideoFrame( int width, int height,
+ byte *captureBuffer, byte *encodeBuffer, qboolean motionJpeg )
+{
+ videoFrameCommand_t *cmd;
+
+ if( !tr.registered ) {
+ return;
+ }
+
+ cmd = R_GetCommandBuffer( sizeof( *cmd ) );
+ if( !cmd ) {
+ return;
+ }
+
+ cmd->commandId = RC_VIDEOFRAME;
+
+ cmd->width = width;
+ cmd->height = height;
+ cmd->captureBuffer = captureBuffer;
+ cmd->encodeBuffer = encodeBuffer;
+ cmd->motionJpeg = motionJpeg;
+}
diff --git a/code/renderer/tr_curve.c b/code/renderer/tr_curve.c
new file mode 100644
index 0000000..7eb1b4c
--- /dev/null
+++ b/code/renderer/tr_curve.c
@@ -0,0 +1,626 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "tr_local.h"
+
+/*
+
+This file does all of the processing necessary to turn a raw grid of points
+read from the map file into a srfGridMesh_t ready for rendering.
+
+The level of detail solution is direction independent, based only on subdivided
+distance from the true curve.
+
+Only a single entry point:
+
+srfGridMesh_t *R_SubdividePatchToGrid( int width, int height,
+ drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) {
+
+*/
+
+
+/*
+============
+LerpDrawVert
+============
+*/
+static void LerpDrawVert( drawVert_t *a, drawVert_t *b, drawVert_t *out ) {
+ out->xyz[0] = 0.5f * (a->xyz[0] + b->xyz[0]);
+ out->xyz[1] = 0.5f * (a->xyz[1] + b->xyz[1]);
+ out->xyz[2] = 0.5f * (a->xyz[2] + b->xyz[2]);
+
+ out->st[0] = 0.5f * (a->st[0] + b->st[0]);
+ out->st[1] = 0.5f * (a->st[1] + b->st[1]);
+
+ out->lightmap[0] = 0.5f * (a->lightmap[0] + b->lightmap[0]);
+ out->lightmap[1] = 0.5f * (a->lightmap[1] + b->lightmap[1]);
+
+ out->color[0] = (a->color[0] + b->color[0]) >> 1;
+ out->color[1] = (a->color[1] + b->color[1]) >> 1;
+ out->color[2] = (a->color[2] + b->color[2]) >> 1;
+ out->color[3] = (a->color[3] + b->color[3]) >> 1;
+}
+
+/*
+============
+Transpose
+============
+*/
+static void Transpose( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) {
+ int i, j;
+ drawVert_t temp;
+
+ if ( width > height ) {
+ for ( i = 0 ; i < height ; i++ ) {
+ for ( j = i + 1 ; j < width ; j++ ) {
+ if ( j < height ) {
+ // swap the value
+ temp = ctrl[j][i];
+ ctrl[j][i] = ctrl[i][j];
+ ctrl[i][j] = temp;
+ } else {
+ // just copy
+ ctrl[j][i] = ctrl[i][j];
+ }
+ }
+ }
+ } else {
+ for ( i = 0 ; i < width ; i++ ) {
+ for ( j = i + 1 ; j < height ; j++ ) {
+ if ( j < width ) {
+ // swap the value
+ temp = ctrl[i][j];
+ ctrl[i][j] = ctrl[j][i];
+ ctrl[j][i] = temp;
+ } else {
+ // just copy
+ ctrl[i][j] = ctrl[j][i];
+ }
+ }
+ }
+ }
+
+}
+
+
+/*
+=================
+MakeMeshNormals
+
+Handles all the complicated wrapping and degenerate cases
+=================
+*/
+static void MakeMeshNormals( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) {
+ int i, j, k, dist;
+ vec3_t normal;
+ vec3_t sum;
+ int count = 0;
+ vec3_t base;
+ vec3_t delta;
+ int x, y;
+ drawVert_t *dv;
+ vec3_t around[8], temp;
+ qboolean good[8];
+ qboolean wrapWidth, wrapHeight;
+ float len;
+static int neighbors[8][2] = {
+ {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1}
+ };
+
+ wrapWidth = qfalse;
+ for ( i = 0 ; i < height ; i++ ) {
+ VectorSubtract( ctrl[i][0].xyz, ctrl[i][width-1].xyz, delta );
+ len = VectorLengthSquared( delta );
+ if ( len > 1.0 ) {
+ break;
+ }
+ }
+ if ( i == height ) {
+ wrapWidth = qtrue;
+ }
+
+ wrapHeight = qfalse;
+ for ( i = 0 ; i < width ; i++ ) {
+ VectorSubtract( ctrl[0][i].xyz, ctrl[height-1][i].xyz, delta );
+ len = VectorLengthSquared( delta );
+ if ( len > 1.0 ) {
+ break;
+ }
+ }
+ if ( i == width) {
+ wrapHeight = qtrue;
+ }
+
+
+ for ( i = 0 ; i < width ; i++ ) {
+ for ( j = 0 ; j < height ; j++ ) {
+ count = 0;
+ dv = &ctrl[j][i];
+ VectorCopy( dv->xyz, base );
+ for ( k = 0 ; k < 8 ; k++ ) {
+ VectorClear( around[k] );
+ good[k] = qfalse;
+
+ for ( dist = 1 ; dist <= 3 ; dist++ ) {
+ x = i + neighbors[k][0] * dist;
+ y = j + neighbors[k][1] * dist;
+ if ( wrapWidth ) {
+ if ( x < 0 ) {
+ x = width - 1 + x;
+ } else if ( x >= width ) {
+ x = 1 + x - width;
+ }
+ }
+ if ( wrapHeight ) {
+ if ( y < 0 ) {
+ y = height - 1 + y;
+ } else if ( y >= height ) {
+ y = 1 + y - height;
+ }
+ }
+
+ if ( x < 0 || x >= width || y < 0 || y >= height ) {
+ break; // edge of patch
+ }
+ VectorSubtract( ctrl[y][x].xyz, base, temp );
+ if ( VectorNormalize2( temp, temp ) == 0 ) {
+ continue; // degenerate edge, get more dist
+ } else {
+ good[k] = qtrue;
+ VectorCopy( temp, around[k] );
+ break; // good edge
+ }
+ }
+ }
+
+ VectorClear( sum );
+ for ( k = 0 ; k < 8 ; k++ ) {
+ if ( !good[k] || !good[(k+1)&7] ) {
+ continue; // didn't get two points
+ }
+ CrossProduct( around[(k+1)&7], around[k], normal );
+ if ( VectorNormalize2( normal, normal ) == 0 ) {
+ continue;
+ }
+ VectorAdd( normal, sum, sum );
+ count++;
+ }
+ if ( count == 0 ) {
+//printf("bad normal\n");
+ count = 1;
+ }
+ VectorNormalize2( sum, dv->normal );
+ }
+ }
+}
+
+
+/*
+============
+InvertCtrl
+============
+*/
+static void InvertCtrl( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) {
+ int i, j;
+ drawVert_t temp;
+
+ for ( i = 0 ; i < height ; i++ ) {
+ for ( j = 0 ; j < width/2 ; j++ ) {
+ temp = ctrl[i][j];
+ ctrl[i][j] = ctrl[i][width-1-j];
+ ctrl[i][width-1-j] = temp;
+ }
+ }
+}
+
+
+/*
+=================
+InvertErrorTable
+=================
+*/
+static void InvertErrorTable( float errorTable[2][MAX_GRID_SIZE], int width, int height ) {
+ int i;
+ float copy[2][MAX_GRID_SIZE];
+
+ Com_Memcpy( copy, errorTable, sizeof( copy ) );
+
+ for ( i = 0 ; i < width ; i++ ) {
+ errorTable[1][i] = copy[0][i]; //[width-1-i];
+ }
+
+ for ( i = 0 ; i < height ; i++ ) {
+ errorTable[0][i] = copy[1][height-1-i];
+ }
+
+}
+
+/*
+==================
+PutPointsOnCurve
+==================
+*/
+static void PutPointsOnCurve( drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE],
+ int width, int height ) {
+ int i, j;
+ drawVert_t prev, next;
+
+ for ( i = 0 ; i < width ; i++ ) {
+ for ( j = 1 ; j < height ; j += 2 ) {
+ LerpDrawVert( &ctrl[j][i], &ctrl[j+1][i], &prev );
+ LerpDrawVert( &ctrl[j][i], &ctrl[j-1][i], &next );
+ LerpDrawVert( &prev, &next, &ctrl[j][i] );
+ }
+ }
+
+
+ for ( j = 0 ; j < height ; j++ ) {
+ for ( i = 1 ; i < width ; i += 2 ) {
+ LerpDrawVert( &ctrl[j][i], &ctrl[j][i+1], &prev );
+ LerpDrawVert( &ctrl[j][i], &ctrl[j][i-1], &next );
+ LerpDrawVert( &prev, &next, &ctrl[j][i] );
+ }
+ }
+}
+
+/*
+=================
+R_CreateSurfaceGridMesh
+=================
+*/
+srfGridMesh_t *R_CreateSurfaceGridMesh(int width, int height,
+ drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], float errorTable[2][MAX_GRID_SIZE] ) {
+ int i, j, size;
+ drawVert_t *vert;
+ vec3_t tmpVec;
+ srfGridMesh_t *grid;
+
+ // copy the results out to a grid
+ size = (width * height - 1) * sizeof( drawVert_t ) + sizeof( *grid );
+
+#ifdef PATCH_STITCHING
+ grid = /*ri.Hunk_Alloc*/ ri.Malloc( size );
+ Com_Memset(grid, 0, size);
+
+ grid->widthLodError = /*ri.Hunk_Alloc*/ ri.Malloc( width * 4 );
+ Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 );
+
+ grid->heightLodError = /*ri.Hunk_Alloc*/ ri.Malloc( height * 4 );
+ Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 );
+#else
+ grid = ri.Hunk_Alloc( size );
+ Com_Memset(grid, 0, size);
+
+ grid->widthLodError = ri.Hunk_Alloc( width * 4 );
+ Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 );
+
+ grid->heightLodError = ri.Hunk_Alloc( height * 4 );
+ Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 );
+#endif
+
+ grid->width = width;
+ grid->height = height;
+ grid->surfaceType = SF_GRID;
+ ClearBounds( grid->meshBounds[0], grid->meshBounds[1] );
+ for ( i = 0 ; i < width ; i++ ) {
+ for ( j = 0 ; j < height ; j++ ) {
+ vert = &grid->verts[j*width+i];
+ *vert = ctrl[j][i];
+ AddPointToBounds( vert->xyz, grid->meshBounds[0], grid->meshBounds[1] );
+ }
+ }
+
+ // compute local origin and bounds
+ VectorAdd( grid->meshBounds[0], grid->meshBounds[1], grid->localOrigin );
+ VectorScale( grid->localOrigin, 0.5f, grid->localOrigin );
+ VectorSubtract( grid->meshBounds[0], grid->localOrigin, tmpVec );
+ grid->meshRadius = VectorLength( tmpVec );
+
+ VectorCopy( grid->localOrigin, grid->lodOrigin );
+ grid->lodRadius = grid->meshRadius;
+ //
+ return grid;
+}
+
+/*
+=================
+R_FreeSurfaceGridMesh
+=================
+*/
+void R_FreeSurfaceGridMesh( srfGridMesh_t *grid ) {
+ ri.Free(grid->widthLodError);
+ ri.Free(grid->heightLodError);
+ ri.Free(grid);
+}
+
+/*
+=================
+R_SubdividePatchToGrid
+=================
+*/
+srfGridMesh_t *R_SubdividePatchToGrid( int width, int height,
+ drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) {
+ int i, j, k, l;
+ drawVert_t_cleared( prev );
+ drawVert_t_cleared( next );
+ drawVert_t_cleared( mid );
+ float len, maxLen;
+ int dir;
+ int t;
+ drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE];
+ float errorTable[2][MAX_GRID_SIZE];
+
+ for ( i = 0 ; i < width ; i++ ) {
+ for ( j = 0 ; j < height ; j++ ) {
+ ctrl[j][i] = points[j*width+i];
+ }
+ }
+
+ for ( dir = 0 ; dir < 2 ; dir++ ) {
+
+ for ( j = 0 ; j < MAX_GRID_SIZE ; j++ ) {
+ errorTable[dir][j] = 0;
+ }
+
+ // horizontal subdivisions
+ for ( j = 0 ; j + 2 < width ; j += 2 ) {
+ // check subdivided midpoints against control points
+
+ // FIXME: also check midpoints of adjacent patches against the control points
+ // this would basically stitch all patches in the same LOD group together.
+
+ maxLen = 0;
+ for ( i = 0 ; i < height ; i++ ) {
+ vec3_t midxyz;
+ vec3_t midxyz2;
+ vec3_t dir;
+ vec3_t projected;
+ float d;
+
+ // calculate the point on the curve
+ for ( l = 0 ; l < 3 ; l++ ) {
+ midxyz[l] = (ctrl[i][j].xyz[l] + ctrl[i][j+1].xyz[l] * 2
+ + ctrl[i][j+2].xyz[l] ) * 0.25f;
+ }
+
+ // see how far off the line it is
+ // using dist-from-line will not account for internal
+ // texture warping, but it gives a lot less polygons than
+ // dist-from-midpoint
+ VectorSubtract( midxyz, ctrl[i][j].xyz, midxyz );
+ VectorSubtract( ctrl[i][j+2].xyz, ctrl[i][j].xyz, dir );
+ VectorNormalize( dir );
+
+ d = DotProduct( midxyz, dir );
+ VectorScale( dir, d, projected );
+ VectorSubtract( midxyz, projected, midxyz2);
+ len = VectorLengthSquared( midxyz2 ); // we will do the sqrt later
+ if ( len > maxLen ) {
+ maxLen = len;
+ }
+ }
+
+ maxLen = sqrt(maxLen);
+
+ // if all the points are on the lines, remove the entire columns
+ if ( maxLen < 0.1f ) {
+ errorTable[dir][j+1] = 999;
+ continue;
+ }
+
+ // see if we want to insert subdivided columns
+ if ( width + 2 > MAX_GRID_SIZE ) {
+ errorTable[dir][j+1] = 1.0f/maxLen;
+ continue; // can't subdivide any more
+ }
+
+ if ( maxLen <= r_subdivisions->value ) {
+ errorTable[dir][j+1] = 1.0f/maxLen;
+ continue; // didn't need subdivision
+ }
+
+ errorTable[dir][j+2] = 1.0f/maxLen;
+
+ // insert two columns and replace the peak
+ width += 2;
+ for ( i = 0 ; i < height ; i++ ) {
+ LerpDrawVert( &ctrl[i][j], &ctrl[i][j+1], &prev );
+ LerpDrawVert( &ctrl[i][j+1], &ctrl[i][j+2], &next );
+ LerpDrawVert( &prev, &next, &mid );
+
+ for ( k = width - 1 ; k > j + 3 ; k-- ) {
+ ctrl[i][k] = ctrl[i][k-2];
+ }
+ ctrl[i][j + 1] = prev;
+ ctrl[i][j + 2] = mid;
+ ctrl[i][j + 3] = next;
+ }
+
+ // back up and recheck this set again, it may need more subdivision
+ j -= 2;
+
+ }
+
+ Transpose( width, height, ctrl );
+ t = width;
+ width = height;
+ height = t;
+ }
+
+
+ // put all the aproximating points on the curve
+ PutPointsOnCurve( ctrl, width, height );
+
+ // cull out any rows or columns that are colinear
+ for ( i = 1 ; i < width-1 ; i++ ) {
+ if ( errorTable[0][i] != 999 ) {
+ continue;
+ }
+ for ( j = i+1 ; j < width ; j++ ) {
+ for ( k = 0 ; k < height ; k++ ) {
+ ctrl[k][j-1] = ctrl[k][j];
+ }
+ errorTable[0][j-1] = errorTable[0][j];
+ }
+ width--;
+ }
+
+ for ( i = 1 ; i < height-1 ; i++ ) {
+ if ( errorTable[1][i] != 999 ) {
+ continue;
+ }
+ for ( j = i+1 ; j < height ; j++ ) {
+ for ( k = 0 ; k < width ; k++ ) {
+ ctrl[j-1][k] = ctrl[j][k];
+ }
+ errorTable[1][j-1] = errorTable[1][j];
+ }
+ height--;
+ }
+
+#if 1
+ // flip for longest tristrips as an optimization
+ // the results should be visually identical with or
+ // without this step
+ if ( height > width ) {
+ Transpose( width, height, ctrl );
+ InvertErrorTable( errorTable, width, height );
+ t = width;
+ width = height;
+ height = t;
+ InvertCtrl( width, height, ctrl );
+ }
+#endif
+
+ // calculate normals
+ MakeMeshNormals( width, height, ctrl );
+
+ return R_CreateSurfaceGridMesh( width, height, ctrl, errorTable );
+}
+
+/*
+===============
+R_GridInsertColumn
+===============
+*/
+srfGridMesh_t *R_GridInsertColumn( srfGridMesh_t *grid, int column, int row, vec3_t point, float loderror ) {
+ int i, j;
+ int width, height, oldwidth;
+ drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE];
+ float errorTable[2][MAX_GRID_SIZE];
+ float lodRadius;
+ vec3_t lodOrigin;
+
+ oldwidth = 0;
+ width = grid->width + 1;
+ if (width > MAX_GRID_SIZE)
+ return NULL;
+ height = grid->height;
+ for (i = 0; i < width; i++) {
+ if (i == column) {
+ //insert new column
+ for (j = 0; j < grid->height; j++) {
+ LerpDrawVert( &grid->verts[j * grid->width + i-1], &grid->verts[j * grid->width + i], &ctrl[j][i] );
+ if (j == row)
+ VectorCopy(point, ctrl[j][i].xyz);
+ }
+ errorTable[0][i] = loderror;
+ continue;
+ }
+ errorTable[0][i] = grid->widthLodError[oldwidth];
+ for (j = 0; j < grid->height; j++) {
+ ctrl[j][i] = grid->verts[j * grid->width + oldwidth];
+ }
+ oldwidth++;
+ }
+ for (j = 0; j < grid->height; j++) {
+ errorTable[1][j] = grid->heightLodError[j];
+ }
+ // put all the aproximating points on the curve
+ //PutPointsOnCurve( ctrl, width, height );
+ // calculate normals
+ MakeMeshNormals( width, height, ctrl );
+
+ VectorCopy(grid->lodOrigin, lodOrigin);
+ lodRadius = grid->lodRadius;
+ // free the old grid
+ R_FreeSurfaceGridMesh(grid);
+ // create a new grid
+ grid = R_CreateSurfaceGridMesh( width, height, ctrl, errorTable );
+ grid->lodRadius = lodRadius;
+ VectorCopy(lodOrigin, grid->lodOrigin);
+ return grid;
+}
+
+/*
+===============
+R_GridInsertRow
+===============
+*/
+srfGridMesh_t *R_GridInsertRow( srfGridMesh_t *grid, int row, int column, vec3_t point, float loderror ) {
+ int i, j;
+ int width, height, oldheight;
+ drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE];
+ float errorTable[2][MAX_GRID_SIZE];
+ float lodRadius;
+ vec3_t lodOrigin;
+
+ oldheight = 0;
+ width = grid->width;
+ height = grid->height + 1;
+ if (height > MAX_GRID_SIZE)
+ return NULL;
+ for (i = 0; i < height; i++) {
+ if (i == row) {
+ //insert new row
+ for (j = 0; j < grid->width; j++) {
+ LerpDrawVert( &grid->verts[(i-1) * grid->width + j], &grid->verts[i * grid->width + j], &ctrl[i][j] );
+ if (j == column)
+ VectorCopy(point, ctrl[i][j].xyz);
+ }
+ errorTable[1][i] = loderror;
+ continue;
+ }
+ errorTable[1][i] = grid->heightLodError[oldheight];
+ for (j = 0; j < grid->width; j++) {
+ ctrl[i][j] = grid->verts[oldheight * grid->width + j];
+ }
+ oldheight++;
+ }
+ for (j = 0; j < grid->width; j++) {
+ errorTable[0][j] = grid->widthLodError[j];
+ }
+ // put all the aproximating points on the curve
+ //PutPointsOnCurve( ctrl, width, height );
+ // calculate normals
+ MakeMeshNormals( width, height, ctrl );
+
+ VectorCopy(grid->lodOrigin, lodOrigin);
+ lodRadius = grid->lodRadius;
+ // free the old grid
+ R_FreeSurfaceGridMesh(grid);
+ // create a new grid
+ grid = R_CreateSurfaceGridMesh( width, height, ctrl, errorTable );
+ grid->lodRadius = lodRadius;
+ VectorCopy(lodOrigin, grid->lodOrigin);
+ return grid;
+}
diff --git a/code/renderer/tr_flares.c b/code/renderer/tr_flares.c
new file mode 100644
index 0000000..d6590c2
--- /dev/null
+++ b/code/renderer/tr_flares.c
@@ -0,0 +1,538 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// tr_flares.c
+
+#include "tr_local.h"
+
+/*
+=============================================================================
+
+LIGHT FLARES
+
+A light flare is an effect that takes place inside the eye when bright light
+sources are visible. The size of the flare reletive to the screen is nearly
+constant, irrespective of distance, but the intensity should be proportional to the
+projected area of the light source.
+
+A surface that has been flagged as having a light flare will calculate the depth
+buffer value that it's midpoint should have when the surface is added.
+
+After all opaque surfaces have been rendered, the depth buffer is read back for
+each flare in view. If the point has not been obscured by a closer surface, the
+flare should be drawn.
+
+Surfaces that have a repeated texture should never be flagged as flaring, because
+there will only be a single flare added at the midpoint of the polygon.
+
+To prevent abrupt popping, the intensity of the flare is interpolated up and
+down as it changes visibility. This involves scene to scene state, unlike almost
+all other aspects of the renderer, and is complicated by the fact that a single
+frame may have multiple scenes.
+
+RB_RenderFlares() will be called once per view (twice in a mirrored scene, potentially
+up to five or more times in a frame with 3D status bar icons).
+
+=============================================================================
+*/
+
+
+// flare states maintain visibility over multiple frames for fading
+// layers: view, mirror, menu
+typedef struct flare_s {
+ struct flare_s *next; // for active chain
+
+ int addedFrame;
+
+ qboolean inPortal; // true if in a portal view of the scene
+ int frameSceneNum;
+ void *surface;
+ int fogNum;
+
+ int fadeTime;
+
+ qboolean visible; // state of last test
+ float drawIntensity; // may be non 0 even if !visible due to fading
+
+ int windowX, windowY;
+ float eyeZ;
+
+ vec3_t origin;
+ vec3_t color;
+} flare_t;
+
+#define MAX_FLARES 128
+
+flare_t r_flareStructs[MAX_FLARES];
+flare_t *r_activeFlares, *r_inactiveFlares;
+
+int flareCoeff;
+
+/*
+==================
+R_ClearFlares
+==================
+*/
+void R_ClearFlares( void ) {
+ int i;
+
+ Com_Memset( r_flareStructs, 0, sizeof( r_flareStructs ) );
+ r_activeFlares = NULL;
+ r_inactiveFlares = NULL;
+
+ for ( i = 0 ; i < MAX_FLARES ; i++ ) {
+ r_flareStructs[i].next = r_inactiveFlares;
+ r_inactiveFlares = &r_flareStructs[i];
+ }
+}
+
+
+/*
+==================
+RB_AddFlare
+
+This is called at surface tesselation time
+==================
+*/
+void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal ) {
+ int i;
+ flare_t *f, *oldest;
+ vec3_t local;
+ float d = 1;
+ vec4_t eye, clip, normalized, window;
+
+ backEnd.pc.c_flareAdds++;
+
+ if(normal && (normal[0] || normal[1] || normal[2]))
+ {
+ VectorSubtract( backEnd.viewParms.or.origin, point, local );
+ VectorNormalizeFast(local);
+ d = DotProduct(local, normal);
+
+ // If the viewer is behind the flare don't add it.
+ if(d < 0)
+ return;
+ }
+
+ // if the point is off the screen, don't bother adding it
+ // calculate screen coordinates and depth
+ R_TransformModelToClip( point, backEnd.or.modelMatrix,
+ backEnd.viewParms.projectionMatrix, eye, clip );
+
+ // check to see if the point is completely off screen
+ for ( i = 0 ; i < 3 ; i++ ) {
+ if ( clip[i] >= clip[3] || clip[i] <= -clip[3] ) {
+ return;
+ }
+ }
+
+ R_TransformClipToWindow( clip, &backEnd.viewParms, normalized, window );
+
+ if ( window[0] < 0 || window[0] >= backEnd.viewParms.viewportWidth
+ || window[1] < 0 || window[1] >= backEnd.viewParms.viewportHeight ) {
+ return; // shouldn't happen, since we check the clip[] above, except for FP rounding
+ }
+
+ // see if a flare with a matching surface, scene, and view exists
+ oldest = r_flareStructs;
+ for ( f = r_activeFlares ; f ; f = f->next ) {
+ if ( f->surface == surface && f->frameSceneNum == backEnd.viewParms.frameSceneNum
+ && f->inPortal == backEnd.viewParms.isPortal ) {
+ break;
+ }
+ }
+
+ // allocate a new one
+ if (!f ) {
+ if ( !r_inactiveFlares ) {
+ // the list is completely full
+ return;
+ }
+ f = r_inactiveFlares;
+ r_inactiveFlares = r_inactiveFlares->next;
+ f->next = r_activeFlares;
+ r_activeFlares = f;
+
+ f->surface = surface;
+ f->frameSceneNum = backEnd.viewParms.frameSceneNum;
+ f->inPortal = backEnd.viewParms.isPortal;
+ f->addedFrame = -1;
+ }
+
+ if ( f->addedFrame != backEnd.viewParms.frameCount - 1 ) {
+ f->visible = qfalse;
+ f->fadeTime = backEnd.refdef.time - 2000;
+ }
+
+ f->addedFrame = backEnd.viewParms.frameCount;
+ f->fogNum = fogNum;
+
+ VectorCopy(point, f->origin);
+ VectorCopy( color, f->color );
+
+ // fade the intensity of the flare down as the
+ // light surface turns away from the viewer
+ VectorScale( f->color, d, f->color );
+
+ // save info needed to test
+ f->windowX = backEnd.viewParms.viewportX + window[0];
+ f->windowY = backEnd.viewParms.viewportY + window[1];
+
+ f->eyeZ = eye[2];
+}
+
+/*
+==================
+RB_AddDlightFlares
+==================
+*/
+void RB_AddDlightFlares( void ) {
+ dlight_t *l;
+ int i, j, k;
+ fog_t *fog = NULL;
+
+ if ( !r_flares->integer ) {
+ return;
+ }
+
+ l = backEnd.refdef.dlights;
+
+ if(tr.world)
+ fog = tr.world->fogs;
+
+ for (i=0 ; i<backEnd.refdef.num_dlights ; i++, l++) {
+
+ if(fog)
+ {
+ // find which fog volume the light is in
+ for ( j = 1 ; j < tr.world->numfogs ; j++ ) {
+ fog = &tr.world->fogs[j];
+ for ( k = 0 ; k < 3 ; k++ ) {
+ if ( l->origin[k] < fog->bounds[0][k] || l->origin[k] > fog->bounds[1][k] ) {
+ break;
+ }
+ }
+ if ( k == 3 ) {
+ break;
+ }
+ }
+ if ( j == tr.world->numfogs ) {
+ j = 0;
+ }
+ }
+ else
+ j = 0;
+
+ RB_AddFlare( (void *)l, j, l->origin, l->color, NULL );
+ }
+}
+
+/*
+===============================================================================
+
+FLARE BACK END
+
+===============================================================================
+*/
+
+#ifndef PANDORA
+
+/*
+==================
+RB_TestFlare
+==================
+*/
+void RB_TestFlare( flare_t *f ) {
+ float depth;
+ qboolean visible;
+ float fade;
+ float screenZ;
+
+ backEnd.pc.c_flareTests++;
+
+ // doing a readpixels is as good as doing a glFinish(), so
+ // don't bother with another sync
+ glState.finishCalled = qfalse;
+
+ // read back the z buffer contents
+ qglReadPixels( f->windowX, f->windowY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth );
+
+ screenZ = backEnd.viewParms.projectionMatrix[14] /
+ ( ( 2*depth - 1 ) * backEnd.viewParms.projectionMatrix[11] - backEnd.viewParms.projectionMatrix[10] );
+
+ visible = ( -f->eyeZ - -screenZ ) < 24;
+
+ if ( visible ) {
+ if ( !f->visible ) {
+ f->visible = qtrue;
+ f->fadeTime = backEnd.refdef.time - 1;
+ }
+ fade = ( ( backEnd.refdef.time - f->fadeTime ) /1000.0f ) * r_flareFade->value;
+ } else {
+ if ( f->visible ) {
+ f->visible = qfalse;
+ f->fadeTime = backEnd.refdef.time - 1;
+ }
+ fade = 1.0f - ( ( backEnd.refdef.time - f->fadeTime ) / 1000.0f ) * r_flareFade->value;
+ }
+
+ if ( fade < 0 ) {
+ fade = 0;
+ }
+ if ( fade > 1 ) {
+ fade = 1;
+ }
+
+ f->drawIntensity = fade;
+}
+
+
+/*
+==================
+RB_RenderFlare
+==================
+*/
+void RB_RenderFlare( flare_t *f ) {
+ float size;
+ vec3_t color;
+ int iColor[3];
+ float distance, intensity, factor;
+ byte fogFactors[3] = {255, 255, 255};
+
+ backEnd.pc.c_flareRenders++;
+
+ // We don't want too big values anyways when dividing by distance.
+ if(f->eyeZ > -1.0f)
+ distance = 1.0f;
+ else
+ distance = -f->eyeZ;
+
+ // calculate the flare size..
+ size = backEnd.viewParms.viewportWidth * ( r_flareSize->value/640.0f + 8 / distance );
+
+/*
+ * This is an alternative to intensity scaling. It changes the size of the flare on screen instead
+ * with growing distance. See in the description at the top why this is not the way to go.
+ // size will change ~ 1/r.
+ size = backEnd.viewParms.viewportWidth * (r_flareSize->value / (distance * -2.0f));
+*/
+
+/*
+ * As flare sizes stay nearly constant with increasing distance we must decrease the intensity
+ * to achieve a reasonable visual result. The intensity is ~ (size^2 / distance^2) which can be
+ * got by considering the ratio of
+ * (flaresurface on screen) : (Surface of sphere defined by flare origin and distance from flare)
+ * An important requirement is:
+ * intensity <= 1 for all distances.
+ *
+ * The formula used here to compute the intensity is as follows:
+ * intensity = flareCoeff * size^2 / (distance + size*sqrt(flareCoeff))^2
+ * As you can see, the intensity will have a max. of 1 when the distance is 0.
+ * The coefficient flareCoeff will determine the falloff speed with increasing distance.
+ */
+
+ factor = distance + size * sqrt(flareCoeff);
+
+ intensity = flareCoeff * size * size / (factor * factor);
+
+ VectorScale(f->color, f->drawIntensity * intensity, color);
+
+// Calculations for fogging
+ if(tr.world && f->fogNum < tr.world->numfogs)
+ {
+ tess.numVertexes = 1;
+ VectorCopy(f->origin, tess.xyz[0]);
+ tess.fogNum = f->fogNum;
+
+ RB_CalcModulateColorsByFog(fogFactors);
+
+ // We don't need to render the flare if colors are 0 anyways.
+ if(!(fogFactors[0] || fogFactors[1] || fogFactors[2]))
+ return;
+ }
+
+ iColor[0] = color[0] * fogFactors[0];
+ iColor[1] = color[1] * fogFactors[1];
+ iColor[2] = color[2] * fogFactors[2];
+
+ RB_BeginSurface( tr.flareShader, f->fogNum );
+
+ // FIXME: use quadstamp?
+ tess.xyz[tess.numVertexes][0] = f->windowX - size;
+ tess.xyz[tess.numVertexes][1] = f->windowY - size;
+ tess.texCoords[tess.numVertexes][0][0] = 0;
+ tess.texCoords[tess.numVertexes][0][1] = 0;
+ tess.vertexColors[tess.numVertexes][0] = iColor[0];
+ tess.vertexColors[tess.numVertexes][1] = iColor[1];
+ tess.vertexColors[tess.numVertexes][2] = iColor[2];
+ tess.vertexColors[tess.numVertexes][3] = 255;
+ tess.numVertexes++;
+
+ tess.xyz[tess.numVertexes][0] = f->windowX - size;
+ tess.xyz[tess.numVertexes][1] = f->windowY + size;
+ tess.texCoords[tess.numVertexes][0][0] = 0;
+ tess.texCoords[tess.numVertexes][0][1] = 1;
+ tess.vertexColors[tess.numVertexes][0] = iColor[0];
+ tess.vertexColors[tess.numVertexes][1] = iColor[1];
+ tess.vertexColors[tess.numVertexes][2] = iColor[2];
+ tess.vertexColors[tess.numVertexes][3] = 255;
+ tess.numVertexes++;
+
+ tess.xyz[tess.numVertexes][0] = f->windowX + size;
+ tess.xyz[tess.numVertexes][1] = f->windowY + size;
+ tess.texCoords[tess.numVertexes][0][0] = 1;
+ tess.texCoords[tess.numVertexes][0][1] = 1;
+ tess.vertexColors[tess.numVertexes][0] = iColor[0];
+ tess.vertexColors[tess.numVertexes][1] = iColor[1];
+ tess.vertexColors[tess.numVertexes][2] = iColor[2];
+ tess.vertexColors[tess.numVertexes][3] = 255;
+ tess.numVertexes++;
+
+ tess.xyz[tess.numVertexes][0] = f->windowX + size;
+ tess.xyz[tess.numVertexes][1] = f->windowY - size;
+ tess.texCoords[tess.numVertexes][0][0] = 1;
+ tess.texCoords[tess.numVertexes][0][1] = 0;
+ tess.vertexColors[tess.numVertexes][0] = iColor[0];
+ tess.vertexColors[tess.numVertexes][1] = iColor[1];
+ tess.vertexColors[tess.numVertexes][2] = iColor[2];
+ tess.vertexColors[tess.numVertexes][3] = 255;
+ tess.numVertexes++;
+
+ tess.indexes[tess.numIndexes++] = 0;
+ tess.indexes[tess.numIndexes++] = 1;
+ tess.indexes[tess.numIndexes++] = 2;
+ tess.indexes[tess.numIndexes++] = 0;
+ tess.indexes[tess.numIndexes++] = 2;
+ tess.indexes[tess.numIndexes++] = 3;
+
+ RB_EndSurface();
+}
+
+/*
+==================
+RB_RenderFlares
+
+Because flares are simulating an occular effect, they should be drawn after
+everything (all views) in the entire frame has been drawn.
+
+Because of the way portals use the depth buffer to mark off areas, the
+needed information would be lost after each view, so we are forced to draw
+flares after each view.
+
+The resulting artifact is that flares in mirrors or portals don't dim properly
+when occluded by something in the main view, and portal flares that should
+extend past the portal edge will be overwritten.
+==================
+*/
+void RB_RenderFlares (void) {
+ flare_t *f;
+ flare_t **prev;
+ qboolean draw;
+
+ if ( !r_flares->integer ) {
+ return;
+ }
+
+ if(r_flareCoeff->modified)
+ {
+ if(r_flareCoeff->value == 0.0f)
+ flareCoeff = atof(FLARE_STDCOEFF);
+ else
+ flareCoeff = r_flareCoeff->value;
+
+ r_flareCoeff->modified = qfalse;
+ }
+
+ // Reset currentEntity to world so that any previously referenced entities
+ // don't have influence on the rendering of these flares (i.e. RF_ renderer flags).
+ backEnd.currentEntity = &tr.worldEntity;
+ backEnd.or = backEnd.viewParms.world;
+
+// RB_AddDlightFlares();
+
+ // perform z buffer readback on each flare in this view
+ draw = qfalse;
+ prev = &r_activeFlares;
+ while ( ( f = *prev ) != NULL ) {
+ // throw out any flares that weren't added last frame
+ if ( f->addedFrame < backEnd.viewParms.frameCount - 1 ) {
+ *prev = f->next;
+ f->next = r_inactiveFlares;
+ r_inactiveFlares = f;
+ continue;
+ }
+
+ // don't draw any here that aren't from this scene / portal
+ f->drawIntensity = 0;
+ if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum
+ && f->inPortal == backEnd.viewParms.isPortal ) {
+ RB_TestFlare( f );
+ if ( f->drawIntensity ) {
+ draw = qtrue;
+ } else {
+ // this flare has completely faded out, so remove it from the chain
+ *prev = f->next;
+ f->next = r_inactiveFlares;
+ r_inactiveFlares = f;
+ continue;
+ }
+ }
+
+ prev = &f->next;
+ }
+
+ if ( !draw ) {
+ return; // none visible
+ }
+
+ if ( backEnd.viewParms.isPortal ) {
+ qglDisable (GL_CLIP_PLANE0);
+ }
+
+ qglPushMatrix();
+ qglLoadIdentity();
+ qglMatrixMode( GL_PROJECTION );
+ qglPushMatrix();
+ qglLoadIdentity();
+ qglOrtho( backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth,
+ backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight,
+ -99999, 99999 );
+
+ for ( f = r_activeFlares ; f ; f = f->next ) {
+ if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum
+ && f->inPortal == backEnd.viewParms.isPortal
+ && f->drawIntensity ) {
+ RB_RenderFlare( f );
+ }
+ }
+
+ qglPopMatrix();
+ qglMatrixMode( GL_MODELVIEW );
+ qglPopMatrix();
+}
+
+#else
+void RB_RenderFlares(void)
+{
+}
+#endif
+
+
diff --git a/code/renderer/tr_font.c b/code/renderer/tr_font.c
new file mode 100644
index 0000000..bdb4251
--- /dev/null
+++ b/code/renderer/tr_font.c
@@ -0,0 +1,549 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// tr_font.c
+//
+//
+// The font system uses FreeType 2.x to render TrueType fonts for use within the game.
+// As of this writing ( Nov, 2000 ) Team Arena uses these fonts for all of the ui and
+// about 90% of the cgame presentation. A few areas of the CGAME were left uses the old
+// fonts since the code is shared with standard Q3A.
+//
+// If you include this font rendering code in a commercial product you MUST include the
+// following somewhere with your product, see www.freetype.org for specifics or changes.
+// The Freetype code also uses some hinting techniques that MIGHT infringe on patents
+// held by apple so be aware of that also.
+//
+// As of Q3A 1.25+ and Team Arena, we are shipping the game with the font rendering code
+// disabled. This removes any potential patent issues and it keeps us from having to
+// distribute an actual TrueTrype font which is 1. expensive to do and 2. seems to require
+// an act of god to accomplish.
+//
+// What we did was pre-render the fonts using FreeType ( which is why we leave the FreeType
+// credit in the credits ) and then saved off the glyph data and then hand touched up the
+// font bitmaps so they scale a bit better in GL.
+//
+// There are limitations in the way fonts are saved and reloaded in that it is based on
+// point size and not name. So if you pre-render Helvetica in 18 point and Impact in 18 point
+// you will end up with a single 18 point data file and image set. Typically you will want to
+// choose 3 sizes to best approximate the scaling you will be doing in the ui scripting system
+//
+// In the UI Scripting code, a scale of 1.0 is equal to a 48 point font. In Team Arena, we
+// use three or four scales, most of them exactly equaling the specific rendered size. We
+// rendered three sizes in Team Arena, 12, 16, and 20.
+//
+// To generate new font data you need to go through the following steps.
+// 1. delete the fontImage_x_xx.tga files and fontImage_xx.dat files from the fonts path.
+// 2. in a ui script, specificy a font, smallFont, and bigFont keyword with font name and
+// point size. the original TrueType fonts must exist in fonts at this point.
+// 3. run the game, you should see things normally.
+// 4. Exit the game and there will be three dat files and at least three tga files. The
+// tga's are in 256x256 pages so if it takes three images to render a 24 point font you
+// will end up with fontImage_0_24.tga through fontImage_2_24.tga
+// 5. You will need to flip the tga's in Photoshop as the tga output code writes them upside
+// down.
+// 6. In future runs of the game, the system looks for these images and data files when a s
+// specific point sized font is rendered and loads them for use.
+// 7. Because of the original beta nature of the FreeType code you will probably want to hand
+// touch the font bitmaps.
+//
+// Currently a define in the project turns on or off the FreeType code which is currently
+// defined out. To pre-render new fonts you need enable the define ( BUILD_FREETYPE ) and
+// uncheck the exclude from build check box in the FreeType2 area of the Renderer project.
+
+
+#include "tr_local.h"
+#include "../qcommon/qcommon.h"
+
+#ifdef BUILD_FREETYPE
+#include <ft2build.h>
+#include <freetype/fterrors.h>
+#include <freetype/ftsystem.h>
+#include <freetype/ftimage.h>
+#include <freetype/freetype.h>
+#include <freetype/ftoutln.h>
+
+#define _FLOOR(x) ((x) & -64)
+#define _CEIL(x) (((x)+63) & -64)
+#define _TRUNC(x) ((x) >> 6)
+
+FT_Library ftLibrary = NULL;
+#endif
+
+#define MAX_FONTS 6
+static int registeredFontCount = 0;
+static fontInfo_t registeredFont[MAX_FONTS];
+
+#ifdef BUILD_FREETYPE
+void R_GetGlyphInfo(FT_GlyphSlot glyph, int *left, int *right, int *width, int *top, int *bottom, int *height, int *pitch) {
+
+ *left = _FLOOR( glyph->metrics.horiBearingX );
+ *right = _CEIL( glyph->metrics.horiBearingX + glyph->metrics.width );
+ *width = _TRUNC(*right - *left);
+
+ *top = _CEIL( glyph->metrics.horiBearingY );
+ *bottom = _FLOOR( glyph->metrics.horiBearingY - glyph->metrics.height );
+ *height = _TRUNC( *top - *bottom );
+ *pitch = ( qtrue ? (*width+3) & -4 : (*width+7) >> 3 );
+}
+
+
+FT_Bitmap *R_RenderGlyph(FT_GlyphSlot glyph, glyphInfo_t* glyphOut) {
+
+ FT_Bitmap *bit2;
+ int left, right, width, top, bottom, height, pitch, size;
+
+ R_GetGlyphInfo(glyph, &left, &right, &width, &top, &bottom, &height, &pitch);
+
+ if ( glyph->format == ft_glyph_format_outline ) {
+ size = pitch*height;
+
+ bit2 = Z_Malloc(sizeof(FT_Bitmap));
+
+ bit2->width = width;
+ bit2->rows = height;
+ bit2->pitch = pitch;
+ bit2->pixel_mode = ft_pixel_mode_grays;
+ //bit2->pixel_mode = ft_pixel_mode_mono;
+ bit2->buffer = Z_Malloc(pitch*height);
+ bit2->num_grays = 256;
+
+ Com_Memset( bit2->buffer, 0, size );
+
+ FT_Outline_Translate( &glyph->outline, -left, -bottom );
+
+ FT_Outline_Get_Bitmap( ftLibrary, &glyph->outline, bit2 );
+
+ glyphOut->height = height;
+ glyphOut->pitch = pitch;
+ glyphOut->top = (glyph->metrics.horiBearingY >> 6) + 1;
+ glyphOut->bottom = bottom;
+
+ return bit2;
+ }
+ else {
+ ri.Printf(PRINT_ALL, "Non-outline fonts are not supported\n");
+ }
+ return NULL;
+}
+
+void WriteTGA (char *filename, byte *data, int width, int height) {
+ byte *buffer;
+ int i, c;
+
+ buffer = Z_Malloc(width*height*4 + 18);
+ Com_Memset (buffer, 0, 18);
+ buffer[2] = 2; // uncompressed type
+ buffer[12] = width&255;
+ buffer[13] = width>>8;
+ buffer[14] = height&255;
+ buffer[15] = height>>8;
+ buffer[16] = 32; // pixel size
+
+ // swap rgb to bgr
+ c = 18 + width * height * 4;
+ for (i=18 ; i<c ; i+=4)
+ {
+ buffer[i] = data[i-18+2]; // blue
+ buffer[i+1] = data[i-18+1]; // green
+ buffer[i+2] = data[i-18+0]; // red
+ buffer[i+3] = data[i-18+3]; // alpha
+ }
+
+ ri.FS_WriteFile(filename, buffer, c);
+
+ //f = fopen (filename, "wb");
+ //fwrite (buffer, 1, c, f);
+ //fclose (f);
+
+ Z_Free (buffer);
+}
+
+static glyphInfo_t *RE_ConstructGlyphInfo(unsigned char *imageOut, int *xOut, int *yOut, int *maxHeight, FT_Face face, const unsigned char c, qboolean calcHeight) {
+ int i;
+ static glyphInfo_t glyph;
+ unsigned char *src, *dst;
+ float scaled_width, scaled_height;
+ FT_Bitmap *bitmap = NULL;
+
+ Com_Memset(&glyph, 0, sizeof(glyphInfo_t));
+ // make sure everything is here
+ if (face != NULL) {
+ FT_Load_Glyph(face, FT_Get_Char_Index( face, c), FT_LOAD_DEFAULT );
+ bitmap = R_RenderGlyph(face->glyph, &glyph);
+ if (bitmap) {
+ glyph.xSkip = (face->glyph->metrics.horiAdvance >> 6) + 1;
+ } else {
+ return &glyph;
+ }
+
+ if (glyph.height > *maxHeight) {
+ *maxHeight = glyph.height;
+ }
+
+ if (calcHeight) {
+ Z_Free(bitmap->buffer);
+ Z_Free(bitmap);
+ return &glyph;
+ }
+
+/*
+ // need to convert to power of 2 sizes so we do not get
+ // any scaling from the gl upload
+ for (scaled_width = 1 ; scaled_width < glyph.pitch ; scaled_width<<=1)
+ ;
+ for (scaled_height = 1 ; scaled_height < glyph.height ; scaled_height<<=1)
+ ;
+*/
+
+ scaled_width = glyph.pitch;
+ scaled_height = glyph.height;
+
+ // we need to make sure we fit
+ if (*xOut + scaled_width + 1 >= 255) {
+ if (*yOut + *maxHeight + 1 >= 255) {
+ *yOut = -1;
+ *xOut = -1;
+ Z_Free(bitmap->buffer);
+ Z_Free(bitmap);
+ return &glyph;
+ } else {
+ *xOut = 0;
+ *yOut += *maxHeight + 1;
+ }
+ } else if (*yOut + *maxHeight + 1 >= 255) {
+ *yOut = -1;
+ *xOut = -1;
+ Z_Free(bitmap->buffer);
+ Z_Free(bitmap);
+ return &glyph;
+ }
+
+
+ src = bitmap->buffer;
+ dst = imageOut + (*yOut * 256) + *xOut;
+
+ if (bitmap->pixel_mode == ft_pixel_mode_mono) {
+ for (i = 0; i < glyph.height; i++) {
+ int j;
+ unsigned char *_src = src;
+ unsigned char *_dst = dst;
+ unsigned char mask = 0x80;
+ unsigned char val = *_src;
+ for (j = 0; j < glyph.pitch; j++) {
+ if (mask == 0x80) {
+ val = *_src++;
+ }
+ if (val & mask) {
+ *_dst = 0xff;
+ }
+ mask >>= 1;
+
+ if ( mask == 0 ) {
+ mask = 0x80;
+ }
+ _dst++;
+ }
+
+ src += glyph.pitch;
+ dst += 256;
+
+ }
+ } else {
+ for (i = 0; i < glyph.height; i++) {
+ Com_Memcpy(dst, src, glyph.pitch);
+ src += glyph.pitch;
+ dst += 256;
+ }
+ }
+
+ // we now have an 8 bit per pixel grey scale bitmap
+ // that is width wide and pf->ftSize->metrics.y_ppem tall
+
+ glyph.imageHeight = scaled_height;
+ glyph.imageWidth = scaled_width;
+ glyph.s = (float)*xOut / 256;
+ glyph.t = (float)*yOut / 256;
+ glyph.s2 = glyph.s + (float)scaled_width / 256;
+ glyph.t2 = glyph.t + (float)scaled_height / 256;
+
+ *xOut += scaled_width + 1;
+ }
+
+ Z_Free(bitmap->buffer);
+ Z_Free(bitmap);
+
+ return &glyph;
+}
+#endif
+
+static int fdOffset;
+static byte *fdFile;
+
+int readInt( void ) {
+ int i = fdFile[fdOffset]+(fdFile[fdOffset+1]<<8)+(fdFile[fdOffset+2]<<16)+(fdFile[fdOffset+3]<<24);
+ fdOffset += 4;
+ return i;
+}
+
+typedef union {
+ byte fred[4];
+ float ffred;
+} poor;
+
+float readFloat( void ) {
+ poor me;
+#if defined Q3_BIG_ENDIAN
+ me.fred[0] = fdFile[fdOffset+3];
+ me.fred[1] = fdFile[fdOffset+2];
+ me.fred[2] = fdFile[fdOffset+1];
+ me.fred[3] = fdFile[fdOffset+0];
+#elif defined Q3_LITTLE_ENDIAN
+ me.fred[0] = fdFile[fdOffset+0];
+ me.fred[1] = fdFile[fdOffset+1];
+ me.fred[2] = fdFile[fdOffset+2];
+ me.fred[3] = fdFile[fdOffset+3];
+#endif
+ fdOffset += 4;
+ return me.ffred;
+}
+
+void RE_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font) {
+#ifdef BUILD_FREETYPE
+ FT_Face face;
+ int j, k, xOut, yOut, lastStart, imageNumber;
+ int scaledSize, newSize, maxHeight, left, satLevels;
+ unsigned char *out, *imageBuff;
+ glyphInfo_t *glyph;
+ image_t *image;
+ qhandle_t h;
+ float max;
+#endif
+ void *faceData;
+ int i, len;
+ char name[1024];
+ float dpi = 72; //
+ float glyphScale = 72.0f / dpi; // change the scale to be relative to 1 based on 72 dpi ( so dpi of 144 means a scale of .5 )
+
+
+ if (!fontName) {
+ ri.Printf(PRINT_ALL, "RE_RegisterFont: called with empty name\n");
+ return;
+ }
+
+ if (pointSize <= 0) {
+ pointSize = 12;
+ }
+ // we also need to adjust the scale based on point size relative to 48 points as the ui scaling is based on a 48 point font
+ glyphScale *= 48.0f / pointSize;
+
+ // make sure the render thread is stopped
+ R_SyncRenderThread();
+
+ if (registeredFontCount >= MAX_FONTS) {
+ ri.Printf(PRINT_ALL, "RE_RegisterFont: Too many fonts registered already.\n");
+ return;
+ }
+
+ Com_sprintf(name, sizeof(name), "fonts/fontImage_%i.dat",pointSize);
+ for (i = 0; i < registeredFontCount; i++) {
+ if (Q_stricmp(name, registeredFont[i].name) == 0) {
+ Com_Memcpy(font, &registeredFont[i], sizeof(fontInfo_t));
+ return;
+ }
+ }
+
+ len = ri.FS_ReadFile(name, NULL);
+ if (len == sizeof(fontInfo_t)) {
+ ri.FS_ReadFile(name, &faceData);
+ fdOffset = 0;
+ fdFile = faceData;
+ for(i=0; i<GLYPHS_PER_FONT; i++) {
+ font->glyphs[i].height = readInt();
+ font->glyphs[i].top = readInt();
+ font->glyphs[i].bottom = readInt();
+ font->glyphs[i].pitch = readInt();
+ font->glyphs[i].xSkip = readInt();
+ font->glyphs[i].imageWidth = readInt();
+ font->glyphs[i].imageHeight = readInt();
+ font->glyphs[i].s = readFloat();
+ font->glyphs[i].t = readFloat();
+ font->glyphs[i].s2 = readFloat();
+ font->glyphs[i].t2 = readFloat();
+ font->glyphs[i].glyph = readInt();
+ Com_Memcpy(font->glyphs[i].shaderName, &fdFile[fdOffset], 32);
+ fdOffset += 32;
+ }
+ font->glyphScale = readFloat();
+ Com_Memcpy(font->name, &fdFile[fdOffset], MAX_QPATH);
+
+// Com_Memcpy(font, faceData, sizeof(fontInfo_t));
+ Q_strncpyz(font->name, name, sizeof(font->name));
+ for (i = GLYPH_START; i < GLYPH_END; i++) {
+ font->glyphs[i].glyph = RE_RegisterShaderNoMip(font->glyphs[i].shaderName);
+ }
+ Com_Memcpy(&registeredFont[registeredFontCount++], font, sizeof(fontInfo_t));
+ return;
+ }
+
+#ifndef BUILD_FREETYPE
+ ri.Printf(PRINT_ALL, "RE_RegisterFont: FreeType code not available\n");
+#else
+ if (ftLibrary == NULL) {
+ ri.Printf(PRINT_ALL, "RE_RegisterFont: FreeType not initialized.\n");
+ return;
+ }
+
+ len = ri.FS_ReadFile(fontName, &faceData);
+ if (len <= 0) {
+ ri.Printf(PRINT_ALL, "RE_RegisterFont: Unable to read font file\n");
+ return;
+ }
+
+ // allocate on the stack first in case we fail
+ if (FT_New_Memory_Face( ftLibrary, faceData, len, 0, &face )) {
+ ri.Printf(PRINT_ALL, "RE_RegisterFont: FreeType2, unable to allocate new face.\n");
+ return;
+ }
+
+
+ if (FT_Set_Char_Size( face, pointSize << 6, pointSize << 6, dpi, dpi)) {
+ ri.Printf(PRINT_ALL, "RE_RegisterFont: FreeType2, Unable to set face char size.\n");
+ return;
+ }
+
+ //*font = &registeredFonts[registeredFontCount++];
+
+ // make a 256x256 image buffer, once it is full, register it, clean it and keep going
+ // until all glyphs are rendered
+
+ out = Z_Malloc(1024*1024);
+ if (out == NULL) {
+ ri.Printf(PRINT_ALL, "RE_RegisterFont: Z_Malloc failure during output image creation.\n");
+ return;
+ }
+ Com_Memset(out, 0, 1024*1024);
+
+ maxHeight = 0;
+
+ for (i = GLYPH_START; i < GLYPH_END; i++) {
+ glyph = RE_ConstructGlyphInfo(out, &xOut, &yOut, &maxHeight, face, (unsigned char)i, qtrue);
+ }
+
+ xOut = 0;
+ yOut = 0;
+ i = GLYPH_START;
+ lastStart = i;
+ imageNumber = 0;
+
+ while ( i <= GLYPH_END ) {
+
+ glyph = RE_ConstructGlyphInfo(out, &xOut, &yOut, &maxHeight, face, (unsigned char)i, qfalse);
+
+ if (xOut == -1 || yOut == -1 || i == GLYPH_END) {
+ // ran out of room
+ // we need to create an image from the bitmap, set all the handles in the glyphs to this point
+ //
+
+ scaledSize = 256*256;
+ newSize = scaledSize * 4;
+ imageBuff = Z_Malloc(newSize);
+ left = 0;
+ max = 0;
+ satLevels = 255;
+ for ( k = 0; k < (scaledSize) ; k++ ) {
+ if (max < out[k]) {
+ max = out[k];
+ }
+ }
+
+ if (max > 0) {
+ max = 255/max;
+ }
+
+ for ( k = 0; k < (scaledSize) ; k++ ) {
+ imageBuff[left++] = 255;
+ imageBuff[left++] = 255;
+ imageBuff[left++] = 255;
+
+ imageBuff[left++] = ((float)out[k] * max);
+ }
+
+ Com_sprintf (name, sizeof(name), "fonts/fontImage_%i_%i.tga", imageNumber++, pointSize);
+ if (r_saveFontData->integer) {
+ WriteTGA(name, imageBuff, 256, 256);
+ }
+
+ //Com_sprintf (name, sizeof(name), "fonts/fontImage_%i_%i", imageNumber++, pointSize);
+ image = R_CreateImage(name, imageBuff, 256, 256, qfalse, qfalse, GL_CLAMP_TO_EDGE);
+ h = RE_RegisterShaderFromImage(name, LIGHTMAP_2D, image, qfalse);
+ for (j = lastStart; j < i; j++) {
+ font->glyphs[j].glyph = h;
+ Q_strncpyz(font->glyphs[j].shaderName, name, sizeof(font->glyphs[j].shaderName));
+ }
+ lastStart = i;
+ Com_Memset(out, 0, 1024*1024);
+ xOut = 0;
+ yOut = 0;
+ Z_Free(imageBuff);
+ i++;
+ } else {
+ Com_Memcpy(&font->glyphs[i], glyph, sizeof(glyphInfo_t));
+ i++;
+ }
+ }
+
+ registeredFont[registeredFontCount].glyphScale = glyphScale;
+ font->glyphScale = glyphScale;
+ Com_Memcpy(&registeredFont[registeredFontCount++], font, sizeof(fontInfo_t));
+
+ if (r_saveFontData->integer) {
+ ri.FS_WriteFile(va("fonts/fontImage_%i.dat", pointSize), font, sizeof(fontInfo_t));
+ }
+
+ Z_Free(out);
+
+ ri.FS_FreeFile(faceData);
+#endif
+}
+
+
+
+void R_InitFreeType(void) {
+#ifdef BUILD_FREETYPE
+ if (FT_Init_FreeType( &ftLibrary )) {
+ ri.Printf(PRINT_ALL, "R_InitFreeType: Unable to initialize FreeType.\n");
+ }
+#endif
+ registeredFontCount = 0;
+}
+
+
+void R_DoneFreeType(void) {
+#ifdef BUILD_FREETYPE
+ if (ftLibrary) {
+ FT_Done_FreeType( ftLibrary );
+ ftLibrary = NULL;
+ }
+#endif
+ registeredFontCount = 0;
+}
+
diff --git a/code/renderer/tr_image.c b/code/renderer/tr_image.c
new file mode 100644
index 0000000..be80fcb
--- /dev/null
+++ b/code/renderer/tr_image.c
@@ -0,0 +1,1606 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// tr_image.c
+#include "tr_local.h"
+
+static byte s_intensitytable[256];
+static unsigned char s_gammatable[256];
+
+int gl_filter_min = GL_LINEAR_MIPMAP_NEAREST;
+int gl_filter_max = GL_LINEAR;
+
+#define FILE_HASH_SIZE 1024
+static image_t* hashTable[FILE_HASH_SIZE];
+
+/*
+** R_GammaCorrect
+*/
+void R_GammaCorrect( byte *buffer, int bufSize ) {
+ int i;
+
+ for ( i = 0; i < bufSize; i++ ) {
+ buffer[i] = s_gammatable[buffer[i]];
+ }
+}
+
+typedef struct {
+ char *name;
+ int minimize, maximize;
+} textureMode_t;
+
+textureMode_t modes[] = {
+ {"GL_NEAREST", GL_NEAREST, GL_NEAREST},
+ {"GL_LINEAR", GL_LINEAR, GL_LINEAR},
+ {"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST},
+ {"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR},
+ {"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST},
+ {"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR}
+};
+
+/*
+================
+return a hash value for the filename
+================
+*/
+static long generateHashValue( const char *fname ) {
+ int i;
+ long hash;
+ char letter;
+
+ hash = 0;
+ i = 0;
+ while (fname[i] != '\0') {
+ letter = tolower(fname[i]);
+ if (letter =='.') break; // don't include extension
+ if (letter =='\\') letter = '/'; // damn path names
+ hash+=(long)(letter)*(i+119);
+ i++;
+ }
+ hash &= (FILE_HASH_SIZE-1);
+ return hash;
+}
+
+/*
+===============
+GL_TextureMode
+===============
+*/
+void GL_TextureMode( const char *string ) {
+ int i;
+ image_t *glt;
+
+ for ( i=0 ; i< 6 ; i++ ) {
+ if ( !Q_stricmp( modes[i].name, string ) ) {
+ break;
+ }
+ }
+
+ // hack to prevent trilinear from being set on voodoo,
+ // because their driver freaks...
+ if ( i == 5 && glConfig.hardwareType == GLHW_3DFX_2D3D ) {
+ ri.Printf( PRINT_ALL, "Refusing to set trilinear on a voodoo.\n" );
+ i = 3;
+ }
+
+
+ if ( i == 6 ) {
+ ri.Printf (PRINT_ALL, "bad filter name\n");
+ return;
+ }
+
+ gl_filter_min = modes[i].minimize;
+ gl_filter_max = modes[i].maximize;
+
+ // change all the existing mipmap texture objects
+ for ( i = 0 ; i < tr.numImages ; i++ ) {
+ glt = tr.images[ i ];
+ if ( glt->mipmap ) {
+ GL_Bind (glt);
+ qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
+ qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
+ }
+ }
+}
+
+/*
+===============
+R_SumOfUsedImages
+===============
+*/
+int R_SumOfUsedImages( void ) {
+ int total;
+ int i;
+
+ total = 0;
+ for ( i = 0; i < tr.numImages; i++ ) {
+ if ( tr.images[i]->frameUsed == tr.frameCount ) {
+ total += tr.images[i]->uploadWidth * tr.images[i]->uploadHeight;
+ }
+ }
+
+ return total;
+}
+
+/*
+===============
+R_ImageList_f
+===============
+*/
+void R_ImageList_f( void ) {
+ int i;
+ image_t *image;
+ int texels;
+ const char *yesno[] = {
+ "no ", "yes"
+ };
+
+ ri.Printf (PRINT_ALL, "\n -w-- -h-- -mm- -TMU- -if-- wrap --name-------\n");
+ texels = 0;
+
+ for ( i = 0 ; i < tr.numImages ; i++ ) {
+ image = tr.images[ i ];
+
+ texels += image->uploadWidth*image->uploadHeight;
+ ri.Printf (PRINT_ALL, "%4i: %4i %4i %s %d ",
+ i, image->uploadWidth, image->uploadHeight, yesno[image->mipmap], image->TMU );
+ switch ( image->internalFormat ) {
+ case 1:
+ ri.Printf( PRINT_ALL, "I " );
+ break;
+ case 2:
+ ri.Printf( PRINT_ALL, "IA " );
+ break;
+ case 3:
+ ri.Printf( PRINT_ALL, "RGB " );
+ break;
+ case 4:
+ ri.Printf( PRINT_ALL, "RGBA " );
+ break;
+#ifndef PANDORA
+ case GL_RGBA8:
+ ri.Printf( PRINT_ALL, "RGBA8" );
+ break;
+ case GL_RGB8:
+ ri.Printf( PRINT_ALL, "RGB8" );
+ break;
+#endif
+ case GL_RGB4_S3TC:
+#ifndef PANDORA
+ case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+#endif
+ ri.Printf( PRINT_ALL, "S3TC " );
+ break;
+#ifndef PANDORA
+ case GL_RGBA4:
+ ri.Printf( PRINT_ALL, "RGBA4" );
+ break;
+ case GL_RGB5:
+ ri.Printf( PRINT_ALL, "RGB5 " );
+ break;
+#endif
+ default:
+ ri.Printf( PRINT_ALL, "???? " );
+ }
+
+ switch ( image->wrapClampMode ) {
+ case GL_REPEAT:
+ ri.Printf( PRINT_ALL, "rept " );
+ break;
+ case GL_CLAMP_TO_EDGE:
+ ri.Printf( PRINT_ALL, "clmp " );
+ break;
+ default:
+ ri.Printf( PRINT_ALL, "%4i ", image->wrapClampMode );
+ break;
+ }
+
+ ri.Printf( PRINT_ALL, " %s\n", image->imgName );
+ }
+ ri.Printf (PRINT_ALL, " ---------\n");
+ ri.Printf (PRINT_ALL, " %i total texels (not including mipmaps)\n", texels);
+ ri.Printf (PRINT_ALL, " %i total images\n\n", tr.numImages );
+}
+
+//=======================================================================
+
+/*
+================
+ResampleTexture
+
+Used to resample images in a more general than quartering fashion.
+
+This will only be filtered properly if the resampled size
+is greater than half the original size.
+
+If a larger shrinking is needed, use the mipmap function
+before or after.
+================
+*/
+static void ResampleTexture( unsigned *in, int inwidth, int inheight, unsigned *out,
+ int outwidth, int outheight ) {
+ int i, j;
+ unsigned *inrow, *inrow2;
+ unsigned frac, fracstep;
+ unsigned p1[2048], p2[2048];
+ byte *pix1, *pix2, *pix3, *pix4;
+
+ if (outwidth>2048)
+ ri.Error(ERR_DROP, "ResampleTexture: max width");
+
+ fracstep = inwidth*0x10000/outwidth;
+
+ frac = fracstep>>2;
+ for ( i=0 ; i<outwidth ; i++ ) {
+ p1[i] = 4*(frac>>16);
+ frac += fracstep;
+ }
+ frac = 3*(fracstep>>2);
+ for ( i=0 ; i<outwidth ; i++ ) {
+ p2[i] = 4*(frac>>16);
+ frac += fracstep;
+ }
+
+ for (i=0 ; i<outheight ; i++, out += outwidth) {
+ inrow = in + inwidth*(int)((i+0.25)*inheight/outheight);
+ inrow2 = in + inwidth*(int)((i+0.75)*inheight/outheight);
+ frac = fracstep >> 1;
+ for (j=0 ; j<outwidth ; j++) {
+ pix1 = (byte *)inrow + p1[j];
+ pix2 = (byte *)inrow + p2[j];
+ pix3 = (byte *)inrow2 + p1[j];
+ pix4 = (byte *)inrow2 + p2[j];
+ ((byte *)(out+j))[0] = (pix1[0] + pix2[0] + pix3[0] + pix4[0])>>2;
+ ((byte *)(out+j))[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2;
+ ((byte *)(out+j))[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2;
+ ((byte *)(out+j))[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2;
+ }
+ }
+}
+
+/*
+================
+R_LightScaleTexture
+
+Scale up the pixel values in a texture to increase the
+lighting range
+================
+*/
+void R_LightScaleTexture (unsigned *in, int inwidth, int inheight, qboolean only_gamma )
+{
+ if ( only_gamma )
+ {
+ if ( !glConfig.deviceSupportsGamma )
+ {
+ int i, c;
+ byte *p;
+
+ p = (byte *)in;
+
+ c = inwidth*inheight;
+ for (i=0 ; i<c ; i++, p+=4)
+ {
+ p[0] = s_gammatable[p[0]];
+ p[1] = s_gammatable[p[1]];
+ p[2] = s_gammatable[p[2]];
+ }
+ }
+ }
+ else
+ {
+ int i, c;
+ byte *p;
+
+ p = (byte *)in;
+
+ c = inwidth*inheight;
+
+ if ( glConfig.deviceSupportsGamma )
+ {
+ for (i=0 ; i<c ; i++, p+=4)
+ {
+ p[0] = s_intensitytable[p[0]];
+ p[1] = s_intensitytable[p[1]];
+ p[2] = s_intensitytable[p[2]];
+ }
+ }
+ else
+ {
+ for (i=0 ; i<c ; i++, p+=4)
+ {
+ p[0] = s_gammatable[s_intensitytable[p[0]]];
+ p[1] = s_gammatable[s_intensitytable[p[1]]];
+ p[2] = s_gammatable[s_intensitytable[p[2]]];
+ }
+ }
+ }
+}
+
+
+/*
+================
+R_MipMap2
+
+Operates in place, quartering the size of the texture
+Proper linear filter
+================
+*/
+static void R_MipMap2( unsigned *in, int inWidth, int inHeight ) {
+ int i, j, k;
+ byte *outpix;
+ int inWidthMask, inHeightMask;
+ int total;
+ int outWidth, outHeight;
+ unsigned *temp;
+
+ outWidth = inWidth >> 1;
+ outHeight = inHeight >> 1;
+ temp = ri.Hunk_AllocateTempMemory( outWidth * outHeight * 4 );
+
+ inWidthMask = inWidth - 1;
+ inHeightMask = inHeight - 1;
+
+ for ( i = 0 ; i < outHeight ; i++ ) {
+ for ( j = 0 ; j < outWidth ; j++ ) {
+ outpix = (byte *) ( temp + i * outWidth + j );
+ for ( k = 0 ; k < 4 ; k++ ) {
+ total =
+ 1 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] +
+ 2 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] +
+ 2 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] +
+ 1 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] +
+
+ 2 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] +
+ 4 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] +
+ 4 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] +
+ 2 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] +
+
+ 2 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] +
+ 4 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] +
+ 4 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] +
+ 2 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] +
+
+ 1 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] +
+ 2 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] +
+ 2 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] +
+ 1 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k];
+ outpix[k] = total / 36;
+ }
+ }
+ }
+
+ Com_Memcpy( in, temp, outWidth * outHeight * 4 );
+ ri.Hunk_FreeTempMemory( temp );
+}
+
+/*
+================
+R_MipMap
+
+Operates in place, quartering the size of the texture
+================
+*/
+static void R_MipMap (byte *in, int width, int height) {
+ int i, j;
+ byte *out;
+ int row;
+
+ if ( !r_simpleMipMaps->integer ) {
+ R_MipMap2( (unsigned *)in, width, height );
+ return;
+ }
+
+ if ( width == 1 && height == 1 ) {
+ return;
+ }
+
+ row = width * 4;
+ out = in;
+ width >>= 1;
+ height >>= 1;
+
+ if ( width == 0 || height == 0 ) {
+ width += height; // get largest
+ for (i=0 ; i<width ; i++, out+=4, in+=8 ) {
+ out[0] = ( in[0] + in[4] )>>1;
+ out[1] = ( in[1] + in[5] )>>1;
+ out[2] = ( in[2] + in[6] )>>1;
+ out[3] = ( in[3] + in[7] )>>1;
+ }
+ return;
+ }
+
+ for (i=0 ; i<height ; i++, in+=row) {
+ for (j=0 ; j<width ; j++, out+=4, in+=8) {
+ out[0] = (in[0] + in[4] + in[row+0] + in[row+4])>>2;
+ out[1] = (in[1] + in[5] + in[row+1] + in[row+5])>>2;
+ out[2] = (in[2] + in[6] + in[row+2] + in[row+6])>>2;
+ out[3] = (in[3] + in[7] + in[row+3] + in[row+7])>>2;
+ }
+ }
+}
+
+
+/*
+==================
+R_BlendOverTexture
+
+Apply a color blend over a set of pixels
+==================
+*/
+static void R_BlendOverTexture( byte *data, int pixelCount, byte blend[4] ) {
+ int i;
+ int inverseAlpha;
+ int premult[3];
+
+ inverseAlpha = 255 - blend[3];
+ premult[0] = blend[0] * blend[3];
+ premult[1] = blend[1] * blend[3];
+ premult[2] = blend[2] * blend[3];
+
+ for ( i = 0 ; i < pixelCount ; i++, data+=4 ) {
+ data[0] = ( data[0] * inverseAlpha + premult[0] ) >> 9;
+ data[1] = ( data[1] * inverseAlpha + premult[1] ) >> 9;
+ data[2] = ( data[2] * inverseAlpha + premult[2] ) >> 9;
+ }
+}
+
+byte mipBlendColors[16][4] = {
+ {0,0,0,0},
+ {255,0,0,128},
+ {0,255,0,128},
+ {0,0,255,128},
+ {255,0,0,128},
+ {0,255,0,128},
+ {0,0,255,128},
+ {255,0,0,128},
+ {0,255,0,128},
+ {0,0,255,128},
+ {255,0,0,128},
+ {0,255,0,128},
+ {0,0,255,128},
+ {255,0,0,128},
+ {0,255,0,128},
+ {0,0,255,128},
+};
+
+
+/*
+===============
+Upload32
+
+===============
+*/
+extern qboolean charSet;
+static void Upload32( unsigned *data,
+ int width, int height,
+ qboolean mipmap,
+ qboolean picmip,
+ qboolean lightMap,
+ int *format,
+ int *pUploadWidth, int *pUploadHeight )
+{
+ int samples;
+ unsigned *scaledBuffer = NULL;
+ unsigned *resampledBuffer = NULL;
+ int scaled_width, scaled_height;
+ int i, c;
+ byte *scan;
+ GLenum internalFormat = GL_RGB;
+ float rMax = 0, gMax = 0, bMax = 0;
+
+ //
+ // convert to exact power of 2 sizes
+ //
+ for (scaled_width = 1 ; scaled_width < width ; scaled_width<<=1)
+ ;
+ for (scaled_height = 1 ; scaled_height < height ; scaled_height<<=1)
+ ;
+ if ( r_roundImagesDown->integer && scaled_width > width )
+ scaled_width >>= 1;
+ if ( r_roundImagesDown->integer && scaled_height > height )
+ scaled_height >>= 1;
+
+ if ( scaled_width != width || scaled_height != height ) {
+ resampledBuffer = ri.Hunk_AllocateTempMemory( scaled_width * scaled_height * 4 );
+ ResampleTexture (data, width, height, resampledBuffer, scaled_width, scaled_height);
+ data = resampledBuffer;
+ width = scaled_width;
+ height = scaled_height;
+ }
+
+ //
+ // perform optional picmip operation
+ //
+ if ( picmip ) {
+ scaled_width >>= r_picmip->integer;
+ scaled_height >>= r_picmip->integer;
+ }
+
+ //
+ // clamp to minimum size
+ //
+ if (scaled_width < 1) {
+ scaled_width = 1;
+ }
+ if (scaled_height < 1) {
+ scaled_height = 1;
+ }
+
+ //
+ // clamp to the current upper OpenGL limit
+ // scale both axis down equally so we don't have to
+ // deal with a half mip resampling
+ //
+ while ( scaled_width > glConfig.maxTextureSize
+ || scaled_height > glConfig.maxTextureSize ) {
+ scaled_width >>= 1;
+ scaled_height >>= 1;
+ }
+
+ scaledBuffer = ri.Hunk_AllocateTempMemory( sizeof( unsigned ) * scaled_width * scaled_height );
+
+ //
+ // scan the texture for each channel's max values
+ // and verify if the alpha channel is being used or not
+ //
+ c = width*height;
+ scan = ((byte *)data);
+ samples = 3;
+
+#ifndef PANDORA
+ if(lightMap)
+ {
+ if(r_greyscale->integer)
+ internalFormat = GL_LUMINANCE;
+ else
+ internalFormat = GL_RGB;
+ }
+ else
+#endif
+ {
+ for ( i = 0; i < c; i++ )
+ {
+ if ( scan[i*4+0] > rMax )
+ {
+ rMax = scan[i*4+0];
+ }
+ if ( scan[i*4+1] > gMax )
+ {
+ gMax = scan[i*4+1];
+ }
+ if ( scan[i*4+2] > bMax )
+ {
+ bMax = scan[i*4+2];
+ }
+ if ( scan[i*4 + 3] != 255 )
+ {
+ samples = 4;
+ break;
+ }
+ }
+ // select proper internal format
+ if ( samples == 3 )
+ {
+#ifdef PANDORA
+ internalFormat = GL_RGBA;
+#else
+ if(r_greyscale->integer)
+ {
+ if(r_texturebits->integer == 16)
+ internalFormat = GL_LUMINANCE8;
+ else if(r_texturebits->integer == 32)
+ internalFormat = GL_LUMINANCE16;
+ else
+ internalFormat = GL_LUMINANCE;
+ }
+ else
+ {
+ if ( glConfig.textureCompression == TC_S3TC_ARB )
+ {
+ internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
+ }
+ else if ( glConfig.textureCompression == TC_S3TC )
+ {
+ internalFormat = GL_RGB4_S3TC;
+ }
+ else if ( r_texturebits->integer == 16 )
+ {
+ internalFormat = GL_RGB5;
+ }
+ else if ( r_texturebits->integer == 32 )
+ {
+ internalFormat = GL_RGB8;
+ }
+ else
+ {
+ internalFormat = GL_RGB;
+ }
+ }
+#endif
+ }
+ else if ( samples == 4 )
+ {
+#ifdef PANDORA
+ internalFormat = GL_RGBA;
+#else
+ if(r_greyscale->integer)
+ {
+ if(r_texturebits->integer == 16)
+ internalFormat = GL_LUMINANCE8_ALPHA8;
+ else if(r_texturebits->integer == 32)
+ internalFormat = GL_LUMINANCE16_ALPHA16;
+ else
+ internalFormat = GL_LUMINANCE_ALPHA;
+ }
+ else
+ {
+ if ( r_texturebits->integer == 16 )
+ {
+ internalFormat = GL_RGBA4;
+ }
+ else if ( r_texturebits->integer == 32 )
+ {
+ internalFormat = GL_RGBA8;
+ }
+ else
+ {
+ internalFormat = GL_RGBA;
+ }
+ }
+#endif
+ }
+ }
+
+ // copy or resample data as appropriate for first MIP level
+ if ( ( scaled_width == width ) &&
+ ( scaled_height == height ) ) {
+ if (!mipmap)
+ {
+ qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ *pUploadWidth = scaled_width;
+ *pUploadHeight = scaled_height;
+ *format = internalFormat;
+
+ goto done;
+ }
+ Com_Memcpy (scaledBuffer, data, width*height*4);
+ }
+ else
+ {
+ // use the normal mip-mapping function to go down from here
+ while ( width > scaled_width || height > scaled_height ) {
+ R_MipMap( (byte *)data, width, height );
+ width >>= 1;
+ height >>= 1;
+ if ( width < 1 ) {
+ width = 1;
+ }
+ if ( height < 1 ) {
+ height = 1;
+ }
+ }
+ Com_Memcpy( scaledBuffer, data, width * height * 4 );
+ }
+
+ R_LightScaleTexture (scaledBuffer, scaled_width, scaled_height, !mipmap );
+
+ *pUploadWidth = scaled_width;
+ *pUploadHeight = scaled_height;
+ *format = internalFormat;
+
+ qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer );
+
+ if (mipmap)
+ {
+ int miplevel;
+
+ miplevel = 0;
+ while (scaled_width > 1 || scaled_height > 1)
+ {
+ R_MipMap( (byte *)scaledBuffer, scaled_width, scaled_height );
+ scaled_width >>= 1;
+ scaled_height >>= 1;
+ if (scaled_width < 1)
+ scaled_width = 1;
+ if (scaled_height < 1)
+ scaled_height = 1;
+ miplevel++;
+
+ if ( r_colorMipLevels->integer ) {
+ R_BlendOverTexture( (byte *)scaledBuffer, scaled_width * scaled_height, mipBlendColors[miplevel] );
+ }
+
+ qglTexImage2D (GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer );
+ }
+ }
+done:
+
+ if (mipmap)
+ {
+#ifndef PANDORA
+ if ( textureFilterAnisotropic )
+ qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,
+ (GLint)Com_Clamp( 1, maxAnisotropy, r_ext_max_anisotropy->integer ) );
+#endif
+ qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
+ qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
+ }
+ else
+ {
+#ifndef PANDORA
+ if ( textureFilterAnisotropic )
+ qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1 );
+#endif
+ qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
+ qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+ }
+
+ GL_CheckErrors();
+
+ if ( scaledBuffer != 0 )
+ ri.Hunk_FreeTempMemory( scaledBuffer );
+ if ( resampledBuffer != 0 )
+ ri.Hunk_FreeTempMemory( resampledBuffer );
+}
+
+
+/*
+================
+R_CreateImage
+
+This is the only way any image_t are created
+================
+*/
+image_t *R_CreateImage( const char *name, const byte *pic, int width, int height,
+ qboolean mipmap, qboolean allowPicmip, int glWrapClampMode ) {
+ image_t *image;
+ qboolean isLightmap = qfalse;
+ long hash;
+
+ if (strlen(name) >= MAX_QPATH ) {
+ ri.Error (ERR_DROP, "R_CreateImage: \"%s\" is too long\n", name);
+ }
+ if ( !strncmp( name, "*lightmap", 9 ) ) {
+ isLightmap = qtrue;
+ }
+
+ if ( tr.numImages == MAX_DRAWIMAGES ) {
+ ri.Error( ERR_DROP, "R_CreateImage: MAX_DRAWIMAGES hit\n");
+ }
+
+ image = tr.images[tr.numImages] = ri.Hunk_Alloc( sizeof( image_t ), h_low );
+ image->texnum = 1024 + tr.numImages;
+ tr.numImages++;
+
+ image->mipmap = mipmap;
+ image->allowPicmip = allowPicmip;
+
+ strcpy (image->imgName, name);
+
+ image->width = width;
+ image->height = height;
+ image->wrapClampMode = glWrapClampMode;
+
+ // lightmaps are always allocated on TMU 1
+ if ( qglActiveTextureARB && isLightmap ) {
+ image->TMU = 1;
+ } else {
+ image->TMU = 0;
+ }
+
+ if ( qglActiveTextureARB ) {
+ GL_SelectTexture( image->TMU );
+ }
+
+ GL_Bind(image);
+
+ Upload32( (unsigned *)pic, image->width, image->height,
+ image->mipmap,
+ allowPicmip,
+ isLightmap,
+ &image->internalFormat,
+ &image->uploadWidth,
+ &image->uploadHeight );
+
+ qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glWrapClampMode );
+ qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glWrapClampMode );
+
+ qglBindTexture( GL_TEXTURE_2D, 0 );
+
+ if ( image->TMU == 1 ) {
+ GL_SelectTexture( 0 );
+ }
+
+ hash = generateHashValue(name);
+ image->next = hashTable[hash];
+ hashTable[hash] = image;
+
+ return image;
+}
+
+//===================================================================
+
+typedef struct
+{
+ char *ext;
+ void (*ImageLoader)( const char *, unsigned char **, int *, int * );
+} imageExtToLoaderMap_t;
+
+// Note that the ordering indicates the order of preference used
+// when there are multiple images of different formats available
+static imageExtToLoaderMap_t imageLoaders[ ] =
+{
+ { "tga", R_LoadTGA },
+ { "jpg", R_LoadJPG },
+ { "jpeg", R_LoadJPG },
+ { "png", R_LoadPNG },
+ { "pcx", R_LoadPCX },
+ { "bmp", R_LoadBMP }
+};
+
+static int numImageLoaders = sizeof( imageLoaders ) /
+ sizeof( imageLoaders[ 0 ] );
+
+/*
+=================
+R_LoadImage
+
+Loads any of the supported image types into a cannonical
+32 bit format.
+=================
+*/
+void R_LoadImage( const char *name, byte **pic, int *width, int *height )
+{
+ qboolean orgNameFailed = qfalse;
+ int i;
+ char localName[ MAX_QPATH ];
+ const char *ext;
+
+ *pic = NULL;
+ *width = 0;
+ *height = 0;
+
+ Q_strncpyz( localName, name, MAX_QPATH );
+
+ ext = COM_GetExtension( localName );
+
+ if( *ext )
+ {
+ // Look for the correct loader and use it
+ for( i = 0; i < numImageLoaders; i++ )
+ {
+ if( !Q_stricmp( ext, imageLoaders[ i ].ext ) )
+ {
+ // Load
+ imageLoaders[ i ].ImageLoader( localName, pic, width, height );
+ break;
+ }
+ }
+
+ // A loader was found
+ if( i < numImageLoaders )
+ {
+ if( *pic == NULL )
+ {
+ // Loader failed, most likely because the file isn't there;
+ // try again without the extension
+ orgNameFailed = qtrue;
+ COM_StripExtension( name, localName, MAX_QPATH );
+ }
+ else
+ {
+ // Something loaded
+ return;
+ }
+ }
+ }
+
+ // Try and find a suitable match using all
+ // the image formats supported
+ for( i = 0; i < numImageLoaders; i++ )
+ {
+ char *altName = va( "%s.%s", localName, imageLoaders[ i ].ext );
+
+ // Load
+ imageLoaders[ i ].ImageLoader( altName, pic, width, height );
+
+ if( *pic )
+ {
+ if( orgNameFailed )
+ {
+ ri.Printf( PRINT_DEVELOPER, "WARNING: %s not present, using %s instead\n",
+ name, altName );
+ }
+
+ break;
+ }
+ }
+}
+
+
+/*
+===============
+R_FindImageFile
+
+Finds or loads the given image.
+Returns NULL if it fails, not a default image.
+==============
+*/
+image_t *R_FindImageFile( const char *name, qboolean mipmap, qboolean allowPicmip, int glWrapClampMode ) {
+ image_t *image;
+ int width, height;
+ byte *pic;
+ long hash;
+
+ if (!name) {
+ return NULL;
+ }
+
+ hash = generateHashValue(name);
+
+ //
+ // see if the image is already loaded
+ //
+ for (image=hashTable[hash]; image; image=image->next) {
+ if ( !strcmp( name, image->imgName ) ) {
+ // the white image can be used with any set of parms, but other mismatches are errors
+ if ( strcmp( name, "*white" ) ) {
+ if ( image->mipmap != mipmap ) {
+ ri.Printf( PRINT_DEVELOPER, "WARNING: reused image %s with mixed mipmap parm\n", name );
+ }
+ if ( image->allowPicmip != allowPicmip ) {
+ ri.Printf( PRINT_DEVELOPER, "WARNING: reused image %s with mixed allowPicmip parm\n", name );
+ }
+ if ( image->wrapClampMode != glWrapClampMode ) {
+ ri.Printf( PRINT_ALL, "WARNING: reused image %s with mixed glWrapClampMode parm\n", name );
+ }
+ }
+ return image;
+ }
+ }
+
+ //
+ // load the pic from disk
+ //
+ R_LoadImage( name, &pic, &width, &height );
+ if ( pic == NULL ) {
+ return NULL;
+ }
+
+ image = R_CreateImage( ( char * ) name, pic, width, height, mipmap, allowPicmip, glWrapClampMode );
+ ri.Free( pic );
+ return image;
+}
+
+
+/*
+================
+R_CreateDlightImage
+================
+*/
+#define DLIGHT_SIZE 16
+static void R_CreateDlightImage( void ) {
+ int x,y;
+ byte data[DLIGHT_SIZE][DLIGHT_SIZE][4];
+ int b;
+
+ // make a centered inverse-square falloff blob for dynamic lighting
+ for (x=0 ; x<DLIGHT_SIZE ; x++) {
+ for (y=0 ; y<DLIGHT_SIZE ; y++) {
+ float d;
+
+ d = ( DLIGHT_SIZE/2 - 0.5f - x ) * ( DLIGHT_SIZE/2 - 0.5f - x ) +
+ ( DLIGHT_SIZE/2 - 0.5f - y ) * ( DLIGHT_SIZE/2 - 0.5f - y );
+ b = 4000 / d;
+ if (b > 255) {
+ b = 255;
+ } else if ( b < 75 ) {
+ b = 0;
+ }
+ data[y][x][0] =
+ data[y][x][1] =
+ data[y][x][2] = b;
+ data[y][x][3] = 255;
+ }
+ }
+ tr.dlightImage = R_CreateImage("*dlight", (byte *)data, DLIGHT_SIZE, DLIGHT_SIZE, qfalse, qfalse, GL_CLAMP_TO_EDGE );
+}
+
+
+/*
+=================
+R_InitFogTable
+=================
+*/
+void R_InitFogTable( void ) {
+ int i;
+ float d;
+ float exp;
+
+ exp = 0.5;
+
+ for ( i = 0 ; i < FOG_TABLE_SIZE ; i++ ) {
+ d = pow ( (float)i/(FOG_TABLE_SIZE-1), exp );
+
+ tr.fogTable[i] = d;
+ }
+}
+
+/*
+================
+R_FogFactor
+
+Returns a 0.0 to 1.0 fog density value
+This is called for each texel of the fog texture on startup
+and for each vertex of transparent shaders in fog dynamically
+================
+*/
+float R_FogFactor( float s, float t ) {
+ float d;
+
+ s -= 1.0/512;
+ if ( s < 0 ) {
+ return 0;
+ }
+ if ( t < 1.0/32 ) {
+ return 0;
+ }
+ if ( t < 31.0/32 ) {
+ s *= (t - 1.0f/32.0f) / (30.0f/32.0f);
+ }
+
+ // we need to leave a lot of clamp range
+ s *= 8;
+
+ if ( s > 1.0 ) {
+ s = 1.0;
+ }
+
+ d = tr.fogTable[ (int)(s * (FOG_TABLE_SIZE-1)) ];
+
+ return d;
+}
+
+/*
+================
+R_CreateFogImage
+================
+*/
+#define FOG_S 256
+#define FOG_T 32
+static void R_CreateFogImage( void ) {
+ int x,y;
+ byte *data;
+ float g;
+ float d;
+#ifndef PANDORA
+ float borderColor[4];
+#endif
+ data = ri.Hunk_AllocateTempMemory( FOG_S * FOG_T * 4 );
+
+ g = 2.0;
+
+ // S is distance, T is depth
+ for (x=0 ; x<FOG_S ; x++) {
+ for (y=0 ; y<FOG_T ; y++) {
+ d = R_FogFactor( ( x + 0.5f ) / FOG_S, ( y + 0.5f ) / FOG_T );
+
+ data[(y*FOG_S+x)*4+0] =
+ data[(y*FOG_S+x)*4+1] =
+ data[(y*FOG_S+x)*4+2] = 255;
+ data[(y*FOG_S+x)*4+3] = 255*d;
+ }
+ }
+ // standard openGL clamping doesn't really do what we want -- it includes
+ // the border color at the edges. OpenGL 1.2 has clamp-to-edge, which does
+ // what we want.
+ tr.fogImage = R_CreateImage("*fog", (byte *)data, FOG_S, FOG_T, qfalse, qfalse, GL_CLAMP_TO_EDGE );
+ ri.Hunk_FreeTempMemory( data );
+
+#ifndef PANDORA
+ borderColor[0] = 1.0;
+ borderColor[1] = 1.0;
+ borderColor[2] = 1.0;
+ borderColor[3] = 1;
+
+ qglTexParameterfv( GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor );
+#endif
+}
+
+/*
+==================
+R_CreateDefaultImage
+==================
+*/
+#define DEFAULT_SIZE 16
+static void R_CreateDefaultImage( void ) {
+ int x;
+ byte data[DEFAULT_SIZE][DEFAULT_SIZE][4];
+
+ // the default image will be a box, to allow you to see the mapping coordinates
+ Com_Memset( data, 32, sizeof( data ) );
+ for ( x = 0 ; x < DEFAULT_SIZE ; x++ ) {
+ data[0][x][0] =
+ data[0][x][1] =
+ data[0][x][2] =
+ data[0][x][3] = 255;
+
+ data[x][0][0] =
+ data[x][0][1] =
+ data[x][0][2] =
+ data[x][0][3] = 255;
+
+ data[DEFAULT_SIZE-1][x][0] =
+ data[DEFAULT_SIZE-1][x][1] =
+ data[DEFAULT_SIZE-1][x][2] =
+ data[DEFAULT_SIZE-1][x][3] = 255;
+
+ data[x][DEFAULT_SIZE-1][0] =
+ data[x][DEFAULT_SIZE-1][1] =
+ data[x][DEFAULT_SIZE-1][2] =
+ data[x][DEFAULT_SIZE-1][3] = 255;
+ }
+ tr.defaultImage = R_CreateImage("*default", (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, qtrue, qfalse, GL_REPEAT );
+}
+
+/*
+==================
+R_CreateBuiltinImages
+==================
+*/
+void R_CreateBuiltinImages( void ) {
+ int x,y;
+ byte data[DEFAULT_SIZE][DEFAULT_SIZE][4];
+
+ R_CreateDefaultImage();
+
+ // we use a solid white image instead of disabling texturing
+ Com_Memset( data, 255, sizeof( data ) );
+ tr.whiteImage = R_CreateImage("*white", (byte *)data, 8, 8, qfalse, qfalse, GL_REPEAT );
+
+ // with overbright bits active, we need an image which is some fraction of full color,
+ // for default lightmaps, etc
+ for (x=0 ; x<DEFAULT_SIZE ; x++) {
+ for (y=0 ; y<DEFAULT_SIZE ; y++) {
+ data[y][x][0] =
+ data[y][x][1] =
+ data[y][x][2] = tr.identityLightByte;
+ data[y][x][3] = 255;
+ }
+ }
+
+ tr.identityLightImage = R_CreateImage("*identityLight", (byte *)data, 8, 8, qfalse, qfalse, GL_REPEAT );
+
+
+ for(x=0;x<32;x++) {
+ // scratchimage is usually used for cinematic drawing
+ tr.scratchImage[x] = R_CreateImage("*scratch", (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, qfalse, qtrue, GL_CLAMP_TO_EDGE );
+ }
+
+ R_CreateDlightImage();
+ R_CreateFogImage();
+}
+
+
+/*
+===============
+R_SetColorMappings
+===============
+*/
+void R_SetColorMappings( void ) {
+ int i, j;
+ float g;
+ int inf;
+ int shift;
+
+ // setup the overbright lighting
+ tr.overbrightBits = r_overBrightBits->integer;
+ if ( !glConfig.deviceSupportsGamma ) {
+ tr.overbrightBits = 0; // need hardware gamma for overbright
+ }
+
+ // never overbright in windowed mode
+ if ( !glConfig.isFullscreen )
+ {
+ tr.overbrightBits = 0;
+ }
+
+ // allow 2 overbright bits in 24 bit, but only 1 in 16 bit
+ if ( glConfig.colorBits > 16 ) {
+ if ( tr.overbrightBits > 2 ) {
+ tr.overbrightBits = 2;
+ }
+ } else {
+ if ( tr.overbrightBits > 1 ) {
+ tr.overbrightBits = 1;
+ }
+ }
+ if ( tr.overbrightBits < 0 ) {
+ tr.overbrightBits = 0;
+ }
+
+ tr.identityLight = 1.0f / ( 1 << tr.overbrightBits );
+ tr.identityLightByte = 255 * tr.identityLight;
+
+
+ if ( r_intensity->value <= 1 ) {
+ ri.Cvar_Set( "r_intensity", "1" );
+ }
+
+ if ( r_gamma->value < 0.5f ) {
+ ri.Cvar_Set( "r_gamma", "0.5" );
+ } else if ( r_gamma->value > 3.0f ) {
+ ri.Cvar_Set( "r_gamma", "3.0" );
+ }
+
+ g = r_gamma->value;
+
+ shift = tr.overbrightBits;
+
+ for ( i = 0; i < 256; i++ ) {
+ if ( g == 1 ) {
+ inf = i;
+ } else {
+ inf = 255 * pow ( i/255.0f, 1.0f / g ) + 0.5f;
+ }
+ inf <<= shift;
+ if (inf < 0) {
+ inf = 0;
+ }
+ if (inf > 255) {
+ inf = 255;
+ }
+ s_gammatable[i] = inf;
+ }
+
+ for (i=0 ; i<256 ; i++) {
+ j = i * r_intensity->value;
+ if (j > 255) {
+ j = 255;
+ }
+ s_intensitytable[i] = j;
+ }
+
+ if ( glConfig.deviceSupportsGamma )
+ {
+ GLimp_SetGamma( s_gammatable, s_gammatable, s_gammatable );
+ }
+}
+
+/*
+===============
+R_InitImages
+===============
+*/
+void R_InitImages( void ) {
+ Com_Memset(hashTable, 0, sizeof(hashTable));
+ // build brightness translation tables
+ R_SetColorMappings();
+
+ // create default texture and white texture
+ R_CreateBuiltinImages();
+}
+
+/*
+===============
+R_DeleteTextures
+===============
+*/
+void R_DeleteTextures( void ) {
+ int i;
+
+ for ( i=0; i<tr.numImages ; i++ ) {
+ qglDeleteTextures( 1, &tr.images[i]->texnum );
+ }
+ Com_Memset( tr.images, 0, sizeof( tr.images ) );
+
+ tr.numImages = 0;
+
+ Com_Memset( glState.currenttextures, 0, sizeof( glState.currenttextures ) );
+ if ( qglActiveTextureARB ) {
+ GL_SelectTexture( 1 );
+ qglBindTexture( GL_TEXTURE_2D, 0 );
+ GL_SelectTexture( 0 );
+ qglBindTexture( GL_TEXTURE_2D, 0 );
+ } else {
+ qglBindTexture( GL_TEXTURE_2D, 0 );
+ }
+}
+
+/*
+============================================================================
+
+SKINS
+
+============================================================================
+*/
+
+/*
+==================
+CommaParse
+
+This is unfortunate, but the skin files aren't
+compatable with our normal parsing rules.
+==================
+*/
+static char *CommaParse( char **data_p ) {
+ int c = 0, len;
+ char *data;
+ static char com_token[MAX_TOKEN_CHARS];
+
+ data = *data_p;
+ len = 0;
+ com_token[0] = 0;
+
+ // make sure incoming data is valid
+ if ( !data ) {
+ *data_p = NULL;
+ return com_token;
+ }
+
+ while ( 1 ) {
+ // skip whitespace
+ while( (c = *data) <= ' ') {
+ if( !c ) {
+ break;
+ }
+ data++;
+ }
+
+
+ c = *data;
+
+ // skip double slash comments
+ if ( c == '/' && data[1] == '/' )
+ {
+ while (*data && *data != '\n')
+ data++;
+ }
+ // skip /* */ comments
+ else if ( c=='/' && data[1] == '*' )
+ {
+ while ( *data && ( *data != '*' || data[1] != '/' ) )
+ {
+ data++;
+ }
+ if ( *data )
+ {
+ data += 2;
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if ( c == 0 ) {
+ return "";
+ }
+
+ // handle quoted strings
+ if (c == '\"')
+ {
+ data++;
+ while (1)
+ {
+ c = *data++;
+ if (c=='\"' || !c)
+ {
+ com_token[len] = 0;
+ *data_p = ( char * ) data;
+ return com_token;
+ }
+ if (len < MAX_TOKEN_CHARS)
+ {
+ com_token[len] = c;
+ len++;
+ }
+ }
+ }
+
+ // parse a regular word
+ do
+ {
+ if (len < MAX_TOKEN_CHARS)
+ {
+ com_token[len] = c;
+ len++;
+ }
+ data++;
+ c = *data;
+ } while (c>32 && c != ',' );
+
+ if (len == MAX_TOKEN_CHARS)
+ {
+// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS);
+ len = 0;
+ }
+ com_token[len] = 0;
+
+ *data_p = ( char * ) data;
+ return com_token;
+}
+
+
+/*
+===============
+RE_RegisterSkin
+
+===============
+*/
+qhandle_t RE_RegisterSkin( const char *name ) {
+ qhandle_t hSkin;
+ skin_t *skin;
+ skinSurface_t *surf;
+ union {
+ char *c;
+ void *v;
+ } text;
+ char *text_p;
+ char *token;
+ char surfName[MAX_QPATH];
+
+ if ( !name || !name[0] ) {
+ Com_Printf( "Empty name passed to RE_RegisterSkin\n" );
+ return 0;
+ }
+
+ if ( strlen( name ) >= MAX_QPATH ) {
+ Com_Printf( "Skin name exceeds MAX_QPATH\n" );
+ return 0;
+ }
+
+
+ // see if the skin is already loaded
+ for ( hSkin = 1; hSkin < tr.numSkins ; hSkin++ ) {
+ skin = tr.skins[hSkin];
+ if ( !Q_stricmp( skin->name, name ) ) {
+ if( skin->numSurfaces == 0 ) {
+ return 0; // default skin
+ }
+ return hSkin;
+ }
+ }
+
+ // allocate a new skin
+ if ( tr.numSkins == MAX_SKINS ) {
+ ri.Printf( PRINT_WARNING, "WARNING: RE_RegisterSkin( '%s' ) MAX_SKINS hit\n", name );
+ return 0;
+ }
+ tr.numSkins++;
+ skin = ri.Hunk_Alloc( sizeof( skin_t ), h_low );
+ tr.skins[hSkin] = skin;
+ Q_strncpyz( skin->name, name, sizeof( skin->name ) );
+ skin->numSurfaces = 0;
+
+ // make sure the render thread is stopped
+ R_SyncRenderThread();
+
+ // If not a .skin file, load as a single shader
+ if ( strcmp( name + strlen( name ) - 5, ".skin" ) ) {
+ skin->numSurfaces = 1;
+ skin->surfaces[0] = ri.Hunk_Alloc( sizeof(skin->surfaces[0]), h_low );
+ skin->surfaces[0]->shader = R_FindShader( name, LIGHTMAP_NONE, qtrue );
+ return hSkin;
+ }
+
+ // load and parse the skin file
+ ri.FS_ReadFile( name, &text.v );
+ if ( !text.c ) {
+ return 0;
+ }
+
+ text_p = text.c;
+ while ( text_p && *text_p ) {
+ // get surface name
+ token = CommaParse( &text_p );
+ Q_strncpyz( surfName, token, sizeof( surfName ) );
+
+ if ( !token[0] ) {
+ break;
+ }
+ // lowercase the surface name so skin compares are faster
+ Q_strlwr( surfName );
+
+ if ( *text_p == ',' ) {
+ text_p++;
+ }
+
+ if ( strstr( token, "tag_" ) ) {
+ continue;
+ }
+
+ // parse the shader name
+ token = CommaParse( &text_p );
+
+ surf = skin->surfaces[ skin->numSurfaces ] = ri.Hunk_Alloc( sizeof( *skin->surfaces[0] ), h_low );
+ Q_strncpyz( surf->name, surfName, sizeof( surf->name ) );
+ surf->shader = R_FindShader( token, LIGHTMAP_NONE, qtrue );
+ skin->numSurfaces++;
+ }
+
+ ri.FS_FreeFile( text.v );
+
+
+ // never let a skin have 0 shaders
+ if ( skin->numSurfaces == 0 ) {
+ return 0; // use default skin
+ }
+
+ return hSkin;
+}
+
+
+/*
+===============
+R_InitSkins
+===============
+*/
+void R_InitSkins( void ) {
+ skin_t *skin;
+
+ tr.numSkins = 1;
+
+ // make the default skin have all default shaders
+ skin = tr.skins[0] = ri.Hunk_Alloc( sizeof( skin_t ), h_low );
+ Q_strncpyz( skin->name, "<default skin>", sizeof( skin->name ) );
+ skin->numSurfaces = 1;
+ skin->surfaces[0] = ri.Hunk_Alloc( sizeof( *skin->surfaces ), h_low );
+ skin->surfaces[0]->shader = tr.defaultShader;
+}
+
+/*
+===============
+R_GetSkinByHandle
+===============
+*/
+skin_t *R_GetSkinByHandle( qhandle_t hSkin ) {
+ if ( hSkin < 1 || hSkin >= tr.numSkins ) {
+ return tr.skins[0];
+ }
+ return tr.skins[ hSkin ];
+}
+
+/*
+===============
+R_SkinList_f
+===============
+*/
+void R_SkinList_f( void ) {
+ int i, j;
+ skin_t *skin;
+
+ ri.Printf (PRINT_ALL, "------------------\n");
+
+ for ( i = 0 ; i < tr.numSkins ; i++ ) {
+ skin = tr.skins[i];
+
+ ri.Printf( PRINT_ALL, "%3i:%s\n", i, skin->name );
+ for ( j = 0 ; j < skin->numSurfaces ; j++ ) {
+ ri.Printf( PRINT_ALL, " %s = %s\n",
+ skin->surfaces[j]->name, skin->surfaces[j]->shader->name );
+ }
+ }
+ ri.Printf (PRINT_ALL, "------------------\n");
+}
+
diff --git a/code/renderer/tr_image_bmp.c b/code/renderer/tr_image_bmp.c
new file mode 100644
index 0000000..0b78abb
--- /dev/null
+++ b/code/renderer/tr_image_bmp.c
@@ -0,0 +1,239 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "tr_local.h"
+
+typedef struct
+{
+ char id[2];
+ unsigned fileSize;
+ unsigned reserved0;
+ unsigned bitmapDataOffset;
+ unsigned bitmapHeaderSize;
+ unsigned width;
+ unsigned height;
+ unsigned short planes;
+ unsigned short bitsPerPixel;
+ unsigned compression;
+ unsigned bitmapDataSize;
+ unsigned hRes;
+ unsigned vRes;
+ unsigned colors;
+ unsigned importantColors;
+ unsigned char palette[256][4];
+} BMPHeader_t;
+
+void R_LoadBMP( const char *name, byte **pic, int *width, int *height )
+{
+ int columns, rows;
+ unsigned numPixels;
+ byte *pixbuf;
+ int row, column;
+ byte *buf_p;
+ byte *end;
+ union {
+ byte *b;
+ void *v;
+ } buffer;
+ int length;
+ BMPHeader_t bmpHeader;
+ byte *bmpRGBA;
+
+ *pic = NULL;
+
+ if(width)
+ *width = 0;
+
+ if(height)
+ *height = 0;
+
+ //
+ // load the file
+ //
+ length = ri.FS_ReadFile( ( char * ) name, &buffer.v);
+ if (!buffer.b || length < 0) {
+ return;
+ }
+
+ if (length < 54)
+ {
+ ri.Error( ERR_DROP, "LoadBMP: header too short (%s)\n", name );
+ }
+
+ buf_p = buffer.b;
+ end = buffer.b + length;
+
+ bmpHeader.id[0] = *buf_p++;
+ bmpHeader.id[1] = *buf_p++;
+ bmpHeader.fileSize = LittleLong( * ( int * ) buf_p );
+ buf_p += 4;
+ bmpHeader.reserved0 = LittleLong( * ( int * ) buf_p );
+ buf_p += 4;
+ bmpHeader.bitmapDataOffset = LittleLong( * ( int * ) buf_p );
+ buf_p += 4;
+ bmpHeader.bitmapHeaderSize = LittleLong( * ( int * ) buf_p );
+ buf_p += 4;
+ bmpHeader.width = LittleLong( * ( int * ) buf_p );
+ buf_p += 4;
+ bmpHeader.height = LittleLong( * ( int * ) buf_p );
+ buf_p += 4;
+ bmpHeader.planes = LittleShort( * ( short * ) buf_p );
+ buf_p += 2;
+ bmpHeader.bitsPerPixel = LittleShort( * ( short * ) buf_p );
+ buf_p += 2;
+ bmpHeader.compression = LittleLong( * ( int * ) buf_p );
+ buf_p += 4;
+ bmpHeader.bitmapDataSize = LittleLong( * ( int * ) buf_p );
+ buf_p += 4;
+ bmpHeader.hRes = LittleLong( * ( int * ) buf_p );
+ buf_p += 4;
+ bmpHeader.vRes = LittleLong( * ( int * ) buf_p );
+ buf_p += 4;
+ bmpHeader.colors = LittleLong( * ( int * ) buf_p );
+ buf_p += 4;
+ bmpHeader.importantColors = LittleLong( * ( int * ) buf_p );
+ buf_p += 4;
+
+ if ( bmpHeader.bitsPerPixel == 8 )
+ {
+ if (buf_p + sizeof(bmpHeader.palette) > end)
+ ri.Error( ERR_DROP, "LoadBMP: header too short (%s)\n", name );
+
+ Com_Memcpy( bmpHeader.palette, buf_p, sizeof( bmpHeader.palette ) );
+ buf_p += sizeof(bmpHeader.palette);
+ }
+
+ if (buffer.b + bmpHeader.bitmapDataOffset > end)
+ {
+ ri.Error( ERR_DROP, "LoadBMP: invalid offset value in header (%s)\n", name );
+ }
+
+ buf_p = buffer.b + bmpHeader.bitmapDataOffset;
+
+ if ( bmpHeader.id[0] != 'B' && bmpHeader.id[1] != 'M' )
+ {
+ ri.Error( ERR_DROP, "LoadBMP: only Windows-style BMP files supported (%s)\n", name );
+ }
+ if ( bmpHeader.fileSize != length )
+ {
+ ri.Error( ERR_DROP, "LoadBMP: header size does not match file size (%u vs. %u) (%s)\n", bmpHeader.fileSize, length, name );
+ }
+ if ( bmpHeader.compression != 0 )
+ {
+ ri.Error( ERR_DROP, "LoadBMP: only uncompressed BMP files supported (%s)\n", name );
+ }
+ if ( bmpHeader.bitsPerPixel < 8 )
+ {
+ ri.Error( ERR_DROP, "LoadBMP: monochrome and 4-bit BMP files not supported (%s)\n", name );
+ }
+
+ switch ( bmpHeader.bitsPerPixel )
+ {
+ case 8:
+ case 16:
+ case 24:
+ case 32:
+ break;
+ default:
+ ri.Error( ERR_DROP, "LoadBMP: illegal pixel_size '%hu' in file '%s'\n", bmpHeader.bitsPerPixel, name );
+ break;
+ }
+
+ columns = bmpHeader.width;
+ rows = bmpHeader.height;
+ if ( rows < 0 )
+ rows = -rows;
+ numPixels = columns * rows;
+
+ if(columns <= 0 || !rows || numPixels > 0x1FFFFFFF // 4*1FFFFFFF == 0x7FFFFFFC < 0x7FFFFFFF
+ || ((numPixels * 4) / columns) / 4 != rows)
+ {
+ ri.Error (ERR_DROP, "LoadBMP: %s has an invalid image size\n", name);
+ }
+ if(buf_p + numPixels*bmpHeader.bitsPerPixel/8 > end)
+ {
+ ri.Error (ERR_DROP, "LoadBMP: file truncated (%s)\n", name);
+ }
+
+ if ( width )
+ *width = columns;
+ if ( height )
+ *height = rows;
+
+ bmpRGBA = ri.Malloc( numPixels * 4 );
+ *pic = bmpRGBA;
+
+
+ for ( row = rows-1; row >= 0; row-- )
+ {
+ pixbuf = bmpRGBA + row*columns*4;
+
+ for ( column = 0; column < columns; column++ )
+ {
+ unsigned char red, green, blue, alpha;
+ int palIndex;
+ unsigned short shortPixel;
+
+ switch ( bmpHeader.bitsPerPixel )
+ {
+ case 8:
+ palIndex = *buf_p++;
+ *pixbuf++ = bmpHeader.palette[palIndex][2];
+ *pixbuf++ = bmpHeader.palette[palIndex][1];
+ *pixbuf++ = bmpHeader.palette[palIndex][0];
+ *pixbuf++ = 0xff;
+ break;
+ case 16:
+ shortPixel = * ( unsigned short * ) pixbuf;
+ pixbuf += 2;
+ *pixbuf++ = ( shortPixel & ( 31 << 10 ) ) >> 7;
+ *pixbuf++ = ( shortPixel & ( 31 << 5 ) ) >> 2;
+ *pixbuf++ = ( shortPixel & ( 31 ) ) << 3;
+ *pixbuf++ = 0xff;
+ break;
+
+ case 24:
+ blue = *buf_p++;
+ green = *buf_p++;
+ red = *buf_p++;
+ *pixbuf++ = red;
+ *pixbuf++ = green;
+ *pixbuf++ = blue;
+ *pixbuf++ = 255;
+ break;
+ case 32:
+ blue = *buf_p++;
+ green = *buf_p++;
+ red = *buf_p++;
+ alpha = *buf_p++;
+ *pixbuf++ = red;
+ *pixbuf++ = green;
+ *pixbuf++ = blue;
+ *pixbuf++ = alpha;
+ break;
+ }
+ }
+ }
+
+ ri.FS_FreeFile( buffer.v );
+
+}
diff --git a/code/renderer/tr_image_jpg.c b/code/renderer/tr_image_jpg.c
new file mode 100644
index 0000000..2e1a52a
--- /dev/null
+++ b/code/renderer/tr_image_jpg.c
@@ -0,0 +1,497 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "tr_local.h"
+
+/*
+ * Include file for users of JPEG library.
+ * You will need to have included system headers that define at least
+ * the typedefs FILE and size_t before you can include jpeglib.h.
+ * (stdio.h is sufficient on ANSI-conforming systems.)
+ * You may also wish to include "jerror.h".
+ */
+
+#define JPEG_INTERNALS
+#include "../jpeg-6b/jpeglib.h"
+
+void R_LoadJPG( const char *filename, unsigned char **pic, int *width, int *height ) {
+ /* This struct contains the JPEG decompression parameters and pointers to
+ * working space (which is allocated as needed by the JPEG library).
+ */
+ struct jpeg_decompress_struct cinfo = {NULL};
+ /* We use our private extension JPEG error handler.
+ * Note that this struct must live as long as the main JPEG parameter
+ * struct, to avoid dangling-pointer problems.
+ */
+ /* This struct represents a JPEG error handler. It is declared separately
+ * because applications often want to supply a specialized error handler
+ * (see the second half of this file for an example). But here we just
+ * take the easy way out and use the standard error handler, which will
+ * print a message on stderr and call exit() if compression fails.
+ * Note that this struct must live as long as the main JPEG parameter
+ * struct, to avoid dangling-pointer problems.
+ */
+ struct jpeg_error_mgr jerr;
+ /* More stuff */
+ JSAMPARRAY buffer; /* Output row buffer */
+ unsigned row_stride; /* physical row width in output buffer */
+ unsigned pixelcount, memcount;
+ unsigned char *out;
+ int len;
+ union {
+ byte *b;
+ void *v;
+ } fbuffer;
+ byte *buf;
+
+ /* In this example we want to open the input file before doing anything else,
+ * so that the setjmp() error recovery below can assume the file is open.
+ * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
+ * requires it in order to read binary files.
+ */
+
+ len = ri.FS_ReadFile ( ( char * ) filename, &fbuffer.v);
+ if (!fbuffer.b || len < 0) {
+ return;
+ }
+
+ /* Step 1: allocate and initialize JPEG decompression object */
+
+ /* We have to set up the error handler first, in case the initialization
+ * step fails. (Unlikely, but it could happen if you are out of memory.)
+ * This routine fills in the contents of struct jerr, and returns jerr's
+ * address which we place into the link field in cinfo.
+ */
+ cinfo.err = jpeg_std_error(&jerr);
+
+ /* Now we can initialize the JPEG decompression object. */
+ jpeg_create_decompress(&cinfo);
+
+ /* Step 2: specify data source (eg, a file) */
+
+ jpeg_mem_src(&cinfo, fbuffer.b, len);
+
+ /* Step 3: read file parameters with jpeg_read_header() */
+
+ (void) jpeg_read_header(&cinfo, TRUE);
+ /* We can ignore the return value from jpeg_read_header since
+ * (a) suspension is not possible with the stdio data source, and
+ * (b) we passed TRUE to reject a tables-only JPEG file as an error.
+ * See libjpeg.doc for more info.
+ */
+
+ /* Step 4: set parameters for decompression */
+
+ /* In this example, we don't need to change any of the defaults set by
+ * jpeg_read_header(), so we do nothing here.
+ */
+
+ /* Step 5: Start decompressor */
+
+ (void) jpeg_start_decompress(&cinfo);
+ /* We can ignore the return value since suspension is not possible
+ * with the stdio data source.
+ */
+
+ /* We may need to do some setup of our own at this point before reading
+ * the data. After jpeg_start_decompress() we have the correct scaled
+ * output image dimensions available, as well as the output colormap
+ * if we asked for color quantization.
+ * In this example, we need to make an output work buffer of the right size.
+ */
+ /* JSAMPLEs per row in output buffer */
+
+ pixelcount = cinfo.output_width * cinfo.output_height;
+
+ if(!cinfo.output_width || !cinfo.output_height
+ || ((pixelcount * 4) / cinfo.output_width) / 4 != cinfo.output_height
+ || pixelcount > 0x1FFFFFFF || cinfo.output_components > 4) // 4*1FFFFFFF == 0x7FFFFFFC < 0x7FFFFFFF
+ {
+ ri.Error (ERR_DROP, "LoadJPG: %s has an invalid image size: %dx%d*4=%d, components: %d\n", filename,
+ cinfo.output_width, cinfo.output_height, pixelcount * 4, cinfo.output_components);
+ }
+
+ memcount = pixelcount * 4;
+ row_stride = cinfo.output_width * cinfo.output_components;
+
+ out = ri.Malloc(memcount);
+
+ *width = cinfo.output_width;
+ *height = cinfo.output_height;
+
+ /* Step 6: while (scan lines remain to be read) */
+ /* jpeg_read_scanlines(...); */
+
+ /* Here we use the library's state variable cinfo.output_scanline as the
+ * loop counter, so that we don't have to keep track ourselves.
+ */
+ while (cinfo.output_scanline < cinfo.output_height) {
+ /* jpeg_read_scanlines expects an array of pointers to scanlines.
+ * Here the array is only one element long, but you could ask for
+ * more than one scanline at a time if that's more convenient.
+ */
+ buf = ((out+(row_stride*cinfo.output_scanline)));
+ buffer = &buf;
+ (void) jpeg_read_scanlines(&cinfo, buffer, 1);
+ }
+
+ buf = out;
+
+ // If we are processing an 8-bit JPEG (greyscale), we'll have to convert
+ // the greyscale values to RGBA.
+ if(cinfo.output_components == 1)
+ {
+ int sindex = pixelcount, dindex = memcount;
+ unsigned char greyshade;
+
+ // Only pixelcount number of bytes have been written.
+ // Expand the color values over the rest of the buffer, starting
+ // from the end.
+ do
+ {
+ greyshade = buf[--sindex];
+
+ buf[--dindex] = 255;
+ buf[--dindex] = greyshade;
+ buf[--dindex] = greyshade;
+ buf[--dindex] = greyshade;
+ } while(sindex);
+ }
+ else
+ {
+ // clear all the alphas to 255
+ int i;
+
+ for ( i = 3 ; i < memcount ; i+=4 )
+ {
+ buf[i] = 255;
+ }
+ }
+
+ *pic = out;
+
+ /* Step 7: Finish decompression */
+
+ (void) jpeg_finish_decompress(&cinfo);
+ /* We can ignore the return value since suspension is not possible
+ * with the stdio data source.
+ */
+
+ /* Step 8: Release JPEG decompression object */
+
+ /* This is an important step since it will release a good deal of memory. */
+ jpeg_destroy_decompress(&cinfo);
+
+ /* After finish_decompress, we can close the input file.
+ * Here we postpone it until after no more JPEG errors are possible,
+ * so as to simplify the setjmp error logic above. (Actually, I don't
+ * think that jpeg_destroy can do an error exit, but why assume anything...)
+ */
+ ri.FS_FreeFile (fbuffer.v);
+
+ /* At this point you may want to check to see whether any corrupt-data
+ * warnings occurred (test whether jerr.pub.num_warnings is nonzero).
+ */
+
+ /* And we're done! */
+}
+
+
+/* Expanded data destination object for stdio output */
+
+typedef struct {
+ struct jpeg_destination_mgr pub; /* public fields */
+
+ byte* outfile; /* target stream */
+ int size;
+} my_destination_mgr;
+
+typedef my_destination_mgr * my_dest_ptr;
+
+
+/*
+ * Initialize destination --- called by jpeg_start_compress
+ * before any data is actually written.
+ */
+
+static void
+init_destination (j_compress_ptr cinfo)
+{
+ my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
+
+ dest->pub.next_output_byte = dest->outfile;
+ dest->pub.free_in_buffer = dest->size;
+}
+
+
+/*
+ * Empty the output buffer --- called whenever buffer fills up.
+ *
+ * In typical applications, this should write the entire output buffer
+ * (ignoring the current state of next_output_byte & free_in_buffer),
+ * reset the pointer & count to the start of the buffer, and return TRUE
+ * indicating that the buffer has been dumped.
+ *
+ * In applications that need to be able to suspend compression due to output
+ * overrun, a FALSE return indicates that the buffer cannot be emptied now.
+ * In this situation, the compressor will return to its caller (possibly with
+ * an indication that it has not accepted all the supplied scanlines). The
+ * application should resume compression after it has made more room in the
+ * output buffer. Note that there are substantial restrictions on the use of
+ * suspension --- see the documentation.
+ *
+ * When suspending, the compressor will back up to a convenient restart point
+ * (typically the start of the current MCU). next_output_byte & free_in_buffer
+ * indicate where the restart point will be if the current call returns FALSE.
+ * Data beyond this point will be regenerated after resumption, so do not
+ * write it out when emptying the buffer externally.
+ */
+
+static boolean
+empty_output_buffer (j_compress_ptr cinfo)
+{
+ return TRUE;
+}
+
+/*
+ * Terminate destination --- called by jpeg_finish_compress
+ * after all data has been written. Usually needs to flush buffer.
+ *
+ * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding
+ * application must deal with any cleanup that should happen even
+ * for error exit.
+ */
+
+static int hackSize;
+
+static void
+term_destination (j_compress_ptr cinfo)
+{
+ my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
+ size_t datacount = dest->size - dest->pub.free_in_buffer;
+ hackSize = datacount;
+}
+
+
+/*
+ * Prepare for output to a stdio stream.
+ * The caller must have already opened the stream, and is responsible
+ * for closing it after finishing compression.
+ */
+
+static void
+jpegDest (j_compress_ptr cinfo, byte* outfile, int size)
+{
+ my_dest_ptr dest;
+
+ /* The destination object is made permanent so that multiple JPEG images
+ * can be written to the same file without re-executing jpeg_stdio_dest.
+ * This makes it dangerous to use this manager and a different destination
+ * manager serially with the same JPEG object, because their private object
+ * sizes may be different. Caveat programmer.
+ */
+ if (cinfo->dest == NULL) { /* first time for this JPEG object? */
+ cinfo->dest = (struct jpeg_destination_mgr *)
+ (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
+ sizeof(my_destination_mgr));
+ }
+
+ dest = (my_dest_ptr) cinfo->dest;
+ dest->pub.init_destination = init_destination;
+ dest->pub.empty_output_buffer = empty_output_buffer;
+ dest->pub.term_destination = term_destination;
+ dest->outfile = outfile;
+ dest->size = size;
+}
+
+void SaveJPG(char * filename, int quality, int image_width, int image_height, unsigned char *image_buffer) {
+ /* This struct contains the JPEG compression parameters and pointers to
+ * working space (which is allocated as needed by the JPEG library).
+ * It is possible to have several such structures, representing multiple
+ * compression/decompression processes, in existence at once. We refer
+ * to any one struct (and its associated working data) as a "JPEG object".
+ */
+ struct jpeg_compress_struct cinfo;
+ /* This struct represents a JPEG error handler. It is declared separately
+ * because applications often want to supply a specialized error handler
+ * (see the second half of this file for an example). But here we just
+ * take the easy way out and use the standard error handler, which will
+ * print a message on stderr and call exit() if compression fails.
+ * Note that this struct must live as long as the main JPEG parameter
+ * struct, to avoid dangling-pointer problems.
+ */
+ struct jpeg_error_mgr jerr;
+ /* More stuff */
+ JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
+ int row_stride; /* physical row width in image buffer */
+ unsigned char *out;
+
+ /* Step 1: allocate and initialize JPEG compression object */
+
+ /* We have to set up the error handler first, in case the initialization
+ * step fails. (Unlikely, but it could happen if you are out of memory.)
+ * This routine fills in the contents of struct jerr, and returns jerr's
+ * address which we place into the link field in cinfo.
+ */
+ cinfo.err = jpeg_std_error(&jerr);
+ /* Now we can initialize the JPEG compression object. */
+ jpeg_create_compress(&cinfo);
+
+ /* Step 2: specify data destination (eg, a file) */
+ /* Note: steps 2 and 3 can be done in either order. */
+
+ /* Here we use the library-supplied code to send compressed data to a
+ * stdio stream. You can also write your own code to do something else.
+ * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
+ * requires it in order to write binary files.
+ */
+ out = ri.Hunk_AllocateTempMemory(image_width*image_height*4);
+ jpegDest(&cinfo, out, image_width*image_height*4);
+
+ /* Step 3: set parameters for compression */
+
+ /* First we supply a description of the input image.
+ * Four fields of the cinfo struct must be filled in:
+ */
+ cinfo.image_width = image_width; /* image width and height, in pixels */
+ cinfo.image_height = image_height;
+ cinfo.input_components = 4; /* # of color components per pixel */
+ cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
+ /* Now use the library's routine to set default compression parameters.
+ * (You must set at least cinfo.in_color_space before calling this,
+ * since the defaults depend on the source color space.)
+ */
+ jpeg_set_defaults(&cinfo);
+ /* Now you can set any non-default parameters you wish to.
+ * Here we just illustrate the use of quality (quantization table) scaling:
+ */
+ jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
+ /* If quality is set high, disable chroma subsampling */
+ if (quality >= 85) {
+ cinfo.comp_info[0].h_samp_factor = 1;
+ cinfo.comp_info[0].v_samp_factor = 1;
+ }
+
+ /* Step 4: Start compressor */
+
+ /* TRUE ensures that we will write a complete interchange-JPEG file.
+ * Pass TRUE unless you are very sure of what you're doing.
+ */
+ jpeg_start_compress(&cinfo, TRUE);
+
+ /* Step 5: while (scan lines remain to be written) */
+ /* jpeg_write_scanlines(...); */
+
+ /* Here we use the library's state variable cinfo.next_scanline as the
+ * loop counter, so that we don't have to keep track ourselves.
+ * To keep things simple, we pass one scanline per call; you can pass
+ * more if you wish, though.
+ */
+ row_stride = image_width * 4; /* JSAMPLEs per row in image_buffer */
+
+ while (cinfo.next_scanline < cinfo.image_height) {
+ /* jpeg_write_scanlines expects an array of pointers to scanlines.
+ * Here the array is only one element long, but you could pass
+ * more than one scanline at a time if that's more convenient.
+ */
+ row_pointer[0] = & image_buffer[((cinfo.image_height-1)*row_stride)-cinfo.next_scanline * row_stride];
+ (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
+ }
+
+ /* Step 6: Finish compression */
+
+ jpeg_finish_compress(&cinfo);
+ /* After finish_compress, we can close the output file. */
+ ri.FS_WriteFile( filename, out, hackSize );
+
+ ri.Hunk_FreeTempMemory(out);
+
+ /* Step 7: release JPEG compression object */
+
+ /* This is an important step since it will release a good deal of memory. */
+ jpeg_destroy_compress(&cinfo);
+
+ /* And we're done! */
+}
+
+/*
+=================
+SaveJPGToBuffer
+=================
+*/
+int SaveJPGToBuffer( byte *buffer, int quality,
+ int image_width, int image_height,
+ byte *image_buffer )
+{
+ struct jpeg_compress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
+ int row_stride; /* physical row width in image buffer */
+
+ /* Step 1: allocate and initialize JPEG compression object */
+ cinfo.err = jpeg_std_error(&jerr);
+ /* Now we can initialize the JPEG compression object. */
+ jpeg_create_compress(&cinfo);
+
+ /* Step 2: specify data destination (eg, a file) */
+ /* Note: steps 2 and 3 can be done in either order. */
+ jpegDest(&cinfo, buffer, image_width*image_height*4);
+
+ /* Step 3: set parameters for compression */
+ cinfo.image_width = image_width; /* image width and height, in pixels */
+ cinfo.image_height = image_height;
+ cinfo.input_components = 4; /* # of color components per pixel */
+ cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
+
+ jpeg_set_defaults(&cinfo);
+ jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
+ /* If quality is set high, disable chroma subsampling */
+ if (quality >= 85) {
+ cinfo.comp_info[0].h_samp_factor = 1;
+ cinfo.comp_info[0].v_samp_factor = 1;
+ }
+
+ /* Step 4: Start compressor */
+ jpeg_start_compress(&cinfo, TRUE);
+
+ /* Step 5: while (scan lines remain to be written) */
+ /* jpeg_write_scanlines(...); */
+ row_stride = image_width * 4; /* JSAMPLEs per row in image_buffer */
+
+ while (cinfo.next_scanline < cinfo.image_height) {
+ /* jpeg_write_scanlines expects an array of pointers to scanlines.
+ * Here the array is only one element long, but you could pass
+ * more than one scanline at a time if that's more convenient.
+ */
+ row_pointer[0] = & image_buffer[((cinfo.image_height-1)*row_stride)-cinfo.next_scanline * row_stride];
+ (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
+ }
+
+ /* Step 6: Finish compression */
+ jpeg_finish_compress(&cinfo);
+
+ /* Step 7: release JPEG compression object */
+ jpeg_destroy_compress(&cinfo);
+
+ /* And we're done! */
+ return hackSize;
+}
diff --git a/code/renderer/tr_image_pcx.c b/code/renderer/tr_image_pcx.c
new file mode 100644
index 0000000..d1b1a5e
--- /dev/null
+++ b/code/renderer/tr_image_pcx.c
@@ -0,0 +1,175 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+ 2008 Ludwig Nussel
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "tr_local.h"
+
+/*
+========================================================================
+
+PCX files are used for 8 bit images
+
+========================================================================
+*/
+
+typedef struct {
+ char manufacturer;
+ char version;
+ char encoding;
+ char bits_per_pixel;
+ unsigned short xmin,ymin,xmax,ymax;
+ unsigned short hres,vres;
+ unsigned char palette[48];
+ char reserved;
+ char color_planes;
+ unsigned short bytes_per_line;
+ unsigned short palette_type;
+ unsigned short hscreensize, vscreensize;
+ char filler[54];
+ unsigned char data[];
+} pcx_t;
+
+void R_LoadPCX ( const char *filename, byte **pic, int *width, int *height)
+{
+ union {
+ byte *b;
+ void *v;
+ } raw;
+ byte *end;
+ pcx_t *pcx;
+ int len;
+ unsigned char dataByte = 0, runLength = 0;
+ byte *out, *pix;
+ unsigned short w, h;
+ byte *pic8;
+ byte *palette;
+ int i;
+ unsigned size = 0;
+
+ if (width)
+ *width = 0;
+ if (height)
+ *height = 0;
+ *pic = NULL;
+
+ //
+ // load the file
+ //
+ len = ri.FS_ReadFile( ( char * ) filename, &raw.v);
+ if (!raw.b || len < 0) {
+ return;
+ }
+
+ if((unsigned)len < sizeof(pcx_t))
+ {
+ ri.Printf (PRINT_ALL, "PCX truncated: %s\n", filename);
+ ri.FS_FreeFile (raw.v);
+ return;
+ }
+
+ //
+ // parse the PCX file
+ //
+ pcx = (pcx_t *)raw.b;
+ end = raw.b+len;
+
+ w = LittleShort(pcx->xmax)+1;
+ h = LittleShort(pcx->ymax)+1;
+ size = w*h;
+
+ if (pcx->manufacturer != 0x0a
+ || pcx->version != 5
+ || pcx->encoding != 1
+ || pcx->color_planes != 1
+ || pcx->bits_per_pixel != 8
+ || w >= 1024
+ || h >= 1024)
+ {
+ ri.Printf (PRINT_ALL, "Bad or unsupported pcx file %s (%dx%d@%d)\n", filename, w, h, pcx->bits_per_pixel);
+ return;
+ }
+
+ pix = pic8 = ri.Malloc ( size );
+
+ raw.b = pcx->data;
+ // FIXME: should use bytes_per_line but original q3 didn't do that either
+ while(pix < pic8+size)
+ {
+ if(runLength > 0) {
+ *pix++ = dataByte;
+ --runLength;
+ continue;
+ }
+
+ if(raw.b+1 > end)
+ break;
+ dataByte = *raw.b++;
+
+ if((dataByte & 0xC0) == 0xC0)
+ {
+ if(raw.b+1 > end)
+ break;
+ runLength = dataByte & 0x3F;
+ dataByte = *raw.b++;
+ }
+ else
+ runLength = 1;
+ }
+
+ if(pix < pic8+size)
+ {
+ ri.Printf (PRINT_ALL, "PCX file truncated: %s\n", filename);
+ ri.FS_FreeFile (pcx);
+ ri.Free (pic8);
+ }
+
+ if (raw.b-(byte*)pcx >= end - (byte*)769 || end[-769] != 0x0c)
+ {
+ ri.Printf (PRINT_ALL, "PCX missing palette: %s\n", filename);
+ ri.FS_FreeFile (pcx);
+ ri.Free (pic8);
+ return;
+ }
+
+ palette = end-768;
+
+ pix = out = ri.Malloc(4 * size );
+ for (i = 0 ; i < size ; i++)
+ {
+ unsigned char p = pic8[i];
+ pix[0] = palette[p*3];
+ pix[1] = palette[p*3 + 1];
+ pix[2] = palette[p*3 + 2];
+ pix[3] = 255;
+ pix += 4;
+ }
+
+ if (width)
+ *width = w;
+ if (height)
+ *height = h;
+
+ *pic = out;
+
+ ri.FS_FreeFile (pcx);
+ ri.Free (pic8);
+}
diff --git a/code/renderer/tr_image_png.c b/code/renderer/tr_image_png.c
new file mode 100644
index 0000000..ef44c16
--- /dev/null
+++ b/code/renderer/tr_image_png.c
@@ -0,0 +1,2488 @@
+/*
+===========================================================================
+ioquake3 png decoder
+Copyright (C) 2007,2008 Joerg Dietrich
+
+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 "tr_local.h"
+
+#include "../qcommon/puff.h"
+
+// we could limit the png size to a lower value here
+#ifndef INT_MAX
+#define INT_MAX 0x1fffffff
+#endif
+
+/*
+=================
+PNG LOADING
+=================
+*/
+
+/*
+ * Quake 3 image format : RGBA
+ */
+
+#define Q3IMAGE_BYTESPERPIXEL (4)
+
+/*
+ * PNG specifications
+ */
+
+/*
+ * The first 8 Bytes of every PNG-File are a fixed signature
+ * to identify the file as a PNG.
+ */
+
+#define PNG_Signature "\x89\x50\x4E\x47\xD\xA\x1A\xA"
+#define PNG_Signature_Size (8)
+
+/*
+ * After the signature diverse chunks follow.
+ * A chunk consists of a header and if Length
+ * is bigger than 0 a body and a CRC of the body follow.
+ */
+
+struct PNG_ChunkHeader
+{
+ uint32_t Length;
+ uint32_t Type;
+};
+
+#define PNG_ChunkHeader_Size (8)
+
+typedef uint32_t PNG_ChunkCRC;
+
+#define PNG_ChunkCRC_Size (4)
+
+/*
+ * We use the following ChunkTypes.
+ * All others are ignored.
+ */
+
+#define MAKE_CHUNKTYPE(a,b,c,d) (((a) << 24) | ((b) << 16) | ((c) << 8) | ((d)))
+
+#define PNG_ChunkType_IHDR MAKE_CHUNKTYPE('I', 'H', 'D', 'R')
+#define PNG_ChunkType_PLTE MAKE_CHUNKTYPE('P', 'L', 'T', 'E')
+#define PNG_ChunkType_IDAT MAKE_CHUNKTYPE('I', 'D', 'A', 'T')
+#define PNG_ChunkType_IEND MAKE_CHUNKTYPE('I', 'E', 'N', 'D')
+#define PNG_ChunkType_tRNS MAKE_CHUNKTYPE('t', 'R', 'N', 'S')
+
+/*
+ * Per specification the first chunk after the signature SHALL be IHDR.
+ */
+
+struct PNG_Chunk_IHDR
+{
+ uint32_t Width;
+ uint32_t Height;
+ uint8_t BitDepth;
+ uint8_t ColourType;
+ uint8_t CompressionMethod;
+ uint8_t FilterMethod;
+ uint8_t InterlaceMethod;
+};
+
+#define PNG_Chunk_IHDR_Size (13)
+
+/*
+ * ColourTypes
+ */
+
+#define PNG_ColourType_Grey (0)
+#define PNG_ColourType_True (2)
+#define PNG_ColourType_Indexed (3)
+#define PNG_ColourType_GreyAlpha (4)
+#define PNG_ColourType_TrueAlpha (6)
+
+/*
+ * number of colour components
+ *
+ * Grey : 1 grey
+ * True : 1 R, 1 G, 1 B
+ * Indexed : 1 index
+ * GreyAlpha : 1 grey, 1 alpha
+ * TrueAlpha : 1 R, 1 G, 1 B, 1 alpha
+ */
+
+#define PNG_NumColourComponents_Grey (1)
+#define PNG_NumColourComponents_True (3)
+#define PNG_NumColourComponents_Indexed (1)
+#define PNG_NumColourComponents_GreyAlpha (2)
+#define PNG_NumColourComponents_TrueAlpha (4)
+
+/*
+ * For the different ColourTypes
+ * different BitDepths are specified.
+ */
+
+#define PNG_BitDepth_1 ( 1)
+#define PNG_BitDepth_2 ( 2)
+#define PNG_BitDepth_4 ( 4)
+#define PNG_BitDepth_8 ( 8)
+#define PNG_BitDepth_16 (16)
+
+/*
+ * Only one valid CompressionMethod is standardized.
+ */
+
+#define PNG_CompressionMethod_0 (0)
+
+/*
+ * Only one valid FilterMethod is currently standardized.
+ */
+
+#define PNG_FilterMethod_0 (0)
+
+/*
+ * This FilterMethod defines 5 FilterTypes
+ */
+
+#define PNG_FilterType_None (0)
+#define PNG_FilterType_Sub (1)
+#define PNG_FilterType_Up (2)
+#define PNG_FilterType_Average (3)
+#define PNG_FilterType_Paeth (4)
+
+/*
+ * Two InterlaceMethods are standardized :
+ * 0 - NonInterlaced
+ * 1 - Interlaced
+ */
+
+#define PNG_InterlaceMethod_NonInterlaced (0)
+#define PNG_InterlaceMethod_Interlaced (1)
+
+/*
+ * The Adam7 interlace method uses 7 passes.
+ */
+
+#define PNG_Adam7_NumPasses (7)
+
+/*
+ * The compressed data starts with a header ...
+ */
+
+struct PNG_ZlibHeader
+{
+ uint8_t CompressionMethod;
+ uint8_t Flags;
+};
+
+#define PNG_ZlibHeader_Size (2)
+
+/*
+ * ... and is followed by a check value
+ */
+
+#define PNG_ZlibCheckValue_Size (4)
+
+/*
+ * Some support functions for buffered files follow.
+ */
+
+/*
+ * buffered file representation
+ */
+
+struct BufferedFile
+{
+ byte *Buffer;
+ int Length;
+ byte *Ptr;
+ int BytesLeft;
+};
+
+/*
+ * Read a file into a buffer.
+ */
+
+static struct BufferedFile *ReadBufferedFile(const char *name)
+{
+ struct BufferedFile *BF;
+ union {
+ byte *b;
+ void *v;
+ } buffer;
+
+ /*
+ * input verification
+ */
+
+ if(!name)
+ {
+ return(NULL);
+ }
+
+ /*
+ * Allocate control struct.
+ */
+
+ BF = ri.Malloc(sizeof(struct BufferedFile));
+ if(!BF)
+ {
+ return(NULL);
+ }
+
+ /*
+ * Initialize the structs components.
+ */
+
+ BF->Length = 0;
+ BF->Buffer = NULL;
+ BF->Ptr = NULL;
+ BF->BytesLeft = 0;
+
+ /*
+ * Read the file.
+ */
+
+ BF->Length = ri.FS_ReadFile((char *) name, &buffer.v);
+ BF->Buffer = buffer.b;
+
+ /*
+ * Did we get it? Is it big enough?
+ */
+
+ if(!(BF->Buffer && (BF->Length > 0)))
+ {
+ ri.Free(BF);
+
+ return(NULL);
+ }
+
+ /*
+ * Set the pointers and counters.
+ */
+
+ BF->Ptr = BF->Buffer;
+ BF->BytesLeft = BF->Length;
+
+ return(BF);
+}
+
+/*
+ * Close a buffered file.
+ */
+
+static void CloseBufferedFile(struct BufferedFile *BF)
+{
+ if(BF)
+ {
+ if(BF->Buffer)
+ {
+ ri.FS_FreeFile(BF->Buffer);
+ }
+
+ ri.Free(BF);
+ }
+}
+
+/*
+ * Get a pointer to the requested bytes.
+ */
+
+static void *BufferedFileRead(struct BufferedFile *BF, unsigned Length)
+{
+ void *RetVal;
+
+ /*
+ * input verification
+ */
+
+ if(!(BF && Length))
+ {
+ return(NULL);
+ }
+
+ /*
+ * not enough bytes left
+ */
+
+ if(Length > BF->BytesLeft)
+ {
+ return(NULL);
+ }
+
+ /*
+ * the pointer to the requested data
+ */
+
+ RetVal = BF->Ptr;
+
+ /*
+ * Raise the pointer and counter.
+ */
+
+ BF->Ptr += Length;
+ BF->BytesLeft -= Length;
+
+ return(RetVal);
+}
+
+/*
+ * Rewind the buffer.
+ */
+
+static qboolean BufferedFileRewind(struct BufferedFile *BF, unsigned Offset)
+{
+ unsigned BytesRead;
+
+ /*
+ * input verification
+ */
+
+ if(!BF)
+ {
+ return(qfalse);
+ }
+
+ /*
+ * special trick to rewind to the beginning of the buffer
+ */
+
+ if(Offset == (unsigned)-1)
+ {
+ BF->Ptr = BF->Buffer;
+ BF->BytesLeft = BF->Length;
+
+ return(qtrue);
+ }
+
+ /*
+ * How many bytes do we have already read?
+ */
+
+ BytesRead = BF->Ptr - BF->Buffer;
+
+ /*
+ * We can only rewind to the beginning of the BufferedFile.
+ */
+
+ if(Offset > BytesRead)
+ {
+ return(qfalse);
+ }
+
+ /*
+ * lower the pointer and counter.
+ */
+
+ BF->Ptr -= Offset;
+ BF->BytesLeft += Offset;
+
+ return(qtrue);
+}
+
+/*
+ * Skip some bytes.
+ */
+
+static qboolean BufferedFileSkip(struct BufferedFile *BF, unsigned Offset)
+{
+ /*
+ * input verification
+ */
+
+ if(!BF)
+ {
+ return(qfalse);
+ }
+
+ /*
+ * We can only skip to the end of the BufferedFile.
+ */
+
+ if(Offset > BF->BytesLeft)
+ {
+ return(qfalse);
+ }
+
+ /*
+ * lower the pointer and counter.
+ */
+
+ BF->Ptr += Offset;
+ BF->BytesLeft -= Offset;
+
+ return(qtrue);
+}
+
+/*
+ * Find a chunk
+ */
+
+static qboolean FindChunk(struct BufferedFile *BF, uint32_t ChunkType)
+{
+ struct PNG_ChunkHeader *CH;
+
+ uint32_t Length;
+ uint32_t Type;
+
+ /*
+ * input verification
+ */
+
+ if(!BF)
+ {
+ return(qfalse);
+ }
+
+ /*
+ * cycle trough the chunks
+ */
+
+ while(qtrue)
+ {
+ /*
+ * Read the chunk-header.
+ */
+
+ CH = BufferedFileRead(BF, PNG_ChunkHeader_Size);
+ if(!CH)
+ {
+ return(qfalse);
+ }
+
+ /*
+ * Do not swap the original types
+ * they might be needed later.
+ */
+
+ Length = BigLong(CH->Length);
+ Type = BigLong(CH->Type);
+
+ /*
+ * We found it!
+ */
+
+ if(Type == ChunkType)
+ {
+ /*
+ * Rewind to the start of the chunk.
+ */
+
+ BufferedFileRewind(BF, PNG_ChunkHeader_Size);
+
+ break;
+ }
+ else
+ {
+ /*
+ * Skip the rest of the chunk.
+ */
+
+ if(Length)
+ {
+ if(!BufferedFileSkip(BF, Length + PNG_ChunkCRC_Size))
+ {
+ return(qfalse);
+ }
+ }
+ }
+ }
+
+ return(qtrue);
+}
+
+/*
+ * Decompress all IDATs
+ */
+
+static uint32_t DecompressIDATs(struct BufferedFile *BF, uint8_t **Buffer)
+{
+ uint8_t *DecompressedData;
+ uint32_t DecompressedDataLength;
+
+ uint8_t *CompressedData;
+ uint8_t *CompressedDataPtr;
+ uint32_t CompressedDataLength;
+
+ struct PNG_ChunkHeader *CH;
+
+ uint32_t Length;
+ uint32_t Type;
+
+ int BytesToRewind;
+
+ int32_t puffResult;
+ uint8_t *puffDest;
+ uint32_t puffDestLen;
+ uint8_t *puffSrc;
+ uint32_t puffSrcLen;
+
+ /*
+ * input verification
+ */
+
+ if(!(BF && Buffer))
+ {
+ return(-1);
+ }
+
+ /*
+ * some zeroing
+ */
+
+ DecompressedData = NULL;
+ DecompressedDataLength = 0;
+ *Buffer = DecompressedData;
+
+ CompressedData = NULL;
+ CompressedDataLength = 0;
+
+ BytesToRewind = 0;
+
+ /*
+ * Find the first IDAT chunk.
+ */
+
+ if(!FindChunk(BF, PNG_ChunkType_IDAT))
+ {
+ return(-1);
+ }
+
+ /*
+ * Count the size of the uncompressed data
+ */
+
+ while(qtrue)
+ {
+ /*
+ * Read chunk header
+ */
+
+ CH = BufferedFileRead(BF, PNG_ChunkHeader_Size);
+ if(!CH)
+ {
+ /*
+ * Rewind to the start of this adventure
+ * and return unsuccessfull
+ */
+
+ BufferedFileRewind(BF, BytesToRewind);
+
+ return(-1);
+ }
+
+ /*
+ * Length and Type of chunk
+ */
+
+ Length = BigLong(CH->Length);
+ Type = BigLong(CH->Type);
+
+ /*
+ * We have reached the end of the IDAT chunks
+ */
+
+ if(!(Type == PNG_ChunkType_IDAT))
+ {
+ BufferedFileRewind(BF, PNG_ChunkHeader_Size);
+
+ break;
+ }
+
+ /*
+ * Add chunk header to count.
+ */
+
+ BytesToRewind += PNG_ChunkHeader_Size;
+
+ /*
+ * Skip to next chunk
+ */
+
+ if(Length)
+ {
+ if(!BufferedFileSkip(BF, Length + PNG_ChunkCRC_Size))
+ {
+ BufferedFileRewind(BF, BytesToRewind);
+
+ return(-1);
+ }
+
+ BytesToRewind += Length + PNG_ChunkCRC_Size;
+ CompressedDataLength += Length;
+ }
+ }
+
+ BufferedFileRewind(BF, BytesToRewind);
+
+ CompressedData = ri.Malloc(CompressedDataLength);
+ if(!CompressedData)
+ {
+ return(-1);
+ }
+
+ CompressedDataPtr = CompressedData;
+
+ /*
+ * Collect the compressed Data
+ */
+
+ while(qtrue)
+ {
+ /*
+ * Read chunk header
+ */
+
+ CH = BufferedFileRead(BF, PNG_ChunkHeader_Size);
+ if(!CH)
+ {
+ ri.Free(CompressedData);
+
+ return(-1);
+ }
+
+ /*
+ * Length and Type of chunk
+ */
+
+ Length = BigLong(CH->Length);
+ Type = BigLong(CH->Type);
+
+ /*
+ * We have reached the end of the IDAT chunks
+ */
+
+ if(!(Type == PNG_ChunkType_IDAT))
+ {
+ BufferedFileRewind(BF, PNG_ChunkHeader_Size);
+
+ break;
+ }
+
+ /*
+ * Copy the Data
+ */
+
+ if(Length)
+ {
+ uint8_t *OrigCompressedData;
+
+ OrigCompressedData = BufferedFileRead(BF, Length);
+ if(!OrigCompressedData)
+ {
+ ri.Free(CompressedData);
+
+ return(-1);
+ }
+
+ if(!BufferedFileSkip(BF, PNG_ChunkCRC_Size))
+ {
+ ri.Free(CompressedData);
+
+ return(-1);
+ }
+
+ memcpy(CompressedDataPtr, OrigCompressedData, Length);
+ CompressedDataPtr += Length;
+ }
+ }
+
+ /*
+ * Let puff() calculate the decompressed data length.
+ */
+
+ puffDest = NULL;
+ puffDestLen = 0;
+
+ /*
+ * The zlib header and checkvalue don't belong to the compressed data.
+ */
+
+ puffSrc = CompressedData + PNG_ZlibHeader_Size;
+ puffSrcLen = CompressedDataLength - PNG_ZlibHeader_Size - PNG_ZlibCheckValue_Size;
+
+ /*
+ * first puff() to calculate the size of the uncompressed data
+ */
+
+ puffResult = puff(puffDest, &puffDestLen, puffSrc, &puffSrcLen);
+ if(!((puffResult == 0) && (puffDestLen > 0)))
+ {
+ ri.Free(CompressedData);
+
+ return(-1);
+ }
+
+ /*
+ * Allocate the buffer for the uncompressed data.
+ */
+
+ DecompressedData = ri.Malloc(puffDestLen);
+ if(!DecompressedData)
+ {
+ ri.Free(CompressedData);
+
+ return(-1);
+ }
+
+ /*
+ * Set the input again in case something was changed by the last puff() .
+ */
+
+ puffDest = DecompressedData;
+ puffSrc = CompressedData + PNG_ZlibHeader_Size;
+ puffSrcLen = CompressedDataLength - PNG_ZlibHeader_Size - PNG_ZlibCheckValue_Size;
+
+ /*
+ * decompression puff()
+ */
+
+ puffResult = puff(puffDest, &puffDestLen, puffSrc, &puffSrcLen);
+
+ /*
+ * The compressed data is not needed anymore.
+ */
+
+ ri.Free(CompressedData);
+
+ /*
+ * Check if the last puff() was successfull.
+ */
+
+ if(!((puffResult == 0) && (puffDestLen > 0)))
+ {
+ ri.Free(DecompressedData);
+
+ return(-1);
+ }
+
+ /*
+ * Set the output of this function.
+ */
+
+ DecompressedDataLength = puffDestLen;
+ *Buffer = DecompressedData;
+
+ return(DecompressedDataLength);
+}
+
+/*
+ * the Paeth predictor
+ */
+
+static uint8_t PredictPaeth(uint8_t a, uint8_t b, uint8_t c)
+{
+ /*
+ * a == Left
+ * b == Up
+ * c == UpLeft
+ */
+
+ uint8_t Pr;
+ int p;
+ int pa, pb, pc;
+
+ Pr = 0;
+
+ p = ((int) a) + ((int) b) - ((int) c);
+ pa = abs(p - ((int) a));
+ pb = abs(p - ((int) b));
+ pc = abs(p - ((int) c));
+
+ if((pa <= pb) && (pa <= pc))
+ {
+ Pr = a;
+ }
+ else if(pb <= pc)
+ {
+ Pr = b;
+ }
+ else
+ {
+ Pr = c;
+ }
+
+ return(Pr);
+
+}
+
+/*
+ * Reverse the filters.
+ */
+
+static qboolean UnfilterImage(uint8_t *DecompressedData,
+ uint32_t ImageHeight,
+ uint32_t BytesPerScanline,
+ uint32_t BytesPerPixel)
+{
+ uint8_t *DecompPtr;
+ uint8_t FilterType;
+ uint8_t *PixelLeft, *PixelUp, *PixelUpLeft;
+ uint32_t w, h, p;
+
+ /*
+ * some zeros for the filters
+ */
+
+ uint8_t Zeros[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+
+ /*
+ * input verification
+ */
+
+ if(!(DecompressedData && BytesPerPixel))
+ {
+ return(qfalse);
+ }
+
+ /*
+ * ImageHeight and BytesPerScanline can be zero in small interlaced images.
+ */
+
+ if((!ImageHeight) || (!BytesPerScanline))
+ {
+ return(qtrue);
+ }
+
+ /*
+ * Set the pointer to the start of the decompressed Data.
+ */
+
+ DecompPtr = DecompressedData;
+
+ /*
+ * Un-filtering is done in place.
+ */
+
+ /*
+ * Go trough all scanlines.
+ */
+
+ for(h = 0; h < ImageHeight; h++)
+ {
+ /*
+ * Every scanline starts with a FilterType byte.
+ */
+
+ FilterType = *DecompPtr;
+ DecompPtr++;
+
+ /*
+ * Left pixel of the first byte in a scanline is zero.
+ */
+
+ PixelLeft = Zeros;
+
+ /*
+ * Set PixelUp to previous line only if we are on the second line or above.
+ *
+ * Plus one byte for the FilterType
+ */
+
+ if(h > 0)
+ {
+ PixelUp = DecompPtr - (BytesPerScanline + 1);
+ }
+ else
+ {
+ PixelUp = Zeros;
+ }
+
+ /*
+ * The pixel left to the first pixel of the previous scanline is zero too.
+ */
+
+ PixelUpLeft = Zeros;
+
+ /*
+ * Cycle trough all pixels of the scanline.
+ */
+
+ for(w = 0; w < (BytesPerScanline / BytesPerPixel); w++)
+ {
+ /*
+ * Cycle trough the bytes of the pixel.
+ */
+
+ for(p = 0; p < BytesPerPixel; p++)
+ {
+ switch(FilterType)
+ {
+ case PNG_FilterType_None :
+ {
+ /*
+ * The byte is unfiltered.
+ */
+
+ break;
+ }
+
+ case PNG_FilterType_Sub :
+ {
+ DecompPtr[p] += PixelLeft[p];
+
+ break;
+ }
+
+ case PNG_FilterType_Up :
+ {
+ DecompPtr[p] += PixelUp[p];
+
+ break;
+ }
+
+ case PNG_FilterType_Average :
+ {
+ DecompPtr[p] += ((uint8_t) ((((uint16_t) PixelLeft[p]) + ((uint16_t) PixelUp[p])) / 2));
+
+ break;
+ }
+
+ case PNG_FilterType_Paeth :
+ {
+ DecompPtr[p] += PredictPaeth(PixelLeft[p], PixelUp[p], PixelUpLeft[p]);
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+ }
+
+ PixelLeft = DecompPtr;
+
+ /*
+ * We only have a upleft pixel if we are on the second line or above.
+ */
+
+ if(h > 0)
+ {
+ PixelUpLeft = DecompPtr - (BytesPerScanline + 1);
+ }
+
+ /*
+ * Skip to the next pixel.
+ */
+
+ DecompPtr += BytesPerPixel;
+
+ /*
+ * We only have a previous line if we are on the second line and above.
+ */
+
+ if(h > 0)
+ {
+ PixelUp = DecompPtr - (BytesPerScanline + 1);
+ }
+ }
+ }
+
+ return(qtrue);
+}
+
+/*
+ * Convert a raw input pixel to Quake 3 RGA format.
+ */
+
+static qboolean ConvertPixel(struct PNG_Chunk_IHDR *IHDR,
+ byte *OutPtr,
+ uint8_t *DecompPtr,
+ qboolean HasTransparentColour,
+ uint8_t *TransparentColour,
+ uint8_t *OutPal)
+{
+ /*
+ * input verification
+ */
+
+ if(!(IHDR && OutPtr && DecompPtr && TransparentColour && OutPal))
+ {
+ return(qfalse);
+ }
+
+ switch(IHDR->ColourType)
+ {
+ case PNG_ColourType_Grey :
+ {
+ switch(IHDR->BitDepth)
+ {
+ case PNG_BitDepth_1 :
+ case PNG_BitDepth_2 :
+ case PNG_BitDepth_4 :
+ {
+ uint8_t Step;
+ uint8_t GreyValue;
+
+ Step = 0xFF / ((1 << IHDR->BitDepth) - 1);
+
+ GreyValue = DecompPtr[0] * Step;
+
+ OutPtr[0] = GreyValue;
+ OutPtr[1] = GreyValue;
+ OutPtr[2] = GreyValue;
+ OutPtr[3] = 0xFF;
+
+ /*
+ * Grey supports full transparency for one specified colour
+ */
+
+ if(HasTransparentColour)
+ {
+ if(TransparentColour[1] == DecompPtr[0])
+ {
+ OutPtr[3] = 0x00;
+ }
+ }
+
+
+ break;
+ }
+
+ case PNG_BitDepth_8 :
+ case PNG_BitDepth_16 :
+ {
+ OutPtr[0] = DecompPtr[0];
+ OutPtr[1] = DecompPtr[0];
+ OutPtr[2] = DecompPtr[0];
+ OutPtr[3] = 0xFF;
+
+ /*
+ * Grey supports full transparency for one specified colour
+ */
+
+ if(HasTransparentColour)
+ {
+ if(IHDR->BitDepth == PNG_BitDepth_8)
+ {
+ if(TransparentColour[1] == DecompPtr[0])
+ {
+ OutPtr[3] = 0x00;
+ }
+ }
+ else
+ {
+ if((TransparentColour[0] == DecompPtr[0]) && (TransparentColour[1] == DecompPtr[1]))
+ {
+ OutPtr[3] = 0x00;
+ }
+ }
+ }
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+
+ break;
+ }
+
+ case PNG_ColourType_True :
+ {
+ switch(IHDR->BitDepth)
+ {
+ case PNG_BitDepth_8 :
+ {
+ OutPtr[0] = DecompPtr[0];
+ OutPtr[1] = DecompPtr[1];
+ OutPtr[2] = DecompPtr[2];
+ OutPtr[3] = 0xFF;
+
+ /*
+ * True supports full transparency for one specified colour
+ */
+
+ if(HasTransparentColour)
+ {
+ if((TransparentColour[1] == DecompPtr[0]) &&
+ (TransparentColour[3] == DecompPtr[1]) &&
+ (TransparentColour[5] == DecompPtr[2]))
+ {
+ OutPtr[3] = 0x00;
+ }
+ }
+
+ break;
+ }
+
+ case PNG_BitDepth_16 :
+ {
+ /*
+ * We use only the upper byte.
+ */
+
+ OutPtr[0] = DecompPtr[0];
+ OutPtr[1] = DecompPtr[2];
+ OutPtr[2] = DecompPtr[4];
+ OutPtr[3] = 0xFF;
+
+ /*
+ * True supports full transparency for one specified colour
+ */
+
+ if(HasTransparentColour)
+ {
+ if((TransparentColour[0] == DecompPtr[0]) && (TransparentColour[1] == DecompPtr[1]) &&
+ (TransparentColour[2] == DecompPtr[2]) && (TransparentColour[3] == DecompPtr[3]) &&
+ (TransparentColour[4] == DecompPtr[4]) && (TransparentColour[5] == DecompPtr[5]))
+ {
+ OutPtr[3] = 0x00;
+ }
+ }
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+
+ break;
+ }
+
+ case PNG_ColourType_Indexed :
+ {
+ OutPtr[0] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 0];
+ OutPtr[1] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 1];
+ OutPtr[2] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 2];
+ OutPtr[3] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 3];
+
+ break;
+ }
+
+ case PNG_ColourType_GreyAlpha :
+ {
+ switch(IHDR->BitDepth)
+ {
+ case PNG_BitDepth_8 :
+ {
+ OutPtr[0] = DecompPtr[0];
+ OutPtr[1] = DecompPtr[0];
+ OutPtr[2] = DecompPtr[0];
+ OutPtr[3] = DecompPtr[1];
+
+ break;
+ }
+
+ case PNG_BitDepth_16 :
+ {
+ /*
+ * We use only the upper byte.
+ */
+
+ OutPtr[0] = DecompPtr[0];
+ OutPtr[1] = DecompPtr[0];
+ OutPtr[2] = DecompPtr[0];
+ OutPtr[3] = DecompPtr[2];
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+
+ break;
+ }
+
+ case PNG_ColourType_TrueAlpha :
+ {
+ switch(IHDR->BitDepth)
+ {
+ case PNG_BitDepth_8 :
+ {
+ OutPtr[0] = DecompPtr[0];
+ OutPtr[1] = DecompPtr[1];
+ OutPtr[2] = DecompPtr[2];
+ OutPtr[3] = DecompPtr[3];
+
+ break;
+ }
+
+ case PNG_BitDepth_16 :
+ {
+ /*
+ * We use only the upper byte.
+ */
+
+ OutPtr[0] = DecompPtr[0];
+ OutPtr[1] = DecompPtr[2];
+ OutPtr[2] = DecompPtr[4];
+ OutPtr[3] = DecompPtr[6];
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+
+ return(qtrue);
+}
+
+
+/*
+ * Decode a non-interlaced image.
+ */
+
+static qboolean DecodeImageNonInterlaced(struct PNG_Chunk_IHDR *IHDR,
+ byte *OutBuffer,
+ uint8_t *DecompressedData,
+ uint32_t DecompressedDataLength,
+ qboolean HasTransparentColour,
+ uint8_t *TransparentColour,
+ uint8_t *OutPal)
+{
+ uint32_t IHDR_Width;
+ uint32_t IHDR_Height;
+ uint32_t BytesPerScanline, BytesPerPixel, PixelsPerByte;
+ uint32_t w, h, p;
+ byte *OutPtr;
+ uint8_t *DecompPtr;
+
+ /*
+ * input verification
+ */
+
+ if(!(IHDR && OutBuffer && DecompressedData && DecompressedDataLength && TransparentColour && OutPal))
+ {
+ return(qfalse);
+ }
+
+ /*
+ * byte swapping
+ */
+
+ IHDR_Width = BigLong(IHDR->Width);
+ IHDR_Height = BigLong(IHDR->Height);
+
+ /*
+ * information for un-filtering
+ */
+
+ switch(IHDR->ColourType)
+ {
+ case PNG_ColourType_Grey :
+ {
+ switch(IHDR->BitDepth)
+ {
+ case PNG_BitDepth_1 :
+ case PNG_BitDepth_2 :
+ case PNG_BitDepth_4 :
+ {
+ BytesPerPixel = 1;
+ PixelsPerByte = 8 / IHDR->BitDepth;
+
+ break;
+ }
+
+ case PNG_BitDepth_8 :
+ case PNG_BitDepth_16 :
+ {
+ BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_Grey;
+ PixelsPerByte = 1;
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+
+ break;
+ }
+
+ case PNG_ColourType_True :
+ {
+ switch(IHDR->BitDepth)
+ {
+ case PNG_BitDepth_8 :
+ case PNG_BitDepth_16 :
+ {
+ BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_True;
+ PixelsPerByte = 1;
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+
+ break;
+ }
+
+ case PNG_ColourType_Indexed :
+ {
+ switch(IHDR->BitDepth)
+ {
+ case PNG_BitDepth_1 :
+ case PNG_BitDepth_2 :
+ case PNG_BitDepth_4 :
+ {
+ BytesPerPixel = 1;
+ PixelsPerByte = 8 / IHDR->BitDepth;
+
+ break;
+ }
+
+ case PNG_BitDepth_8 :
+ {
+ BytesPerPixel = PNG_NumColourComponents_Indexed;
+ PixelsPerByte = 1;
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+
+ break;
+ }
+
+ case PNG_ColourType_GreyAlpha :
+ {
+ switch(IHDR->BitDepth)
+ {
+ case PNG_BitDepth_8 :
+ case PNG_BitDepth_16 :
+ {
+ BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_GreyAlpha;
+ PixelsPerByte = 1;
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+
+ break;
+ }
+
+ case PNG_ColourType_TrueAlpha :
+ {
+ switch(IHDR->BitDepth)
+ {
+ case PNG_BitDepth_8 :
+ case PNG_BitDepth_16 :
+ {
+ BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_TrueAlpha;
+ PixelsPerByte = 1;
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+
+ /*
+ * Calculate the size of one scanline
+ */
+
+ BytesPerScanline = (IHDR_Width * BytesPerPixel + (PixelsPerByte - 1)) / PixelsPerByte;
+
+ /*
+ * Check if we have enough data for the whole image.
+ */
+
+ if(!(DecompressedDataLength == ((BytesPerScanline + 1) * IHDR_Height)))
+ {
+ return(qfalse);
+ }
+
+ /*
+ * Unfilter the image.
+ */
+
+ if(!UnfilterImage(DecompressedData, IHDR_Height, BytesPerScanline, BytesPerPixel))
+ {
+ return(qfalse);
+ }
+
+ /*
+ * Set the working pointers to the beginning of the buffers.
+ */
+
+ OutPtr = OutBuffer;
+ DecompPtr = DecompressedData;
+
+ /*
+ * Create the output image.
+ */
+
+ for(h = 0; h < IHDR_Height; h++)
+ {
+ /*
+ * Count the pixels on the scanline for those multipixel bytes
+ */
+
+ uint32_t CurrPixel;
+
+ /*
+ * skip FilterType
+ */
+
+ DecompPtr++;
+
+ /*
+ * Reset the pixel count.
+ */
+
+ CurrPixel = 0;
+
+ for(w = 0; w < (BytesPerScanline / BytesPerPixel); w++)
+ {
+ if(PixelsPerByte > 1)
+ {
+ uint8_t Mask;
+ uint32_t Shift;
+ uint8_t SinglePixel;
+
+ for(p = 0; p < PixelsPerByte; p++)
+ {
+ if(CurrPixel < IHDR_Width)
+ {
+ Mask = (1 << IHDR->BitDepth) - 1;
+ Shift = (PixelsPerByte - 1 - p) * IHDR->BitDepth;
+
+ SinglePixel = ((DecompPtr[0] & (Mask << Shift)) >> Shift);
+
+ if(!ConvertPixel(IHDR, OutPtr, &SinglePixel, HasTransparentColour, TransparentColour, OutPal))
+ {
+ return(qfalse);
+ }
+
+ OutPtr += Q3IMAGE_BYTESPERPIXEL;
+ CurrPixel++;
+ }
+ }
+
+ }
+ else
+ {
+ if(!ConvertPixel(IHDR, OutPtr, DecompPtr, HasTransparentColour, TransparentColour, OutPal))
+ {
+ return(qfalse);
+ }
+
+
+ OutPtr += Q3IMAGE_BYTESPERPIXEL;
+ }
+
+ DecompPtr += BytesPerPixel;
+ }
+ }
+
+ return(qtrue);
+}
+
+/*
+ * Decode an interlaced image.
+ */
+
+static qboolean DecodeImageInterlaced(struct PNG_Chunk_IHDR *IHDR,
+ byte *OutBuffer,
+ uint8_t *DecompressedData,
+ uint32_t DecompressedDataLength,
+ qboolean HasTransparentColour,
+ uint8_t *TransparentColour,
+ uint8_t *OutPal)
+{
+ uint32_t IHDR_Width;
+ uint32_t IHDR_Height;
+ uint32_t BytesPerScanline[PNG_Adam7_NumPasses], BytesPerPixel, PixelsPerByte;
+ uint32_t PassWidth[PNG_Adam7_NumPasses], PassHeight[PNG_Adam7_NumPasses];
+ uint32_t WSkip[PNG_Adam7_NumPasses], WOffset[PNG_Adam7_NumPasses], HSkip[PNG_Adam7_NumPasses], HOffset[PNG_Adam7_NumPasses];
+ uint32_t w, h, p, a;
+ byte *OutPtr;
+ uint8_t *DecompPtr;
+ uint32_t TargetLength;
+
+ /*
+ * input verification
+ */
+
+ if(!(IHDR && OutBuffer && DecompressedData && DecompressedDataLength && TransparentColour && OutPal))
+ {
+ return(qfalse);
+ }
+
+ /*
+ * byte swapping
+ */
+
+ IHDR_Width = BigLong(IHDR->Width);
+ IHDR_Height = BigLong(IHDR->Height);
+
+ /*
+ * Skip and Offset for the passes.
+ */
+
+ WSkip[0] = 8;
+ WOffset[0] = 0;
+ HSkip[0] = 8;
+ HOffset[0] = 0;
+
+ WSkip[1] = 8;
+ WOffset[1] = 4;
+ HSkip[1] = 8;
+ HOffset[1] = 0;
+
+ WSkip[2] = 4;
+ WOffset[2] = 0;
+ HSkip[2] = 8;
+ HOffset[2] = 4;
+
+ WSkip[3] = 4;
+ WOffset[3] = 2;
+ HSkip[3] = 4;
+ HOffset[3] = 0;
+
+ WSkip[4] = 2;
+ WOffset[4] = 0;
+ HSkip[4] = 4;
+ HOffset[4] = 2;
+
+ WSkip[5] = 2;
+ WOffset[5] = 1;
+ HSkip[5] = 2;
+ HOffset[5] = 0;
+
+ WSkip[6] = 1;
+ WOffset[6] = 0;
+ HSkip[6] = 2;
+ HOffset[6] = 1;
+
+ /*
+ * Calculate the sizes of the passes.
+ */
+
+ PassWidth[0] = (IHDR_Width + 7) / 8;
+ PassHeight[0] = (IHDR_Height + 7) / 8;
+
+ PassWidth[1] = (IHDR_Width + 3) / 8;
+ PassHeight[1] = (IHDR_Height + 7) / 8;
+
+ PassWidth[2] = (IHDR_Width + 3) / 4;
+ PassHeight[2] = (IHDR_Height + 3) / 8;
+
+ PassWidth[3] = (IHDR_Width + 1) / 4;
+ PassHeight[3] = (IHDR_Height + 3) / 4;
+
+ PassWidth[4] = (IHDR_Width + 1) / 2;
+ PassHeight[4] = (IHDR_Height + 1) / 4;
+
+ PassWidth[5] = (IHDR_Width + 0) / 2;
+ PassHeight[5] = (IHDR_Height + 1) / 2;
+
+ PassWidth[6] = (IHDR_Width + 0) / 1;
+ PassHeight[6] = (IHDR_Height + 0) / 2;
+
+ /*
+ * information for un-filtering
+ */
+
+ switch(IHDR->ColourType)
+ {
+ case PNG_ColourType_Grey :
+ {
+ switch(IHDR->BitDepth)
+ {
+ case PNG_BitDepth_1 :
+ case PNG_BitDepth_2 :
+ case PNG_BitDepth_4 :
+ {
+ BytesPerPixel = 1;
+ PixelsPerByte = 8 / IHDR->BitDepth;
+
+ break;
+ }
+
+ case PNG_BitDepth_8 :
+ case PNG_BitDepth_16 :
+ {
+ BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_Grey;
+ PixelsPerByte = 1;
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+
+ break;
+ }
+
+ case PNG_ColourType_True :
+ {
+ switch(IHDR->BitDepth)
+ {
+ case PNG_BitDepth_8 :
+ case PNG_BitDepth_16 :
+ {
+ BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_True;
+ PixelsPerByte = 1;
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+
+ break;
+ }
+
+ case PNG_ColourType_Indexed :
+ {
+ switch(IHDR->BitDepth)
+ {
+ case PNG_BitDepth_1 :
+ case PNG_BitDepth_2 :
+ case PNG_BitDepth_4 :
+ {
+ BytesPerPixel = 1;
+ PixelsPerByte = 8 / IHDR->BitDepth;
+
+ break;
+ }
+
+ case PNG_BitDepth_8 :
+ {
+ BytesPerPixel = PNG_NumColourComponents_Indexed;
+ PixelsPerByte = 1;
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+
+ break;
+ }
+
+ case PNG_ColourType_GreyAlpha :
+ {
+ switch(IHDR->BitDepth)
+ {
+ case PNG_BitDepth_8 :
+ case PNG_BitDepth_16 :
+ {
+ BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_GreyAlpha;
+ PixelsPerByte = 1;
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+
+ break;
+ }
+
+ case PNG_ColourType_TrueAlpha :
+ {
+ switch(IHDR->BitDepth)
+ {
+ case PNG_BitDepth_8 :
+ case PNG_BitDepth_16 :
+ {
+ BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_TrueAlpha;
+ PixelsPerByte = 1;
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+
+ break;
+ }
+
+ default :
+ {
+ return(qfalse);
+ }
+ }
+
+ /*
+ * Calculate the size of the scanlines per pass
+ */
+
+ for(a = 0; a < PNG_Adam7_NumPasses; a++)
+ {
+ BytesPerScanline[a] = (PassWidth[a] * BytesPerPixel + (PixelsPerByte - 1)) / PixelsPerByte;
+ }
+
+ /*
+ * Calculate the size of all passes
+ */
+
+ TargetLength = 0;
+
+ for(a = 0; a < PNG_Adam7_NumPasses; a++)
+ {
+ TargetLength += ((BytesPerScanline[a] + (BytesPerScanline[a] ? 1 : 0)) * PassHeight[a]);
+ }
+
+ /*
+ * Check if we have enough data for the whole image.
+ */
+
+ if(!(DecompressedDataLength == TargetLength))
+ {
+ return(qfalse);
+ }
+
+ /*
+ * Unfilter the image.
+ */
+
+ DecompPtr = DecompressedData;
+
+ for(a = 0; a < PNG_Adam7_NumPasses; a++)
+ {
+ if(!UnfilterImage(DecompPtr, PassHeight[a], BytesPerScanline[a], BytesPerPixel))
+ {
+ return(qfalse);
+ }
+
+ DecompPtr += ((BytesPerScanline[a] + (BytesPerScanline[a] ? 1 : 0)) * PassHeight[a]);
+ }
+
+ /*
+ * Set the working pointers to the beginning of the buffers.
+ */
+
+ DecompPtr = DecompressedData;
+
+ /*
+ * Create the output image.
+ */
+
+ for(a = 0; a < PNG_Adam7_NumPasses; a++)
+ {
+ for(h = 0; h < PassHeight[a]; h++)
+ {
+ /*
+ * Count the pixels on the scanline for those multipixel bytes
+ */
+
+ uint32_t CurrPixel;
+
+ /*
+ * skip FilterType
+ * but only when the pass has a width bigger than zero
+ */
+
+ if(BytesPerScanline[a])
+ {
+ DecompPtr++;
+ }
+
+ /*
+ * Reset the pixel count.
+ */
+
+ CurrPixel = 0;
+
+ for(w = 0; w < (BytesPerScanline[a] / BytesPerPixel); w++)
+ {
+ if(PixelsPerByte > 1)
+ {
+ uint8_t Mask;
+ uint32_t Shift;
+ uint8_t SinglePixel;
+
+ for(p = 0; p < PixelsPerByte; p++)
+ {
+ if(CurrPixel < PassWidth[a])
+ {
+ Mask = (1 << IHDR->BitDepth) - 1;
+ Shift = (PixelsPerByte - 1 - p) * IHDR->BitDepth;
+
+ SinglePixel = ((DecompPtr[0] & (Mask << Shift)) >> Shift);
+
+ OutPtr = OutBuffer + (((((h * HSkip[a]) + HOffset[a]) * IHDR_Width) + ((CurrPixel * WSkip[a]) + WOffset[a])) * Q3IMAGE_BYTESPERPIXEL);
+
+ if(!ConvertPixel(IHDR, OutPtr, &SinglePixel, HasTransparentColour, TransparentColour, OutPal))
+ {
+ return(qfalse);
+ }
+
+ CurrPixel++;
+ }
+ }
+
+ }
+ else
+ {
+ OutPtr = OutBuffer + (((((h * HSkip[a]) + HOffset[a]) * IHDR_Width) + ((w * WSkip[a]) + WOffset[a])) * Q3IMAGE_BYTESPERPIXEL);
+
+ if(!ConvertPixel(IHDR, OutPtr, DecompPtr, HasTransparentColour, TransparentColour, OutPal))
+ {
+ return(qfalse);
+ }
+ }
+
+ DecompPtr += BytesPerPixel;
+ }
+ }
+ }
+
+ return(qtrue);
+}
+
+/*
+ * The PNG loader
+ */
+
+void R_LoadPNG(const char *name, byte **pic, int *width, int *height)
+{
+ struct BufferedFile *ThePNG;
+ byte *OutBuffer;
+ uint8_t *Signature;
+ struct PNG_ChunkHeader *CH;
+ uint32_t ChunkHeaderLength;
+ uint32_t ChunkHeaderType;
+ struct PNG_Chunk_IHDR *IHDR;
+ uint32_t IHDR_Width;
+ uint32_t IHDR_Height;
+ PNG_ChunkCRC *CRC;
+ uint8_t *InPal;
+ uint8_t *DecompressedData;
+ uint32_t DecompressedDataLength;
+ uint32_t i;
+
+ /*
+ * palette with 256 RGBA entries
+ */
+
+ uint8_t OutPal[1024];
+
+ /*
+ * transparent colour from the tRNS chunk
+ */
+
+ qboolean HasTransparentColour = qfalse;
+ uint8_t TransparentColour[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+
+ /*
+ * input verification
+ */
+
+ if(!(name && pic))
+ {
+ return;
+ }
+
+ /*
+ * Zero out return values.
+ */
+
+ *pic = NULL;
+
+ if(width)
+ {
+ *width = 0;
+ }
+
+ if(height)
+ {
+ *height = 0;
+ }
+
+ /*
+ * Read the file.
+ */
+
+ ThePNG = ReadBufferedFile(name);
+ if(!ThePNG)
+ {
+ return;
+ }
+
+ /*
+ * Read the siganture of the file.
+ */
+
+ Signature = BufferedFileRead(ThePNG, PNG_Signature_Size);
+ if(!Signature)
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Is it a PNG?
+ */
+
+ if(memcmp(Signature, PNG_Signature, PNG_Signature_Size))
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Read the first chunk-header.
+ */
+
+ CH = BufferedFileRead(ThePNG, PNG_ChunkHeader_Size);
+ if(!CH)
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * PNG multi-byte types are in Big Endian
+ */
+
+ ChunkHeaderLength = BigLong(CH->Length);
+ ChunkHeaderType = BigLong(CH->Type);
+
+ /*
+ * Check if the first chunk is an IHDR.
+ */
+
+ if(!((ChunkHeaderType == PNG_ChunkType_IHDR) && (ChunkHeaderLength == PNG_Chunk_IHDR_Size)))
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Read the IHDR.
+ */
+
+ IHDR = BufferedFileRead(ThePNG, PNG_Chunk_IHDR_Size);
+ if(!IHDR)
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Read the CRC for IHDR
+ */
+
+ CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size);
+ if(!CRC)
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Here we could check the CRC if we wanted to.
+ */
+
+ /*
+ * multi-byte type swapping
+ */
+
+ IHDR_Width = BigLong(IHDR->Width);
+ IHDR_Height = BigLong(IHDR->Height);
+
+ /*
+ * Check if Width and Height are valid.
+ */
+
+ if(!((IHDR_Width > 0) && (IHDR_Height > 0))
+ || IHDR_Width > INT_MAX / Q3IMAGE_BYTESPERPIXEL / IHDR_Height)
+ {
+ CloseBufferedFile(ThePNG);
+
+ Com_Printf(S_COLOR_YELLOW "%s: invalid image size\n", name);
+
+ return;
+ }
+
+ /*
+ * Do we need to check if the dimensions of the image are valid for Quake3?
+ */
+
+ /*
+ * Check if CompressionMethod and FilterMethod are valid.
+ */
+
+ if(!((IHDR->CompressionMethod == PNG_CompressionMethod_0) && (IHDR->FilterMethod == PNG_FilterMethod_0)))
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Check if InterlaceMethod is valid.
+ */
+
+ if(!((IHDR->InterlaceMethod == PNG_InterlaceMethod_NonInterlaced) || (IHDR->InterlaceMethod == PNG_InterlaceMethod_Interlaced)))
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Read palette for an indexed image.
+ */
+
+ if(IHDR->ColourType == PNG_ColourType_Indexed)
+ {
+ /*
+ * We need the palette first.
+ */
+
+ if(!FindChunk(ThePNG, PNG_ChunkType_PLTE))
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Read the chunk-header.
+ */
+
+ CH = BufferedFileRead(ThePNG, PNG_ChunkHeader_Size);
+ if(!CH)
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * PNG multi-byte types are in Big Endian
+ */
+
+ ChunkHeaderLength = BigLong(CH->Length);
+ ChunkHeaderType = BigLong(CH->Type);
+
+ /*
+ * Check if the chunk is an PLTE.
+ */
+
+ if(!(ChunkHeaderType == PNG_ChunkType_PLTE))
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Check if Length is divisible by 3
+ */
+
+ if(ChunkHeaderLength % 3)
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Read the raw palette data
+ */
+
+ InPal = BufferedFileRead(ThePNG, ChunkHeaderLength);
+ if(!InPal)
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Read the CRC for the palette
+ */
+
+ CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size);
+ if(!CRC)
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Set some default values.
+ */
+
+ for(i = 0; i < 256; i++)
+ {
+ OutPal[i * Q3IMAGE_BYTESPERPIXEL + 0] = 0x00;
+ OutPal[i * Q3IMAGE_BYTESPERPIXEL + 1] = 0x00;
+ OutPal[i * Q3IMAGE_BYTESPERPIXEL + 2] = 0x00;
+ OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = 0xFF;
+ }
+
+ /*
+ * Convert to the Quake3 RGBA-format.
+ */
+
+ for(i = 0; i < (ChunkHeaderLength / 3); i++)
+ {
+ OutPal[i * Q3IMAGE_BYTESPERPIXEL + 0] = InPal[i*3+0];
+ OutPal[i * Q3IMAGE_BYTESPERPIXEL + 1] = InPal[i*3+1];
+ OutPal[i * Q3IMAGE_BYTESPERPIXEL + 2] = InPal[i*3+2];
+ OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = 0xFF;
+ }
+ }
+
+ /*
+ * transparency information is sometimes stored in an tRNS chunk
+ */
+
+ /*
+ * Let's see if there is a tRNS chunk
+ */
+
+ if(FindChunk(ThePNG, PNG_ChunkType_tRNS))
+ {
+ uint8_t *Trans;
+
+ /*
+ * Read the chunk-header.
+ */
+
+ CH = BufferedFileRead(ThePNG, PNG_ChunkHeader_Size);
+ if(!CH)
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * PNG multi-byte types are in Big Endian
+ */
+
+ ChunkHeaderLength = BigLong(CH->Length);
+ ChunkHeaderType = BigLong(CH->Type);
+
+ /*
+ * Check if the chunk is an tRNS.
+ */
+
+ if(!(ChunkHeaderType == PNG_ChunkType_tRNS))
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Read the transparency information.
+ */
+
+ Trans = BufferedFileRead(ThePNG, ChunkHeaderLength);
+ if(!Trans)
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Read the CRC.
+ */
+
+ CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size);
+ if(!CRC)
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Only for Grey, True and Indexed ColourType should tRNS exist.
+ */
+
+ switch(IHDR->ColourType)
+ {
+ case PNG_ColourType_Grey :
+ {
+ if(!ChunkHeaderLength == 2)
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ HasTransparentColour = qtrue;
+
+ /*
+ * Grey can have one colour which is completely transparent.
+ * This colour is always stored in 16 bits.
+ */
+
+ TransparentColour[0] = Trans[0];
+ TransparentColour[1] = Trans[1];
+
+ break;
+ }
+
+ case PNG_ColourType_True :
+ {
+ if(!ChunkHeaderLength == 6)
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ HasTransparentColour = qtrue;
+
+ /*
+ * True can have one colour which is completely transparent.
+ * This colour is always stored in 16 bits.
+ */
+
+ TransparentColour[0] = Trans[0];
+ TransparentColour[1] = Trans[1];
+ TransparentColour[2] = Trans[2];
+ TransparentColour[3] = Trans[3];
+ TransparentColour[4] = Trans[4];
+ TransparentColour[5] = Trans[5];
+
+ break;
+ }
+
+ case PNG_ColourType_Indexed :
+ {
+ /*
+ * Maximum of 256 one byte transparency entries.
+ */
+
+ if(ChunkHeaderLength > 256)
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ HasTransparentColour = qtrue;
+
+ /*
+ * alpha values for palette entries
+ */
+
+ for(i = 0; i < ChunkHeaderLength; i++)
+ {
+ OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = Trans[i];
+ }
+
+ break;
+ }
+
+ /*
+ * All other ColourTypes should not have tRNS chunks
+ */
+
+ default :
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+ }
+ }
+
+ /*
+ * Rewind to the start of the file.
+ */
+
+ if(!BufferedFileRewind(ThePNG, -1))
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Skip the signature
+ */
+
+ if(!BufferedFileSkip(ThePNG, PNG_Signature_Size))
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Decompress all IDAT chunks
+ */
+
+ DecompressedDataLength = DecompressIDATs(ThePNG, &DecompressedData);
+ if(!(DecompressedDataLength && DecompressedData))
+ {
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Allocate output buffer.
+ */
+
+ OutBuffer = ri.Malloc(IHDR_Width * IHDR_Height * Q3IMAGE_BYTESPERPIXEL);
+ if(!OutBuffer)
+ {
+ ri.Free(DecompressedData);
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ /*
+ * Interlaced and Non-interlaced images need to be handled differently.
+ */
+
+ switch(IHDR->InterlaceMethod)
+ {
+ case PNG_InterlaceMethod_NonInterlaced :
+ {
+ if(!DecodeImageNonInterlaced(IHDR, OutBuffer, DecompressedData, DecompressedDataLength, HasTransparentColour, TransparentColour, OutPal))
+ {
+ ri.Free(OutBuffer);
+ ri.Free(DecompressedData);
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ break;
+ }
+
+ case PNG_InterlaceMethod_Interlaced :
+ {
+ if(!DecodeImageInterlaced(IHDR, OutBuffer, DecompressedData, DecompressedDataLength, HasTransparentColour, TransparentColour, OutPal))
+ {
+ ri.Free(OutBuffer);
+ ri.Free(DecompressedData);
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+
+ break;
+ }
+
+ default :
+ {
+ ri.Free(OutBuffer);
+ ri.Free(DecompressedData);
+ CloseBufferedFile(ThePNG);
+
+ return;
+ }
+ }
+
+ /*
+ * update the pointer to the image data
+ */
+
+ *pic = OutBuffer;
+
+ /*
+ * Fill width and height.
+ */
+
+ if(width)
+ {
+ *width = IHDR_Width;
+ }
+
+ if(height)
+ {
+ *height = IHDR_Height;
+ }
+
+ /*
+ * DecompressedData is not needed anymore.
+ */
+
+ ri.Free(DecompressedData);
+
+ /*
+ * We have all data, so close the file.
+ */
+
+ CloseBufferedFile(ThePNG);
+}
diff --git a/code/renderer/tr_image_tga.c b/code/renderer/tr_image_tga.c
new file mode 100644
index 0000000..e66e60d
--- /dev/null
+++ b/code/renderer/tr_image_tga.c
@@ -0,0 +1,320 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "tr_local.h"
+
+/*
+========================================================================
+
+TGA files are used for 24/32 bit images
+
+========================================================================
+*/
+
+typedef struct _TargaHeader {
+ unsigned char id_length, colormap_type, image_type;
+ unsigned short colormap_index, colormap_length;
+ unsigned char colormap_size;
+ unsigned short x_origin, y_origin, width, height;
+ unsigned char pixel_size, attributes;
+} TargaHeader;
+
+void R_LoadTGA ( const char *name, byte **pic, int *width, int *height)
+{
+ unsigned columns, rows, numPixels;
+ byte *pixbuf;
+ int row, column;
+ byte *buf_p;
+ byte *end;
+ union {
+ byte *b;
+ void *v;
+ } buffer;
+ TargaHeader targa_header;
+ byte *targa_rgba;
+ int length;
+
+ *pic = NULL;
+
+ if(width)
+ *width = 0;
+ if(height)
+ *height = 0;
+
+ //
+ // load the file
+ //
+ length = ri.FS_ReadFile ( ( char * ) name, &buffer.v);
+ if (!buffer.b || length < 0) {
+ return;
+ }
+
+ if(length < 18)
+ {
+ ri.Error( ERR_DROP, "LoadTGA: header too short (%s)\n", name );
+ }
+
+ buf_p = buffer.b;
+ end = buffer.b + length;
+
+ targa_header.id_length = buf_p[0];
+ targa_header.colormap_type = buf_p[1];
+ targa_header.image_type = buf_p[2];
+
+ memcpy(&targa_header.colormap_index, &buf_p[3], 2);
+ memcpy(&targa_header.colormap_length, &buf_p[5], 2);
+ targa_header.colormap_size = buf_p[7];
+ memcpy(&targa_header.x_origin, &buf_p[8], 2);
+ memcpy(&targa_header.y_origin, &buf_p[10], 2);
+ memcpy(&targa_header.width, &buf_p[12], 2);
+ memcpy(&targa_header.height, &buf_p[14], 2);
+ targa_header.pixel_size = buf_p[16];
+ targa_header.attributes = buf_p[17];
+
+ targa_header.colormap_index = LittleShort(targa_header.colormap_index);
+ targa_header.colormap_length = LittleShort(targa_header.colormap_length);
+ targa_header.x_origin = LittleShort(targa_header.x_origin);
+ targa_header.y_origin = LittleShort(targa_header.y_origin);
+ targa_header.width = LittleShort(targa_header.width);
+ targa_header.height = LittleShort(targa_header.height);
+
+ buf_p += 18;
+
+ if (targa_header.image_type!=2
+ && targa_header.image_type!=10
+ && targa_header.image_type != 3 )
+ {
+ ri.Error (ERR_DROP, "LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n");
+ }
+
+ if ( targa_header.colormap_type != 0 )
+ {
+ ri.Error( ERR_DROP, "LoadTGA: colormaps not supported\n" );
+ }
+
+ if ( ( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) && targa_header.image_type != 3 )
+ {
+ ri.Error (ERR_DROP, "LoadTGA: Only 32 or 24 bit images supported (no colormaps)\n");
+ }
+
+ columns = targa_header.width;
+ rows = targa_header.height;
+ numPixels = columns * rows * 4;
+
+ if(!columns || !rows || numPixels > 0x7FFFFFFF || numPixels / columns / 4 != rows)
+ {
+ ri.Error (ERR_DROP, "LoadTGA: %s has an invalid image size\n", name);
+ }
+
+
+ targa_rgba = ri.Malloc (numPixels);
+
+ if (targa_header.id_length != 0)
+ {
+ if (buf_p + targa_header.id_length > end)
+ ri.Error( ERR_DROP, "LoadTGA: header too short (%s)\n", name );
+
+ buf_p += targa_header.id_length; // skip TARGA image comment
+ }
+
+ if ( targa_header.image_type==2 || targa_header.image_type == 3 )
+ {
+ if(buf_p + columns*rows*targa_header.pixel_size/8 > end)
+ {
+ ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)\n", name);
+ }
+
+ // Uncompressed RGB or gray scale image
+ for(row=rows-1; row>=0; row--)
+ {
+ pixbuf = targa_rgba + row*columns*4;
+ for(column=0; column<columns; column++)
+ {
+ unsigned char red,green,blue,alphabyte;
+ switch (targa_header.pixel_size)
+ {
+
+ case 8:
+ blue = *buf_p++;
+ green = blue;
+ red = blue;
+ *pixbuf++ = red;
+ *pixbuf++ = green;
+ *pixbuf++ = blue;
+ *pixbuf++ = 255;
+ break;
+
+ case 24:
+ blue = *buf_p++;
+ green = *buf_p++;
+ red = *buf_p++;
+ *pixbuf++ = red;
+ *pixbuf++ = green;
+ *pixbuf++ = blue;
+ *pixbuf++ = 255;
+ break;
+ case 32:
+ blue = *buf_p++;
+ green = *buf_p++;
+ red = *buf_p++;
+ alphabyte = *buf_p++;
+ *pixbuf++ = red;
+ *pixbuf++ = green;
+ *pixbuf++ = blue;
+ *pixbuf++ = alphabyte;
+ break;
+ default:
+ ri.Error( ERR_DROP, "LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size, name );
+ break;
+ }
+ }
+ }
+ }
+ else if (targa_header.image_type==10) { // Runlength encoded RGB images
+ unsigned char red,green,blue,alphabyte,packetHeader,packetSize,j;
+
+ red = 0;
+ green = 0;
+ blue = 0;
+ alphabyte = 0xff;
+
+ for(row=rows-1; row>=0; row--) {
+ pixbuf = targa_rgba + row*columns*4;
+ for(column=0; column<columns; ) {
+ if(buf_p + 1 > end)
+ ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)\n", name);
+ packetHeader= *buf_p++;
+ packetSize = 1 + (packetHeader & 0x7f);
+ if (packetHeader & 0x80) { // run-length packet
+ if(buf_p + targa_header.pixel_size/8 > end)
+ ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)\n", name);
+ switch (targa_header.pixel_size) {
+ case 24:
+ blue = *buf_p++;
+ green = *buf_p++;
+ red = *buf_p++;
+ alphabyte = 255;
+ break;
+ case 32:
+ blue = *buf_p++;
+ green = *buf_p++;
+ red = *buf_p++;
+ alphabyte = *buf_p++;
+ break;
+ default:
+ ri.Error( ERR_DROP, "LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size, name );
+ break;
+ }
+
+ for(j=0;j<packetSize;j++) {
+ *pixbuf++=red;
+ *pixbuf++=green;
+ *pixbuf++=blue;
+ *pixbuf++=alphabyte;
+ column++;
+ if (column==columns) { // run spans across rows
+ column=0;
+ if (row>0)
+ row--;
+ else
+ goto breakOut;
+ pixbuf = targa_rgba + row*columns*4;
+ }
+ }
+ }
+ else { // non run-length packet
+
+ if(buf_p + targa_header.pixel_size/8*packetSize > end)
+ ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)\n", name);
+ for(j=0;j<packetSize;j++) {
+ switch (targa_header.pixel_size) {
+ case 24:
+ blue = *buf_p++;
+ green = *buf_p++;
+ red = *buf_p++;
+ *pixbuf++ = red;
+ *pixbuf++ = green;
+ *pixbuf++ = blue;
+ *pixbuf++ = 255;
+ break;
+ case 32:
+ blue = *buf_p++;
+ green = *buf_p++;
+ red = *buf_p++;
+ alphabyte = *buf_p++;
+ *pixbuf++ = red;
+ *pixbuf++ = green;
+ *pixbuf++ = blue;
+ *pixbuf++ = alphabyte;
+ break;
+ default:
+ ri.Error( ERR_DROP, "LoadTGA: illegal pixel_size '%d' in file '%s'\n", targa_header.pixel_size, name );
+ break;
+ }
+ column++;
+ if (column==columns) { // pixel packet run spans across rows
+ column=0;
+ if (row>0)
+ row--;
+ else
+ goto breakOut;
+ pixbuf = targa_rgba + row*columns*4;
+ }
+ }
+ }
+ }
+ breakOut:;
+ }
+ }
+
+#if 0
+ // TTimo: this is the chunk of code to ensure a behavior that meets TGA specs
+ // bit 5 set => top-down
+ if (targa_header.attributes & 0x20) {
+ unsigned char *flip = (unsigned char*)malloc (columns*4);
+ unsigned char *src, *dst;
+
+ for (row = 0; row < rows/2; row++) {
+ src = targa_rgba + row * 4 * columns;
+ dst = targa_rgba + (rows - row - 1) * 4 * columns;
+
+ memcpy (flip, src, columns*4);
+ memcpy (src, dst, columns*4);
+ memcpy (dst, flip, columns*4);
+ }
+ free (flip);
+ }
+#endif
+ // instead we just print a warning
+ if (targa_header.attributes & 0x20) {
+ ri.Printf( PRINT_WARNING, "WARNING: '%s' TGA file header declares top-down image, ignoring\n", name);
+ }
+
+ if (width)
+ *width = columns;
+ if (height)
+ *height = rows;
+
+ *pic = targa_rgba;
+
+ ri.FS_FreeFile (buffer.v);
+}
diff --git a/code/renderer/tr_init.c b/code/renderer/tr_init.c
new file mode 100644
index 0000000..cc8449d
--- /dev/null
+++ b/code/renderer/tr_init.c
@@ -0,0 +1,1258 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// tr_init.c -- functions that are not called every frame
+
+#include "tr_local.h"
+
+glconfig_t glConfig;
+qboolean textureFilterAnisotropic = qfalse;
+int maxAnisotropy = 0;
+float displayAspect = 0.0f;
+
+glstate_t glState;
+
+static void GfxInfo_f( void );
+
+cvar_t *r_flareSize;
+cvar_t *r_flareFade;
+cvar_t *r_flareCoeff;
+
+cvar_t *r_railWidth;
+cvar_t *r_railCoreWidth;
+cvar_t *r_railSegmentLength;
+
+cvar_t *r_ignoreFastPath;
+
+cvar_t *r_verbose;
+cvar_t *r_ignore;
+
+cvar_t *r_detailTextures;
+
+cvar_t *r_znear;
+cvar_t *r_zproj;
+cvar_t *r_stereoSeparation;
+
+cvar_t *r_smp;
+cvar_t *r_showSmp;
+cvar_t *r_skipBackEnd;
+
+cvar_t *r_stereoEnabled;
+cvar_t *r_anaglyphMode;
+
+cvar_t *r_greyscale;
+
+cvar_t *r_ignorehwgamma;
+cvar_t *r_measureOverdraw;
+
+cvar_t *r_inGameVideo;
+cvar_t *r_fastsky;
+cvar_t *r_drawSun;
+cvar_t *r_dynamiclight;
+cvar_t *r_dlightBacks;
+
+cvar_t *r_lodbias;
+cvar_t *r_lodscale;
+
+cvar_t *r_norefresh;
+cvar_t *r_drawentities;
+cvar_t *r_drawworld;
+cvar_t *r_speeds;
+cvar_t *r_fullbright;
+cvar_t *r_novis;
+cvar_t *r_nocull;
+cvar_t *r_facePlaneCull;
+cvar_t *r_showcluster;
+cvar_t *r_nocurves;
+
+cvar_t *r_allowExtensions;
+
+cvar_t *r_ext_compressed_textures;
+cvar_t *r_ext_multitexture;
+cvar_t *r_ext_compiled_vertex_array;
+cvar_t *r_ext_texture_env_add;
+cvar_t *r_ext_texture_filter_anisotropic;
+cvar_t *r_ext_max_anisotropy;
+
+cvar_t *r_ignoreGLErrors;
+cvar_t *r_logFile;
+
+cvar_t *r_stencilbits;
+cvar_t *r_depthbits;
+cvar_t *r_colorbits;
+cvar_t *r_primitives;
+cvar_t *r_texturebits;
+cvar_t *r_ext_multisample;
+
+cvar_t *r_drawBuffer;
+cvar_t *r_lightmap;
+cvar_t *r_vertexLight;
+cvar_t *r_uiFullScreen;
+cvar_t *r_shadows;
+cvar_t *r_flares;
+cvar_t *r_mode;
+cvar_t *r_nobind;
+cvar_t *r_singleShader;
+cvar_t *r_roundImagesDown;
+cvar_t *r_colorMipLevels;
+cvar_t *r_picmip;
+cvar_t *r_showtris;
+cvar_t *r_showsky;
+cvar_t *r_shownormals;
+cvar_t *r_finish;
+cvar_t *r_clear;
+cvar_t *r_swapInterval;
+cvar_t *r_textureMode;
+cvar_t *r_offsetFactor;
+cvar_t *r_offsetUnits;
+cvar_t *r_gamma;
+cvar_t *r_intensity;
+cvar_t *r_lockpvs;
+cvar_t *r_noportals;
+cvar_t *r_portalOnly;
+
+cvar_t *r_subdivisions;
+cvar_t *r_lodCurveError;
+
+cvar_t *r_fullscreen;
+cvar_t *r_noborder;
+
+cvar_t *r_customwidth;
+cvar_t *r_customheight;
+cvar_t *r_customPixelAspect;
+
+cvar_t *r_overBrightBits;
+cvar_t *r_mapOverBrightBits;
+
+cvar_t *r_debugSurface;
+cvar_t *r_simpleMipMaps;
+
+cvar_t *r_showImages;
+
+cvar_t *r_ambientScale;
+cvar_t *r_directedScale;
+cvar_t *r_debugLight;
+cvar_t *r_debugSort;
+cvar_t *r_printShaders;
+cvar_t *r_saveFontData;
+
+cvar_t *r_marksOnTriangleMeshes;
+
+cvar_t *r_maxpolys;
+int max_polys;
+cvar_t *r_maxpolyverts;
+int max_polyverts;
+
+#ifdef PANDORA
+void (APIENTRY * qglMultiTexCoord2fARB) (GLenum texture, GLfloat s, GLfloat t);
+void (APIENTRY * qglActiveTextureARB) (GLenum texture);
+void (APIENTRY * qglClientActiveTextureARB) (GLenum texture);
+void (APIENTRY * qglLockArraysEXT) (GLint, GLint);
+void (APIENTRY * qglUnlockArraysEXT) (void);
+
+#define qglClearDepth qglClearDepthf
+#define qglColor4f glColor4f
+
+#endif
+
+/*
+** InitOpenGL
+**
+** This function is responsible for initializing a valid OpenGL subsystem. This
+** is done by calling GLimp_Init (which gives us a working OGL subsystem) then
+** setting variables, checking GL constants, and reporting the gfx system config
+** to the user.
+*/
+static void InitOpenGL( void )
+{
+ char renderer_buffer[1024];
+
+ //
+ // initialize OS specific portions of the renderer
+ //
+ // GLimp_Init directly or indirectly references the following cvars:
+ // - r_fullscreen
+ // - r_mode
+ // - r_(color|depth|stencil)bits
+ // - r_ignorehwgamma
+ // - r_gamma
+ //
+
+ if ( glConfig.vidWidth == 0 )
+ {
+ GLint temp;
+
+ GLimp_Init();
+
+ strcpy( renderer_buffer, glConfig.renderer_string );
+ Q_strlwr( renderer_buffer );
+
+ // OpenGL driver constants
+ qglGetIntegerv( GL_MAX_TEXTURE_SIZE, &temp );
+ glConfig.maxTextureSize = temp;
+
+ // stubbed or broken drivers may have reported 0...
+ if ( glConfig.maxTextureSize <= 0 )
+ {
+ glConfig.maxTextureSize = 0;
+ }
+ }
+
+ // init command buffers and SMP
+ R_InitCommandBuffers();
+
+ // print info
+ GfxInfo_f();
+
+ // set default state
+ GL_SetDefaultState();
+}
+
+/*
+==================
+GL_CheckErrors
+==================
+*/
+void GL_CheckErrors( void ) {
+ int err;
+ char s[64];
+
+ err = qglGetError();
+ if ( err == GL_NO_ERROR ) {
+ return;
+ }
+ if ( r_ignoreGLErrors->integer ) {
+ return;
+ }
+ switch( err ) {
+ case GL_INVALID_ENUM:
+ strcpy( s, "GL_INVALID_ENUM" );
+ break;
+ case GL_INVALID_VALUE:
+ strcpy( s, "GL_INVALID_VALUE" );
+ break;
+ case GL_INVALID_OPERATION:
+ strcpy( s, "GL_INVALID_OPERATION" );
+ break;
+ case GL_STACK_OVERFLOW:
+ strcpy( s, "GL_STACK_OVERFLOW" );
+ break;
+ case GL_STACK_UNDERFLOW:
+ strcpy( s, "GL_STACK_UNDERFLOW" );
+ break;
+ case GL_OUT_OF_MEMORY:
+ strcpy( s, "GL_OUT_OF_MEMORY" );
+ break;
+ default:
+ Com_sprintf( s, sizeof(s), "%i", err);
+ break;
+ }
+
+ ri.Error( ERR_FATAL, "GL_CheckErrors: %s", s );
+}
+
+
+/*
+** R_GetModeInfo
+*/
+typedef struct vidmode_s
+{
+ const char *description;
+ int width, height;
+ float pixelAspect; // pixel width / height
+} vidmode_t;
+
+vidmode_t r_vidModes[] =
+{
+ { "Mode 0: 320x240", 320, 240, 1 },
+ { "Mode 1: 400x300", 400, 300, 1 },
+ { "Mode 2: 512x384", 512, 384, 1 },
+ { "Mode 3: 640x480", 640, 480, 1 },
+ { "Mode 4: 800x600", 800, 600, 1 },
+ { "Mode 5: 960x720", 960, 720, 1 },
+ { "Mode 6: 1024x768", 1024, 768, 1 },
+ { "Mode 7: 1152x864", 1152, 864, 1 },
+ { "Mode 8: 1280x1024", 1280, 1024, 1 },
+ { "Mode 9: 1600x1200", 1600, 1200, 1 },
+ { "Mode 10: 2048x1536", 2048, 1536, 1 },
+ { "Mode 11: 856x480 (wide)",856, 480, 1 }
+};
+static int s_numVidModes = ( sizeof( r_vidModes ) / sizeof( r_vidModes[0] ) );
+
+qboolean R_GetModeInfo( int *width, int *height, float *windowAspect, int mode ) {
+ vidmode_t *vm;
+ float pixelAspect;
+
+ if ( mode < -1 ) {
+ return qfalse;
+ }
+ if ( mode >= s_numVidModes ) {
+ return qfalse;
+ }
+
+ if ( mode == -1 ) {
+ *width = r_customwidth->integer;
+ *height = r_customheight->integer;
+ pixelAspect = r_customPixelAspect->value;
+ } else {
+ vm = &r_vidModes[mode];
+
+ *width = vm->width;
+ *height = vm->height;
+ pixelAspect = vm->pixelAspect;
+ }
+
+ *windowAspect = (float)*width / ( *height * pixelAspect );
+
+ return qtrue;
+}
+
+/*
+** R_ModeList_f
+*/
+static void R_ModeList_f( void )
+{
+ int i;
+
+ ri.Printf( PRINT_ALL, "\n" );
+ for ( i = 0; i < s_numVidModes; i++ )
+ {
+ ri.Printf( PRINT_ALL, "%s\n", r_vidModes[i].description );
+ }
+ ri.Printf( PRINT_ALL, "\n" );
+}
+
+
+/*
+==============================================================================
+
+ SCREEN SHOTS
+
+NOTE TTimo
+some thoughts about the screenshots system:
+screenshots get written in fs_homepath + fs_gamedir
+vanilla q3 .. baseq3/screenshots/ *.tga
+team arena .. missionpack/screenshots/ *.tga
+
+two commands: "screenshot" and "screenshotJPEG"
+we use statics to store a count and start writing the first screenshot/screenshot????.tga (.jpg) available
+(with FS_FileExists / FS_FOpenFileWrite calls)
+FIXME: the statics don't get a reinit between fs_game changes
+
+==============================================================================
+*/
+
+/*
+==================
+RB_TakeScreenshot
+==================
+*/
+void RB_TakeScreenshot( int x, int y, int width, int height, char *fileName ) {
+ byte *buffer;
+ int i, c, temp;
+
+ buffer = ri.Hunk_AllocateTempMemory(glConfig.vidWidth*glConfig.vidHeight*3+18);
+
+ Com_Memset (buffer, 0, 18);
+ buffer[2] = 2; // uncompressed type
+ buffer[12] = width & 255;
+ buffer[13] = width >> 8;
+ buffer[14] = height & 255;
+ buffer[15] = height >> 8;
+ buffer[16] = 24; // pixel size
+
+ qglReadPixels( x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, buffer+18 );
+
+ // swap rgb to bgr
+ c = 18 + width * height * 3;
+ for (i=18 ; i<c ; i+=3) {
+ temp = buffer[i];
+ buffer[i] = buffer[i+2];
+ buffer[i+2] = temp;
+ }
+
+ // gamma correct
+ if ( glConfig.deviceSupportsGamma ) {
+ R_GammaCorrect( buffer + 18, glConfig.vidWidth * glConfig.vidHeight * 3 );
+ }
+
+ ri.FS_WriteFile( fileName, buffer, c );
+
+ ri.Hunk_FreeTempMemory( buffer );
+}
+
+/*
+==================
+RB_TakeScreenshotJPEG
+==================
+*/
+void RB_TakeScreenshotJPEG( int x, int y, int width, int height, char *fileName ) {
+ byte *buffer;
+
+ buffer = ri.Hunk_AllocateTempMemory(glConfig.vidWidth*glConfig.vidHeight*4);
+
+ qglReadPixels( x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer );
+
+ // gamma correct
+ if ( glConfig.deviceSupportsGamma ) {
+ R_GammaCorrect( buffer, glConfig.vidWidth * glConfig.vidHeight * 4 );
+ }
+
+ ri.FS_WriteFile( fileName, buffer, 1 ); // create path
+ SaveJPG( fileName, 90, glConfig.vidWidth, glConfig.vidHeight, buffer);
+
+ ri.Hunk_FreeTempMemory( buffer );
+}
+
+/*
+==================
+RB_TakeScreenshotCmd
+==================
+*/
+const void *RB_TakeScreenshotCmd( const void *data ) {
+ const screenshotCommand_t *cmd;
+
+ cmd = (const screenshotCommand_t *)data;
+
+ if (cmd->jpeg)
+ RB_TakeScreenshotJPEG( cmd->x, cmd->y, cmd->width, cmd->height, cmd->fileName);
+ else
+ RB_TakeScreenshot( cmd->x, cmd->y, cmd->width, cmd->height, cmd->fileName);
+
+ return (const void *)(cmd + 1);
+}
+
+/*
+==================
+R_TakeScreenshot
+==================
+*/
+void R_TakeScreenshot( int x, int y, int width, int height, char *name, qboolean jpeg ) {
+ static char fileName[MAX_OSPATH]; // bad things if two screenshots per frame?
+ screenshotCommand_t *cmd;
+
+ cmd = R_GetCommandBuffer( sizeof( *cmd ) );
+ if ( !cmd ) {
+ return;
+ }
+ cmd->commandId = RC_SCREENSHOT;
+
+ cmd->x = x;
+ cmd->y = y;
+ cmd->width = width;
+ cmd->height = height;
+ Q_strncpyz( fileName, name, sizeof(fileName) );
+ cmd->fileName = fileName;
+ cmd->jpeg = jpeg;
+}
+
+/*
+==================
+R_ScreenshotFilename
+==================
+*/
+void R_ScreenshotFilename( int lastNumber, char *fileName ) {
+ int a,b,c,d;
+
+ if ( lastNumber < 0 || lastNumber > 9999 ) {
+ Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot9999.tga" );
+ return;
+ }
+
+ a = lastNumber / 1000;
+ lastNumber -= a*1000;
+ b = lastNumber / 100;
+ lastNumber -= b*100;
+ c = lastNumber / 10;
+ lastNumber -= c*10;
+ d = lastNumber;
+
+ Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot%i%i%i%i.tga"
+ , a, b, c, d );
+}
+
+/*
+==================
+R_ScreenshotFilename
+==================
+*/
+void R_ScreenshotFilenameJPEG( int lastNumber, char *fileName ) {
+ int a,b,c,d;
+
+ if ( lastNumber < 0 || lastNumber > 9999 ) {
+ Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot9999.jpg" );
+ return;
+ }
+
+ a = lastNumber / 1000;
+ lastNumber -= a*1000;
+ b = lastNumber / 100;
+ lastNumber -= b*100;
+ c = lastNumber / 10;
+ lastNumber -= c*10;
+ d = lastNumber;
+
+ Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot%i%i%i%i.jpg"
+ , a, b, c, d );
+}
+
+/*
+====================
+R_LevelShot
+
+levelshots are specialized 128*128 thumbnails for
+the menu system, sampled down from full screen distorted images
+====================
+*/
+void R_LevelShot( void ) {
+ char checkname[MAX_OSPATH];
+ byte *buffer;
+ byte *source;
+ byte *src, *dst;
+ int x, y;
+ int r, g, b;
+ float xScale, yScale;
+ int xx, yy;
+
+ Com_sprintf(checkname, sizeof(checkname), "levelshots/%s.tga", tr.world->baseName);
+
+ source = ri.Hunk_AllocateTempMemory( glConfig.vidWidth * glConfig.vidHeight * 3 );
+
+ buffer = ri.Hunk_AllocateTempMemory( 128 * 128*3 + 18);
+ Com_Memset (buffer, 0, 18);
+ buffer[2] = 2; // uncompressed type
+ buffer[12] = 128;
+ buffer[14] = 128;
+ buffer[16] = 24; // pixel size
+
+ qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_RGB, GL_UNSIGNED_BYTE, source );
+
+ // resample from source
+ xScale = glConfig.vidWidth / 512.0f;
+ yScale = glConfig.vidHeight / 384.0f;
+ for ( y = 0 ; y < 128 ; y++ ) {
+ for ( x = 0 ; x < 128 ; x++ ) {
+ r = g = b = 0;
+ for ( yy = 0 ; yy < 3 ; yy++ ) {
+ for ( xx = 0 ; xx < 4 ; xx++ ) {
+ src = source + 3 * ( glConfig.vidWidth * (int)( (y*3+yy)*yScale ) + (int)( (x*4+xx)*xScale ) );
+ r += src[0];
+ g += src[1];
+ b += src[2];
+ }
+ }
+ dst = buffer + 18 + 3 * ( y * 128 + x );
+ dst[0] = b / 12;
+ dst[1] = g / 12;
+ dst[2] = r / 12;
+ }
+ }
+
+ // gamma correct
+ if ( glConfig.deviceSupportsGamma ) {
+ R_GammaCorrect( buffer + 18, 128 * 128 * 3 );
+ }
+
+ ri.FS_WriteFile( checkname, buffer, 128 * 128*3 + 18 );
+
+ ri.Hunk_FreeTempMemory( buffer );
+ ri.Hunk_FreeTempMemory( source );
+
+ ri.Printf( PRINT_ALL, "Wrote %s\n", checkname );
+}
+
+/*
+==================
+R_ScreenShot_f
+
+screenshot
+screenshot [silent]
+screenshot [levelshot]
+screenshot [filename]
+
+Doesn't print the pacifier message if there is a second arg
+==================
+*/
+void R_ScreenShot_f (void) {
+ char checkname[MAX_OSPATH];
+ static int lastNumber = -1;
+ qboolean silent;
+
+ if ( !strcmp( ri.Cmd_Argv(1), "levelshot" ) ) {
+ R_LevelShot();
+ return;
+ }
+
+ if ( !strcmp( ri.Cmd_Argv(1), "silent" ) ) {
+ silent = qtrue;
+ } else {
+ silent = qfalse;
+ }
+
+ if ( ri.Cmd_Argc() == 2 && !silent ) {
+ // explicit filename
+ Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.tga", ri.Cmd_Argv( 1 ) );
+ } else {
+ // scan for a free filename
+
+ // if we have saved a previous screenshot, don't scan
+ // again, because recording demo avis can involve
+ // thousands of shots
+ if ( lastNumber == -1 ) {
+ lastNumber = 0;
+ }
+ // scan for a free number
+ for ( ; lastNumber <= 9999 ; lastNumber++ ) {
+ R_ScreenshotFilename( lastNumber, checkname );
+
+ if (!ri.FS_FileExists( checkname ))
+ {
+ break; // file doesn't exist
+ }
+ }
+
+ if ( lastNumber >= 9999 ) {
+ ri.Printf (PRINT_ALL, "ScreenShot: Couldn't create a file\n");
+ return;
+ }
+
+ lastNumber++;
+ }
+
+ R_TakeScreenshot( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname, qfalse );
+
+ if ( !silent ) {
+ ri.Printf (PRINT_ALL, "Wrote %s\n", checkname);
+ }
+}
+
+void R_ScreenShotJPEG_f (void) {
+ char checkname[MAX_OSPATH];
+ static int lastNumber = -1;
+ qboolean silent;
+
+ if ( !strcmp( ri.Cmd_Argv(1), "levelshot" ) ) {
+ R_LevelShot();
+ return;
+ }
+
+ if ( !strcmp( ri.Cmd_Argv(1), "silent" ) ) {
+ silent = qtrue;
+ } else {
+ silent = qfalse;
+ }
+
+ if ( ri.Cmd_Argc() == 2 && !silent ) {
+ // explicit filename
+ Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.jpg", ri.Cmd_Argv( 1 ) );
+ } else {
+ // scan for a free filename
+
+ // if we have saved a previous screenshot, don't scan
+ // again, because recording demo avis can involve
+ // thousands of shots
+ if ( lastNumber == -1 ) {
+ lastNumber = 0;
+ }
+ // scan for a free number
+ for ( ; lastNumber <= 9999 ; lastNumber++ ) {
+ R_ScreenshotFilenameJPEG( lastNumber, checkname );
+
+ if (!ri.FS_FileExists( checkname ))
+ {
+ break; // file doesn't exist
+ }
+ }
+
+ if ( lastNumber == 10000 ) {
+ ri.Printf (PRINT_ALL, "ScreenShot: Couldn't create a file\n");
+ return;
+ }
+
+ lastNumber++;
+ }
+
+ R_TakeScreenshot( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname, qtrue );
+
+ if ( !silent ) {
+ ri.Printf (PRINT_ALL, "Wrote %s\n", checkname);
+ }
+}
+
+//============================================================================
+
+/*
+==================
+RB_TakeVideoFrameCmd
+==================
+*/
+const void *RB_TakeVideoFrameCmd( const void *data )
+{
+ const videoFrameCommand_t *cmd;
+ int frameSize;
+ int i;
+
+ cmd = (const videoFrameCommand_t *)data;
+
+ qglReadPixels( 0, 0, cmd->width, cmd->height, GL_RGBA,
+ GL_UNSIGNED_BYTE, cmd->captureBuffer );
+
+ // gamma correct
+ if( glConfig.deviceSupportsGamma )
+ R_GammaCorrect( cmd->captureBuffer, cmd->width * cmd->height * 4 );
+
+ if( cmd->motionJpeg )
+ {
+ frameSize = SaveJPGToBuffer( cmd->encodeBuffer, 90,
+ cmd->width, cmd->height, cmd->captureBuffer );
+ ri.CL_WriteAVIVideoFrame( cmd->encodeBuffer, frameSize );
+ }
+ else
+ {
+ frameSize = cmd->width * cmd->height;
+
+ for( i = 0; i < frameSize; i++) // Pack to 24bpp and swap R and B
+ {
+ cmd->encodeBuffer[ i*3 ] = cmd->captureBuffer[ i*4 + 2 ];
+ cmd->encodeBuffer[ i*3 + 1 ] = cmd->captureBuffer[ i*4 + 1 ];
+ cmd->encodeBuffer[ i*3 + 2 ] = cmd->captureBuffer[ i*4 ];
+ }
+
+ ri.CL_WriteAVIVideoFrame( cmd->encodeBuffer, frameSize * 3 );
+ }
+
+ return (const void *)(cmd + 1);
+}
+
+//============================================================================
+
+/*
+** GL_SetDefaultState
+*/
+void GL_SetDefaultState( void )
+{
+ qglClearDepth( 1.0f );
+
+ qglCullFace(GL_FRONT);
+
+ qglColor4f (1,1,1,1);
+
+ // initialize downstream texture unit if we're running
+ // in a multitexture environment
+ if ( qglActiveTextureARB ) {
+ GL_SelectTexture( 1 );
+ GL_TextureMode( r_textureMode->string );
+ GL_TexEnv( GL_MODULATE );
+ qglDisable( GL_TEXTURE_2D );
+ GL_SelectTexture( 0 );
+ }
+
+ qglEnable(GL_TEXTURE_2D);
+ GL_TextureMode( r_textureMode->string );
+ GL_TexEnv( GL_MODULATE );
+
+ qglShadeModel( GL_SMOOTH );
+ qglDepthFunc( GL_LEQUAL );
+
+ // the vertex array is always enabled, but the color and texture
+ // arrays are enabled and disabled around the compiled vertex array call
+ qglEnableClientState (GL_VERTEX_ARRAY);
+
+ //
+ // make sure our GL state vector is set correctly
+ //
+ glState.glStateBits = GLS_DEPTHTEST_DISABLE | GLS_DEPTHMASK_TRUE;
+#ifndef PANDORA
+ qglPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
+#endif
+ qglDepthMask( GL_TRUE );
+ qglDisable( GL_DEPTH_TEST );
+ qglEnable( GL_SCISSOR_TEST );
+ qglDisable( GL_CULL_FACE );
+ qglDisable( GL_BLEND );
+}
+
+
+/*
+================
+GfxInfo_f
+================
+*/
+void GfxInfo_f( void )
+{
+ const char *enablestrings[] =
+ {
+ "disabled",
+ "enabled"
+ };
+ const char *fsstrings[] =
+ {
+ "windowed",
+ "fullscreen"
+ };
+
+ ri.Printf( PRINT_ALL, "\nGL_VENDOR: %s\n", glConfig.vendor_string );
+ ri.Printf( PRINT_ALL, "GL_RENDERER: %s\n", glConfig.renderer_string );
+ ri.Printf( PRINT_ALL, "GL_VERSION: %s\n", glConfig.version_string );
+ ri.Printf( PRINT_ALL, "GL_EXTENSIONS: %s\n", glConfig.extensions_string );
+ ri.Printf( PRINT_ALL, "GL_MAX_TEXTURE_SIZE: %d\n", glConfig.maxTextureSize );
+ ri.Printf( PRINT_ALL, "GL_MAX_TEXTURE_UNITS_ARB: %d\n", glConfig.numTextureUnits );
+ ri.Printf( PRINT_ALL, "\nPIXELFORMAT: color(%d-bits) Z(%d-bit) stencil(%d-bits)\n", glConfig.colorBits, glConfig.depthBits, glConfig.stencilBits );
+ ri.Printf( PRINT_ALL, "MODE: %d, %d x %d %s hz:", r_mode->integer, glConfig.vidWidth, glConfig.vidHeight, fsstrings[r_fullscreen->integer == 1] );
+ if ( glConfig.displayFrequency )
+ {
+ ri.Printf( PRINT_ALL, "%d\n", glConfig.displayFrequency );
+ }
+ else
+ {
+ ri.Printf( PRINT_ALL, "N/A\n" );
+ }
+ if ( glConfig.deviceSupportsGamma )
+ {
+ ri.Printf( PRINT_ALL, "GAMMA: hardware w/ %d overbright bits\n", tr.overbrightBits );
+ }
+ else
+ {
+ ri.Printf( PRINT_ALL, "GAMMA: software w/ %d overbright bits\n", tr.overbrightBits );
+ }
+
+ // rendering primitives
+ {
+ int primitives;
+
+ // default is to use triangles if compiled vertex arrays are present
+ ri.Printf( PRINT_ALL, "rendering primitives: " );
+ primitives = r_primitives->integer;
+ if ( primitives == 0 ) {
+ if ( qglLockArraysEXT ) {
+ primitives = 2;
+ } else {
+ primitives = 1;
+ }
+ }
+ if ( primitives == -1 ) {
+ ri.Printf( PRINT_ALL, "none\n" );
+ } else if ( primitives == 2 ) {
+ ri.Printf( PRINT_ALL, "single glDrawElements\n" );
+ } else if ( primitives == 1 ) {
+ ri.Printf( PRINT_ALL, "multiple glArrayElement\n" );
+ } else if ( primitives == 3 ) {
+ ri.Printf( PRINT_ALL, "multiple glColor4ubv + glTexCoord2fv + glVertex3fv\n" );
+ }
+ }
+
+ ri.Printf( PRINT_ALL, "texturemode: %s\n", r_textureMode->string );
+ ri.Printf( PRINT_ALL, "picmip: %d\n", r_picmip->integer );
+ ri.Printf( PRINT_ALL, "texture bits: %d\n", r_texturebits->integer );
+ ri.Printf( PRINT_ALL, "multitexture: %s\n", enablestrings[qglActiveTextureARB != 0] );
+ ri.Printf( PRINT_ALL, "compiled vertex arrays: %s\n", enablestrings[qglLockArraysEXT != 0 ] );
+ ri.Printf( PRINT_ALL, "texenv add: %s\n", enablestrings[glConfig.textureEnvAddAvailable != 0] );
+ ri.Printf( PRINT_ALL, "compressed textures: %s\n", enablestrings[glConfig.textureCompression!=TC_NONE] );
+ if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 )
+ {
+ ri.Printf( PRINT_ALL, "HACK: using vertex lightmap approximation\n" );
+ }
+ if ( glConfig.hardwareType == GLHW_RAGEPRO )
+ {
+ ri.Printf( PRINT_ALL, "HACK: ragePro approximations\n" );
+ }
+ if ( glConfig.hardwareType == GLHW_RIVA128 )
+ {
+ ri.Printf( PRINT_ALL, "HACK: riva128 approximations\n" );
+ }
+ if ( glConfig.smpActive ) {
+ ri.Printf( PRINT_ALL, "Using dual processor acceleration\n" );
+ }
+ if ( r_finish->integer ) {
+ ri.Printf( PRINT_ALL, "Forcing glFinish\n" );
+ }
+}
+
+/*
+===============
+R_Register
+===============
+*/
+void R_Register( void )
+{
+ //
+ // latched and archived variables
+ //
+ r_allowExtensions = ri.Cvar_Get( "r_allowExtensions", "1", CVAR_ARCHIVE | CVAR_LATCH );
+ r_ext_compressed_textures = ri.Cvar_Get( "r_ext_compressed_textures", "0", CVAR_ARCHIVE | CVAR_LATCH );
+ r_ext_multitexture = ri.Cvar_Get( "r_ext_multitexture", "1", CVAR_ARCHIVE | CVAR_LATCH );
+ r_ext_compiled_vertex_array = ri.Cvar_Get( "r_ext_compiled_vertex_array", "1", CVAR_ARCHIVE | CVAR_LATCH);
+ r_ext_texture_env_add = ri.Cvar_Get( "r_ext_texture_env_add", "1", CVAR_ARCHIVE | CVAR_LATCH);
+
+ r_ext_texture_filter_anisotropic = ri.Cvar_Get( "r_ext_texture_filter_anisotropic",
+ "0", CVAR_ARCHIVE | CVAR_LATCH );
+ r_ext_max_anisotropy = ri.Cvar_Get( "r_ext_max_anisotropy", "2", CVAR_ARCHIVE | CVAR_LATCH );
+
+ r_picmip = ri.Cvar_Get ("r_picmip", "1", CVAR_ARCHIVE | CVAR_LATCH );
+ r_roundImagesDown = ri.Cvar_Get ("r_roundImagesDown", "1", CVAR_ARCHIVE | CVAR_LATCH );
+ r_colorMipLevels = ri.Cvar_Get ("r_colorMipLevels", "0", CVAR_LATCH );
+ ri.Cvar_CheckRange( r_picmip, 0, 16, qtrue );
+ r_detailTextures = ri.Cvar_Get( "r_detailtextures", "1", CVAR_ARCHIVE | CVAR_LATCH );
+ r_texturebits = ri.Cvar_Get( "r_texturebits", "0", CVAR_ARCHIVE | CVAR_LATCH );
+ r_colorbits = ri.Cvar_Get( "r_colorbits", "0", CVAR_ARCHIVE | CVAR_LATCH );
+ r_stencilbits = ri.Cvar_Get( "r_stencilbits", "8", CVAR_ARCHIVE | CVAR_LATCH );
+ r_depthbits = ri.Cvar_Get( "r_depthbits", "0", CVAR_ARCHIVE | CVAR_LATCH );
+ r_ext_multisample = ri.Cvar_Get( "r_ext_multisample", "0", CVAR_ARCHIVE | CVAR_LATCH );
+ ri.Cvar_CheckRange( r_ext_multisample, 0, 4, qtrue );
+ r_overBrightBits = ri.Cvar_Get ("r_overBrightBits", "1", CVAR_ARCHIVE | CVAR_LATCH );
+ r_ignorehwgamma = ri.Cvar_Get( "r_ignorehwgamma", "0", CVAR_ARCHIVE | CVAR_LATCH);
+ r_mode = ri.Cvar_Get( "r_mode", "3", CVAR_ARCHIVE | CVAR_LATCH );
+ r_fullscreen = ri.Cvar_Get( "r_fullscreen", "1", CVAR_ARCHIVE );
+ r_noborder = Cvar_Get("r_noborder", "0", CVAR_ARCHIVE);
+ r_customwidth = ri.Cvar_Get( "r_customwidth", "1600", CVAR_ARCHIVE | CVAR_LATCH );
+ r_customheight = ri.Cvar_Get( "r_customheight", "1024", CVAR_ARCHIVE | CVAR_LATCH );
+ r_customPixelAspect = ri.Cvar_Get( "r_customPixelAspect", "1", CVAR_ARCHIVE | CVAR_LATCH );
+ r_simpleMipMaps = ri.Cvar_Get( "r_simpleMipMaps", "1", CVAR_ARCHIVE | CVAR_LATCH );
+ r_vertexLight = ri.Cvar_Get( "r_vertexLight", "0", CVAR_ARCHIVE | CVAR_LATCH );
+ r_uiFullScreen = ri.Cvar_Get( "r_uifullscreen", "0", 0);
+ r_subdivisions = ri.Cvar_Get ("r_subdivisions", "4", CVAR_ARCHIVE | CVAR_LATCH);
+ r_smp = ri.Cvar_Get( "r_smp", "0", CVAR_ARCHIVE | CVAR_LATCH);
+ r_stereoEnabled = ri.Cvar_Get( "r_stereoEnabled", "0", CVAR_ARCHIVE | CVAR_LATCH);
+ r_ignoreFastPath = ri.Cvar_Get( "r_ignoreFastPath", "1", CVAR_ARCHIVE | CVAR_LATCH );
+ r_greyscale = ri.Cvar_Get("r_greyscale", "0", CVAR_ARCHIVE | CVAR_LATCH);
+
+ //
+ // temporary latched variables that can only change over a restart
+ //
+ r_fullbright = ri.Cvar_Get ("r_fullbright", "0", CVAR_LATCH|CVAR_CHEAT );
+ r_mapOverBrightBits = ri.Cvar_Get ("r_mapOverBrightBits", "2", CVAR_LATCH );
+ r_intensity = ri.Cvar_Get ("r_intensity", "1", CVAR_LATCH );
+ r_singleShader = ri.Cvar_Get ("r_singleShader", "0", CVAR_CHEAT | CVAR_LATCH );
+
+ //
+ // archived variables that can change at any time
+ //
+ r_lodCurveError = ri.Cvar_Get( "r_lodCurveError", "250", CVAR_ARCHIVE|CVAR_CHEAT );
+ r_lodbias = ri.Cvar_Get( "r_lodbias", "0", CVAR_ARCHIVE );
+ r_flares = ri.Cvar_Get ("r_flares", "0", CVAR_ARCHIVE );
+ r_znear = ri.Cvar_Get( "r_znear", "4", CVAR_CHEAT );
+ ri.Cvar_CheckRange( r_znear, 0.001f, 200, qfalse );
+ r_zproj = ri.Cvar_Get( "r_zproj", "64", CVAR_ARCHIVE );
+ r_stereoSeparation = ri.Cvar_Get( "r_stereoSeparation", "64", CVAR_ARCHIVE );
+ r_ignoreGLErrors = ri.Cvar_Get( "r_ignoreGLErrors", "1", CVAR_ARCHIVE );
+ r_fastsky = ri.Cvar_Get( "r_fastsky", "0", CVAR_ARCHIVE );
+ r_inGameVideo = ri.Cvar_Get( "r_inGameVideo", "1", CVAR_ARCHIVE );
+ r_drawSun = ri.Cvar_Get( "r_drawSun", "0", CVAR_ARCHIVE );
+ r_dynamiclight = ri.Cvar_Get( "r_dynamiclight", "1", CVAR_ARCHIVE );
+ r_dlightBacks = ri.Cvar_Get( "r_dlightBacks", "1", CVAR_ARCHIVE );
+ r_finish = ri.Cvar_Get ("r_finish", "0", CVAR_ARCHIVE);
+ r_textureMode = ri.Cvar_Get( "r_textureMode", "GL_LINEAR_MIPMAP_NEAREST", CVAR_ARCHIVE );
+ r_swapInterval = ri.Cvar_Get( "r_swapInterval", "0",
+ CVAR_ARCHIVE | CVAR_LATCH );
+ r_gamma = ri.Cvar_Get( "r_gamma", "1", CVAR_ARCHIVE );
+ r_facePlaneCull = ri.Cvar_Get ("r_facePlaneCull", "1", CVAR_ARCHIVE );
+
+ r_railWidth = ri.Cvar_Get( "r_railWidth", "16", CVAR_ARCHIVE );
+ r_railCoreWidth = ri.Cvar_Get( "r_railCoreWidth", "6", CVAR_ARCHIVE );
+ r_railSegmentLength = ri.Cvar_Get( "r_railSegmentLength", "32", CVAR_ARCHIVE );
+
+ r_primitives = ri.Cvar_Get( "r_primitives", "0", CVAR_ARCHIVE );
+
+ r_ambientScale = ri.Cvar_Get( "r_ambientScale", "0.6", CVAR_CHEAT );
+ r_directedScale = ri.Cvar_Get( "r_directedScale", "1", CVAR_CHEAT );
+
+ r_anaglyphMode = ri.Cvar_Get("r_anaglyphMode", "0", CVAR_ARCHIVE);
+
+ //
+ // temporary variables that can change at any time
+ //
+ r_showImages = ri.Cvar_Get( "r_showImages", "0", CVAR_TEMP );
+
+ r_debugLight = ri.Cvar_Get( "r_debuglight", "0", CVAR_TEMP );
+ r_debugSort = ri.Cvar_Get( "r_debugSort", "0", CVAR_CHEAT );
+ r_printShaders = ri.Cvar_Get( "r_printShaders", "0", 0 );
+ r_saveFontData = ri.Cvar_Get( "r_saveFontData", "0", 0 );
+
+ r_nocurves = ri.Cvar_Get ("r_nocurves", "0", CVAR_CHEAT );
+ r_drawworld = ri.Cvar_Get ("r_drawworld", "1", CVAR_CHEAT );
+ r_lightmap = ri.Cvar_Get ("r_lightmap", "0", 0 );
+ r_portalOnly = ri.Cvar_Get ("r_portalOnly", "0", CVAR_CHEAT );
+
+ r_flareSize = ri.Cvar_Get ("r_flareSize", "40", CVAR_CHEAT);
+ r_flareFade = ri.Cvar_Get ("r_flareFade", "7", CVAR_CHEAT);
+ r_flareCoeff = ri.Cvar_Get ("r_flareCoeff", FLARE_STDCOEFF, CVAR_CHEAT);
+
+ r_showSmp = ri.Cvar_Get ("r_showSmp", "0", CVAR_CHEAT);
+ r_skipBackEnd = ri.Cvar_Get ("r_skipBackEnd", "0", CVAR_CHEAT);
+
+ r_measureOverdraw = ri.Cvar_Get( "r_measureOverdraw", "0", CVAR_CHEAT );
+ r_lodscale = ri.Cvar_Get( "r_lodscale", "5", CVAR_CHEAT );
+ r_norefresh = ri.Cvar_Get ("r_norefresh", "0", CVAR_CHEAT);
+ r_drawentities = ri.Cvar_Get ("r_drawentities", "1", CVAR_CHEAT );
+ r_ignore = ri.Cvar_Get( "r_ignore", "1", CVAR_CHEAT );
+ r_nocull = ri.Cvar_Get ("r_nocull", "0", CVAR_CHEAT);
+ r_novis = ri.Cvar_Get ("r_novis", "0", CVAR_CHEAT);
+ r_showcluster = ri.Cvar_Get ("r_showcluster", "0", CVAR_CHEAT);
+ r_speeds = ri.Cvar_Get ("r_speeds", "0", CVAR_CHEAT);
+ r_verbose = ri.Cvar_Get( "r_verbose", "0", CVAR_CHEAT );
+ r_logFile = ri.Cvar_Get( "r_logFile", "0", CVAR_CHEAT );
+ r_debugSurface = ri.Cvar_Get ("r_debugSurface", "0", CVAR_CHEAT);
+ r_nobind = ri.Cvar_Get ("r_nobind", "0", CVAR_CHEAT);
+ r_showtris = ri.Cvar_Get ("r_showtris", "0", CVAR_CHEAT);
+ r_showsky = ri.Cvar_Get ("r_showsky", "0", CVAR_CHEAT);
+ r_shownormals = ri.Cvar_Get ("r_shownormals", "0", CVAR_CHEAT);
+ r_clear = ri.Cvar_Get ("r_clear", "0", CVAR_CHEAT);
+ r_offsetFactor = ri.Cvar_Get( "r_offsetfactor", "-1", CVAR_CHEAT );
+ r_offsetUnits = ri.Cvar_Get( "r_offsetunits", "-2", CVAR_CHEAT );
+ r_drawBuffer = ri.Cvar_Get( "r_drawBuffer", "GL_BACK", CVAR_CHEAT );
+ r_lockpvs = ri.Cvar_Get ("r_lockpvs", "0", CVAR_CHEAT);
+ r_noportals = ri.Cvar_Get ("r_noportals", "0", CVAR_CHEAT);
+ r_shadows = ri.Cvar_Get( "cg_shadows", "1", 0 );
+
+ r_marksOnTriangleMeshes = ri.Cvar_Get("r_marksOnTriangleMeshes", "0", CVAR_ARCHIVE);
+
+ r_maxpolys = ri.Cvar_Get( "r_maxpolys", va("%d", MAX_POLYS), 0);
+ r_maxpolyverts = ri.Cvar_Get( "r_maxpolyverts", va("%d", MAX_POLYVERTS), 0);
+
+ // make sure all the commands added here are also
+ // removed in R_Shutdown
+ ri.Cmd_AddCommand( "imagelist", R_ImageList_f );
+ ri.Cmd_AddCommand( "shaderlist", R_ShaderList_f );
+ ri.Cmd_AddCommand( "skinlist", R_SkinList_f );
+ ri.Cmd_AddCommand( "modellist", R_Modellist_f );
+ ri.Cmd_AddCommand( "modelist", R_ModeList_f );
+ ri.Cmd_AddCommand( "screenshot", R_ScreenShot_f );
+ ri.Cmd_AddCommand( "screenshotJPEG", R_ScreenShotJPEG_f );
+ ri.Cmd_AddCommand( "gfxinfo", GfxInfo_f );
+}
+
+/*
+===============
+R_Init
+===============
+*/
+void R_Init( void ) {
+ int err;
+ int i;
+ byte *ptr;
+
+ ri.Printf( PRINT_ALL, "----- R_Init -----\n" );
+
+ // clear all our internal state
+ Com_Memset( &tr, 0, sizeof( tr ) );
+ Com_Memset( &backEnd, 0, sizeof( backEnd ) );
+ Com_Memset( &tess, 0, sizeof( tess ) );
+
+ if(sizeof(glconfig_t) != 11332)
+ {
+ ri.Error( ERR_FATAL, "Mod ABI incompatible: sizeof(glconfig_t) == %zd != 11332", sizeof(glconfig_t));
+ }
+
+// Swap_Init();
+
+ if ( (intptr_t)tess.xyz & 15 ) {
+ Com_Printf( "WARNING: tess.xyz not 16 byte aligned\n" );
+ }
+ Com_Memset( tess.constantColor255, 255, sizeof( tess.constantColor255 ) );
+
+ //
+ // init function tables
+ //
+ for ( i = 0; i < FUNCTABLE_SIZE; i++ )
+ {
+ tr.sinTable[i] = sin( DEG2RAD( i * 360.0f / ( ( float ) ( FUNCTABLE_SIZE - 1 ) ) ) );
+ tr.squareTable[i] = ( i < FUNCTABLE_SIZE/2 ) ? 1.0f : -1.0f;
+ tr.sawToothTable[i] = (float)i / FUNCTABLE_SIZE;
+ tr.inverseSawToothTable[i] = 1.0f - tr.sawToothTable[i];
+
+ if ( i < FUNCTABLE_SIZE / 2 )
+ {
+ if ( i < FUNCTABLE_SIZE / 4 )
+ {
+ tr.triangleTable[i] = ( float ) i / ( FUNCTABLE_SIZE / 4 );
+ }
+ else
+ {
+ tr.triangleTable[i] = 1.0f - tr.triangleTable[i-FUNCTABLE_SIZE / 4];
+ }
+ }
+ else
+ {
+ tr.triangleTable[i] = -tr.triangleTable[i-FUNCTABLE_SIZE/2];
+ }
+ }
+
+ R_InitFogTable();
+
+ R_NoiseInit();
+
+ R_Register();
+
+ max_polys = r_maxpolys->integer;
+ if (max_polys < MAX_POLYS)
+ max_polys = MAX_POLYS;
+
+ max_polyverts = r_maxpolyverts->integer;
+ if (max_polyverts < MAX_POLYVERTS)
+ max_polyverts = MAX_POLYVERTS;
+
+ ptr = ri.Hunk_Alloc( sizeof( *backEndData[0] ) + sizeof(srfPoly_t) * max_polys + sizeof(polyVert_t) * max_polyverts, h_low);
+ backEndData[0] = (backEndData_t *) ptr;
+ backEndData[0]->polys = (srfPoly_t *) ((char *) ptr + sizeof( *backEndData[0] ));
+ backEndData[0]->polyVerts = (polyVert_t *) ((char *) ptr + sizeof( *backEndData[0] ) + sizeof(srfPoly_t) * max_polys);
+ if ( r_smp->integer ) {
+ ptr = ri.Hunk_Alloc( sizeof( *backEndData[1] ) + sizeof(srfPoly_t) * max_polys + sizeof(polyVert_t) * max_polyverts, h_low);
+ backEndData[1] = (backEndData_t *) ptr;
+ backEndData[1]->polys = (srfPoly_t *) ((char *) ptr + sizeof( *backEndData[1] ));
+ backEndData[1]->polyVerts = (polyVert_t *) ((char *) ptr + sizeof( *backEndData[1] ) + sizeof(srfPoly_t) * max_polys);
+ } else {
+ backEndData[1] = NULL;
+ }
+ R_ToggleSmpFrame();
+
+ InitOpenGL();
+
+ R_InitImages();
+
+ R_InitShaders();
+
+ R_InitSkins();
+
+ R_ModelInit();
+
+ R_InitFreeType();
+
+
+ err = qglGetError();
+ if ( err != GL_NO_ERROR )
+ ri.Printf (PRINT_ALL, "glGetError() = 0x%x\n", err);
+
+ ri.Printf( PRINT_ALL, "----- finished R_Init -----\n" );
+}
+
+/*
+===============
+RE_Shutdown
+===============
+*/
+void RE_Shutdown( qboolean destroyWindow ) {
+
+ ri.Printf( PRINT_ALL, "RE_Shutdown( %i )\n", destroyWindow );
+
+ ri.Cmd_RemoveCommand ("modellist");
+ ri.Cmd_RemoveCommand ("screenshotJPEG");
+ ri.Cmd_RemoveCommand ("screenshot");
+ ri.Cmd_RemoveCommand ("imagelist");
+ ri.Cmd_RemoveCommand ("shaderlist");
+ ri.Cmd_RemoveCommand ("skinlist");
+ ri.Cmd_RemoveCommand ("gfxinfo");
+ ri.Cmd_RemoveCommand( "modelist" );
+ ri.Cmd_RemoveCommand( "shaderstate" );
+
+
+ if ( tr.registered ) {
+ R_SyncRenderThread();
+ R_ShutdownCommandBuffers();
+ R_DeleteTextures();
+ }
+
+ R_DoneFreeType();
+
+ // shut down platform specific OpenGL stuff
+ if ( destroyWindow ) {
+ GLimp_Shutdown();
+ }
+
+ tr.registered = qfalse;
+}
+
+
+/*
+=============
+RE_EndRegistration
+
+Touch all images to make sure they are resident
+=============
+*/
+void RE_EndRegistration( void ) {
+ R_SyncRenderThread();
+ if (!Sys_LowPhysicalMemory()) {
+ RB_ShowImages();
+ }
+}
+
+
+/*
+@@@@@@@@@@@@@@@@@@@@@
+GetRefAPI
+
+@@@@@@@@@@@@@@@@@@@@@
+*/
+refexport_t *GetRefAPI ( int apiVersion, refimport_t *rimp ) {
+ static refexport_t re;
+
+ ri = *rimp;
+
+ Com_Memset( &re, 0, sizeof( re ) );
+
+ if ( apiVersion != REF_API_VERSION ) {
+ ri.Printf(PRINT_ALL, "Mismatched REF_API_VERSION: expected %i, got %i\n",
+ REF_API_VERSION, apiVersion );
+ return NULL;
+ }
+
+ // the RE_ functions are Renderer Entry points
+
+ re.Shutdown = RE_Shutdown;
+
+ re.BeginRegistration = RE_BeginRegistration;
+ re.RegisterModel = RE_RegisterModel;
+ re.RegisterSkin = RE_RegisterSkin;
+ re.RegisterShader = RE_RegisterShader;
+ re.RegisterShaderNoMip = RE_RegisterShaderNoMip;
+ re.LoadWorld = RE_LoadWorldMap;
+ re.SetWorldVisData = RE_SetWorldVisData;
+ re.EndRegistration = RE_EndRegistration;
+
+ re.BeginFrame = RE_BeginFrame;
+ re.EndFrame = RE_EndFrame;
+
+ re.MarkFragments = R_MarkFragments;
+ re.LerpTag = R_LerpTag;
+ re.ModelBounds = R_ModelBounds;
+
+ re.ClearScene = RE_ClearScene;
+ re.AddRefEntityToScene = RE_AddRefEntityToScene;
+ re.AddPolyToScene = RE_AddPolyToScene;
+ re.LightForPoint = R_LightForPoint;
+ re.AddLightToScene = RE_AddLightToScene;
+ re.AddAdditiveLightToScene = RE_AddAdditiveLightToScene;
+ re.RenderScene = RE_RenderScene;
+
+ re.SetColor = RE_SetColor;
+ re.DrawStretchPic = RE_StretchPic;
+ re.DrawStretchRaw = RE_StretchRaw;
+ re.UploadCinematic = RE_UploadCinematic;
+
+ re.RegisterFont = RE_RegisterFont;
+ re.RemapShader = R_RemapShader;
+ re.GetEntityToken = R_GetEntityToken;
+ re.inPVS = R_inPVS;
+
+ re.TakeVideoFrame = RE_TakeVideoFrame;
+
+ return &re;
+}
diff --git a/code/renderer/tr_light.c b/code/renderer/tr_light.c
new file mode 100644
index 0000000..4cc9cf2
--- /dev/null
+++ b/code/renderer/tr_light.c
@@ -0,0 +1,394 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// tr_light.c
+
+#include "tr_local.h"
+
+#define DLIGHT_AT_RADIUS 16
+// at the edge of a dlight's influence, this amount of light will be added
+
+#define DLIGHT_MINIMUM_RADIUS 16
+// never calculate a range less than this to prevent huge light numbers
+
+
+/*
+===============
+R_TransformDlights
+
+Transforms the origins of an array of dlights.
+Used by both the front end (for DlightBmodel) and
+the back end (before doing the lighting calculation)
+===============
+*/
+void R_TransformDlights( int count, dlight_t *dl, orientationr_t *or) {
+ int i;
+ vec3_t temp;
+
+ for ( i = 0 ; i < count ; i++, dl++ ) {
+ VectorSubtract( dl->origin, or->origin, temp );
+ dl->transformed[0] = DotProduct( temp, or->axis[0] );
+ dl->transformed[1] = DotProduct( temp, or->axis[1] );
+ dl->transformed[2] = DotProduct( temp, or->axis[2] );
+ }
+}
+
+/*
+=============
+R_DlightBmodel
+
+Determine which dynamic lights may effect this bmodel
+=============
+*/
+void R_DlightBmodel( bmodel_t *bmodel ) {
+ int i, j;
+ dlight_t *dl;
+ int mask;
+ msurface_t *surf;
+
+ // transform all the lights
+ R_TransformDlights( tr.refdef.num_dlights, tr.refdef.dlights, &tr.or );
+
+ mask = 0;
+ for ( i=0 ; i<tr.refdef.num_dlights ; i++ ) {
+ dl = &tr.refdef.dlights[i];
+
+ // see if the point is close enough to the bounds to matter
+ for ( j = 0 ; j < 3 ; j++ ) {
+ if ( dl->transformed[j] - bmodel->bounds[1][j] > dl->radius ) {
+ break;
+ }
+ if ( bmodel->bounds[0][j] - dl->transformed[j] > dl->radius ) {
+ break;
+ }
+ }
+ if ( j < 3 ) {
+ continue;
+ }
+
+ // we need to check this light
+ mask |= 1 << i;
+ }
+
+ tr.currentEntity->needDlights = (mask != 0);
+
+ // set the dlight bits in all the surfaces
+ for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) {
+ surf = bmodel->firstSurface + i;
+
+ if ( *surf->data == SF_FACE ) {
+ ((srfSurfaceFace_t *)surf->data)->dlightBits[ tr.smpFrame ] = mask;
+ } else if ( *surf->data == SF_GRID ) {
+ ((srfGridMesh_t *)surf->data)->dlightBits[ tr.smpFrame ] = mask;
+ } else if ( *surf->data == SF_TRIANGLES ) {
+ ((srfTriangles_t *)surf->data)->dlightBits[ tr.smpFrame ] = mask;
+ }
+ }
+}
+
+
+/*
+=============================================================================
+
+LIGHT SAMPLING
+
+=============================================================================
+*/
+
+extern cvar_t *r_ambientScale;
+extern cvar_t *r_directedScale;
+extern cvar_t *r_debugLight;
+
+/*
+=================
+R_SetupEntityLightingGrid
+
+=================
+*/
+static void R_SetupEntityLightingGrid( trRefEntity_t *ent ) {
+ vec3_t lightOrigin;
+ int pos[3];
+ int i, j;
+ byte *gridData;
+ float frac[3];
+ int gridStep[3];
+ vec3_t direction;
+ float totalFactor;
+
+ if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) {
+ // seperate lightOrigins are needed so an object that is
+ // sinking into the ground can still be lit, and so
+ // multi-part models can be lit identically
+ VectorCopy( ent->e.lightingOrigin, lightOrigin );
+ } else {
+ VectorCopy( ent->e.origin, lightOrigin );
+ }
+
+ VectorSubtract( lightOrigin, tr.world->lightGridOrigin, lightOrigin );
+ for ( i = 0 ; i < 3 ; i++ ) {
+ float v;
+
+ v = lightOrigin[i]*tr.world->lightGridInverseSize[i];
+ pos[i] = floor( v );
+ frac[i] = v - pos[i];
+ if ( pos[i] < 0 ) {
+ pos[i] = 0;
+ } else if ( pos[i] >= tr.world->lightGridBounds[i] - 1 ) {
+ pos[i] = tr.world->lightGridBounds[i] - 1;
+ }
+ }
+
+ VectorClear( ent->ambientLight );
+ VectorClear( ent->directedLight );
+ VectorClear( direction );
+
+ assert( tr.world->lightGridData ); // NULL with -nolight maps
+
+ // trilerp the light value
+ gridStep[0] = 8;
+ gridStep[1] = 8 * tr.world->lightGridBounds[0];
+ gridStep[2] = 8 * tr.world->lightGridBounds[0] * tr.world->lightGridBounds[1];
+ gridData = tr.world->lightGridData + pos[0] * gridStep[0]
+ + pos[1] * gridStep[1] + pos[2] * gridStep[2];
+
+ totalFactor = 0;
+ for ( i = 0 ; i < 8 ; i++ ) {
+ float factor;
+ byte *data;
+ int lat, lng;
+ vec3_t normal;
+ #if idppc
+ float d0, d1, d2, d3, d4, d5;
+ #endif
+ factor = 1.0;
+ data = gridData;
+ for ( j = 0 ; j < 3 ; j++ ) {
+ if ( i & (1<<j) ) {
+ factor *= frac[j];
+ data += gridStep[j];
+ } else {
+ factor *= (1.0f - frac[j]);
+ }
+ }
+
+ if ( !(data[0]+data[1]+data[2]) ) {
+ continue; // ignore samples in walls
+ }
+ totalFactor += factor;
+ #if idppc
+ d0 = data[0]; d1 = data[1]; d2 = data[2];
+ d3 = data[3]; d4 = data[4]; d5 = data[5];
+
+ ent->ambientLight[0] += factor * d0;
+ ent->ambientLight[1] += factor * d1;
+ ent->ambientLight[2] += factor * d2;
+
+ ent->directedLight[0] += factor * d3;
+ ent->directedLight[1] += factor * d4;
+ ent->directedLight[2] += factor * d5;
+ #else
+ ent->ambientLight[0] += factor * data[0];
+ ent->ambientLight[1] += factor * data[1];
+ ent->ambientLight[2] += factor * data[2];
+
+ ent->directedLight[0] += factor * data[3];
+ ent->directedLight[1] += factor * data[4];
+ ent->directedLight[2] += factor * data[5];
+ #endif
+ lat = data[7];
+ lng = data[6];
+ lat *= (FUNCTABLE_SIZE/256);
+ lng *= (FUNCTABLE_SIZE/256);
+
+ // decode X as cos( lat ) * sin( long )
+ // decode Y as sin( lat ) * sin( long )
+ // decode Z as cos( long )
+
+ normal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+ normal[1] = tr.sinTable[lat] * tr.sinTable[lng];
+ normal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+
+ VectorMA( direction, factor, normal, direction );
+ }
+
+ if ( totalFactor > 0 && totalFactor < 0.99 ) {
+ totalFactor = 1.0f / totalFactor;
+ VectorScale( ent->ambientLight, totalFactor, ent->ambientLight );
+ VectorScale( ent->directedLight, totalFactor, ent->directedLight );
+ }
+
+ VectorScale( ent->ambientLight, r_ambientScale->value, ent->ambientLight );
+ VectorScale( ent->directedLight, r_directedScale->value, ent->directedLight );
+
+ VectorNormalize2( direction, ent->lightDir );
+}
+
+
+/*
+===============
+LogLight
+===============
+*/
+static void LogLight( trRefEntity_t *ent ) {
+ int max1, max2;
+
+ if ( !(ent->e.renderfx & RF_FIRST_PERSON ) ) {
+ return;
+ }
+
+ max1 = ent->ambientLight[0];
+ if ( ent->ambientLight[1] > max1 ) {
+ max1 = ent->ambientLight[1];
+ } else if ( ent->ambientLight[2] > max1 ) {
+ max1 = ent->ambientLight[2];
+ }
+
+ max2 = ent->directedLight[0];
+ if ( ent->directedLight[1] > max2 ) {
+ max2 = ent->directedLight[1];
+ } else if ( ent->directedLight[2] > max2 ) {
+ max2 = ent->directedLight[2];
+ }
+
+ ri.Printf( PRINT_ALL, "amb:%i dir:%i\n", max1, max2 );
+}
+
+/*
+=================
+R_SetupEntityLighting
+
+Calculates all the lighting values that will be used
+by the Calc_* functions
+=================
+*/
+void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ) {
+ int i;
+ dlight_t *dl;
+ float power;
+ vec3_t dir;
+ float d;
+ vec3_t lightDir;
+ vec3_t lightOrigin;
+
+ // lighting calculations
+ if ( ent->lightingCalculated ) {
+ return;
+ }
+ ent->lightingCalculated = qtrue;
+
+ //
+ // trace a sample point down to find ambient light
+ //
+ if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) {
+ // seperate lightOrigins are needed so an object that is
+ // sinking into the ground can still be lit, and so
+ // multi-part models can be lit identically
+ VectorCopy( ent->e.lightingOrigin, lightOrigin );
+ } else {
+ VectorCopy( ent->e.origin, lightOrigin );
+ }
+
+ // if NOWORLDMODEL, only use dynamic lights (menu system, etc)
+ if ( !(refdef->rdflags & RDF_NOWORLDMODEL )
+ && tr.world->lightGridData ) {
+ R_SetupEntityLightingGrid( ent );
+ } else {
+ ent->ambientLight[0] = ent->ambientLight[1] =
+ ent->ambientLight[2] = tr.identityLight * 150;
+ ent->directedLight[0] = ent->directedLight[1] =
+ ent->directedLight[2] = tr.identityLight * 150;
+ VectorCopy( tr.sunDirection, ent->lightDir );
+ }
+
+ // bonus items and view weapons have a fixed minimum add
+ if ( 1 /* ent->e.renderfx & RF_MINLIGHT */ ) {
+ // give everything a minimum light add
+ ent->ambientLight[0] += tr.identityLight * 32;
+ ent->ambientLight[1] += tr.identityLight * 32;
+ ent->ambientLight[2] += tr.identityLight * 32;
+ }
+
+ //
+ // modify the light by dynamic lights
+ //
+ d = VectorLength( ent->directedLight );
+ VectorScale( ent->lightDir, d, lightDir );
+
+ for ( i = 0 ; i < refdef->num_dlights ; i++ ) {
+ dl = &refdef->dlights[i];
+ VectorSubtract( dl->origin, lightOrigin, dir );
+ d = VectorNormalize( dir );
+
+ power = DLIGHT_AT_RADIUS * ( dl->radius * dl->radius );
+ if ( d < DLIGHT_MINIMUM_RADIUS ) {
+ d = DLIGHT_MINIMUM_RADIUS;
+ }
+ d = power / ( d * d );
+
+ VectorMA( ent->directedLight, d, dl->color, ent->directedLight );
+ VectorMA( lightDir, d, dir, lightDir );
+ }
+
+ // clamp ambient
+ for ( i = 0 ; i < 3 ; i++ ) {
+ if ( ent->ambientLight[i] > tr.identityLightByte ) {
+ ent->ambientLight[i] = tr.identityLightByte;
+ }
+ }
+
+ if ( r_debugLight->integer ) {
+ LogLight( ent );
+ }
+
+ // save out the byte packet version
+ ((byte *)&ent->ambientLightInt)[0] = myftol( ent->ambientLight[0] );
+ ((byte *)&ent->ambientLightInt)[1] = myftol( ent->ambientLight[1] );
+ ((byte *)&ent->ambientLightInt)[2] = myftol( ent->ambientLight[2] );
+ ((byte *)&ent->ambientLightInt)[3] = 0xff;
+
+ // transform the direction to local space
+ VectorNormalize( lightDir );
+ ent->lightDir[0] = DotProduct( lightDir, ent->e.axis[0] );
+ ent->lightDir[1] = DotProduct( lightDir, ent->e.axis[1] );
+ ent->lightDir[2] = DotProduct( lightDir, ent->e.axis[2] );
+}
+
+/*
+=================
+R_LightForPoint
+=================
+*/
+int R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir )
+{
+ trRefEntity_t ent;
+
+ if ( tr.world->lightGridData == NULL )
+ return qfalse;
+
+ Com_Memset(&ent, 0, sizeof(ent));
+ VectorCopy( point, ent.e.origin );
+ R_SetupEntityLightingGrid( &ent );
+ VectorCopy(ent.ambientLight, ambientLight);
+ VectorCopy(ent.directedLight, directedLight);
+ VectorCopy(ent.lightDir, lightDir);
+
+ return qtrue;
+}
diff --git a/code/renderer/tr_local.h b/code/renderer/tr_local.h
new file mode 100644
index 0000000..cd778fd
--- /dev/null
+++ b/code/renderer/tr_local.h
@@ -0,0 +1,1720 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+
+#ifndef TR_LOCAL_H
+#define TR_LOCAL_H
+
+#include "../qcommon/q_shared.h"
+#include "../qcommon/qfiles.h"
+#include "../qcommon/qcommon.h"
+#include "tr_public.h"
+#include "qgl.h"
+
+#ifdef PANDORA
+#define GL_INDEX_TYPE GL_UNSIGNED_SHORT
+typedef unsigned short glIndex_t;
+#else
+#define GL_INDEX_TYPE GL_UNSIGNED_INT
+typedef unsigned int glIndex_t;
+#endif
+
+// fast float to int conversion
+#if id386 && !defined(__GNUC__)
+long myftol( float f );
+#else
+#define myftol(x) ((int)(x))
+#endif
+
+
+// everything that is needed by the backend needs
+// to be double buffered to allow it to run in
+// parallel on a dual cpu machine
+#define SMP_FRAMES 2
+
+// 12 bits
+// see QSORT_SHADERNUM_SHIFT
+#define MAX_SHADERS 16384
+
+//#define MAX_SHADER_STATES 2048
+#define MAX_STATES_PER_SHADER 32
+#define MAX_STATE_NAME 32
+
+// can't be increased without changing bit packing for drawsurfs
+
+
+typedef struct dlight_s {
+ vec3_t origin;
+ vec3_t color; // range from 0.0 to 1.0, should be color normalized
+ float radius;
+
+ vec3_t transformed; // origin in local coordinate system
+ int additive; // texture detail is lost tho when the lightmap is dark
+} dlight_t;
+
+
+// a trRefEntity_t has all the information passed in by
+// the client game, as well as some locally derived info
+typedef struct {
+ refEntity_t e;
+
+ float axisLength; // compensate for non-normalized axis
+
+ qboolean needDlights; // true for bmodels that touch a dlight
+ qboolean lightingCalculated;
+ vec3_t lightDir; // normalized direction towards light
+ vec3_t ambientLight; // color normalized to 0-255
+ int ambientLightInt; // 32 bit rgba packed
+ vec3_t directedLight;
+} trRefEntity_t;
+
+
+typedef struct {
+ vec3_t origin; // in world coordinates
+ vec3_t axis[3]; // orientation in world
+ vec3_t viewOrigin; // viewParms->or.origin in local coordinates
+ float modelMatrix[16];
+} orientationr_t;
+
+typedef struct image_s {
+ char imgName[MAX_QPATH]; // game path, including extension
+ int width, height; // source image
+ int uploadWidth, uploadHeight; // after power of two and picmip but not including clamp to MAX_TEXTURE_SIZE
+ GLuint texnum; // gl texture binding
+
+ int frameUsed; // for texture usage in frame statistics
+
+ int internalFormat;
+ int TMU; // only needed for voodoo2
+
+ qboolean mipmap;
+ qboolean allowPicmip;
+ int wrapClampMode; // GL_CLAMP_TO_EDGE or GL_REPEAT
+
+ struct image_s* next;
+} image_t;
+
+//===============================================================================
+
+typedef enum {
+ SS_BAD,
+ SS_PORTAL, // mirrors, portals, viewscreens
+ SS_ENVIRONMENT, // sky box
+ SS_OPAQUE, // opaque
+
+ SS_DECAL, // scorch marks, etc.
+ SS_SEE_THROUGH, // ladders, grates, grills that may have small blended edges
+ // in addition to alpha test
+ SS_BANNER,
+
+ SS_FOG,
+
+ SS_UNDERWATER, // for items that should be drawn in front of the water plane
+
+ SS_BLEND0, // regular transparency and filters
+ SS_BLEND1, // generally only used for additive type effects
+ SS_BLEND2,
+ SS_BLEND3,
+
+ SS_BLEND6,
+ SS_STENCIL_SHADOW,
+ SS_ALMOST_NEAREST, // gun smoke puffs
+
+ SS_NEAREST // blood blobs
+} shaderSort_t;
+
+
+#define MAX_SHADER_STAGES 8
+
+typedef enum {
+ GF_NONE,
+
+ GF_SIN,
+ GF_SQUARE,
+ GF_TRIANGLE,
+ GF_SAWTOOTH,
+ GF_INVERSE_SAWTOOTH,
+
+ GF_NOISE
+
+} genFunc_t;
+
+
+typedef enum {
+ DEFORM_NONE,
+ DEFORM_WAVE,
+ DEFORM_NORMALS,
+ DEFORM_BULGE,
+ DEFORM_MOVE,
+ DEFORM_PROJECTION_SHADOW,
+ DEFORM_AUTOSPRITE,
+ DEFORM_AUTOSPRITE2,
+ DEFORM_TEXT0,
+ DEFORM_TEXT1,
+ DEFORM_TEXT2,
+ DEFORM_TEXT3,
+ DEFORM_TEXT4,
+ DEFORM_TEXT5,
+ DEFORM_TEXT6,
+ DEFORM_TEXT7
+} deform_t;
+
+typedef enum {
+ AGEN_IDENTITY,
+ AGEN_SKIP,
+ AGEN_ENTITY,
+ AGEN_ONE_MINUS_ENTITY,
+ AGEN_VERTEX,
+ AGEN_ONE_MINUS_VERTEX,
+ AGEN_LIGHTING_SPECULAR,
+ AGEN_WAVEFORM,
+ AGEN_PORTAL,
+ AGEN_CONST
+} alphaGen_t;
+
+typedef enum {
+ CGEN_BAD,
+ CGEN_IDENTITY_LIGHTING, // tr.identityLight
+ CGEN_IDENTITY, // always (1,1,1,1)
+ CGEN_ENTITY, // grabbed from entity's modulate field
+ CGEN_ONE_MINUS_ENTITY, // grabbed from 1 - entity.modulate
+ CGEN_EXACT_VERTEX, // tess.vertexColors
+ CGEN_VERTEX, // tess.vertexColors * tr.identityLight
+ CGEN_ONE_MINUS_VERTEX,
+ CGEN_WAVEFORM, // programmatically generated
+ CGEN_LIGHTING_DIFFUSE,
+ CGEN_FOG, // standard fog
+ CGEN_CONST // fixed color
+} colorGen_t;
+
+typedef enum {
+ TCGEN_BAD,
+ TCGEN_IDENTITY, // clear to 0,0
+ TCGEN_LIGHTMAP,
+ TCGEN_TEXTURE,
+ TCGEN_ENVIRONMENT_MAPPED,
+ TCGEN_FOG,
+ TCGEN_VECTOR // S and T from world coordinates
+} texCoordGen_t;
+
+typedef enum {
+ ACFF_NONE,
+ ACFF_MODULATE_RGB,
+ ACFF_MODULATE_RGBA,
+ ACFF_MODULATE_ALPHA
+} acff_t;
+
+typedef struct {
+ genFunc_t func;
+
+ float base;
+ float amplitude;
+ float phase;
+ float frequency;
+} waveForm_t;
+
+#define TR_MAX_TEXMODS 4
+
+typedef enum {
+ TMOD_NONE,
+ TMOD_TRANSFORM,
+ TMOD_TURBULENT,
+ TMOD_SCROLL,
+ TMOD_SCALE,
+ TMOD_STRETCH,
+ TMOD_ROTATE,
+ TMOD_ENTITY_TRANSLATE
+} texMod_t;
+
+#define MAX_SHADER_DEFORMS 3
+typedef struct {
+ deform_t deformation; // vertex coordinate modification type
+
+ vec3_t moveVector;
+ waveForm_t deformationWave;
+ float deformationSpread;
+
+ float bulgeWidth;
+ float bulgeHeight;
+ float bulgeSpeed;
+} deformStage_t;
+
+
+typedef struct {
+ texMod_t type;
+
+ // used for TMOD_TURBULENT and TMOD_STRETCH
+ waveForm_t wave;
+
+ // used for TMOD_TRANSFORM
+ float matrix[2][2]; // s' = s * m[0][0] + t * m[1][0] + trans[0]
+ float translate[2]; // t' = s * m[0][1] + t * m[0][1] + trans[1]
+
+ // used for TMOD_SCALE
+ float scale[2]; // s *= scale[0]
+ // t *= scale[1]
+
+ // used for TMOD_SCROLL
+ float scroll[2]; // s' = s + scroll[0] * time
+ // t' = t + scroll[1] * time
+
+ // + = clockwise
+ // - = counterclockwise
+ float rotateSpeed;
+
+} texModInfo_t;
+
+
+#define MAX_IMAGE_ANIMATIONS 8
+
+typedef struct {
+ image_t *image[MAX_IMAGE_ANIMATIONS];
+ int numImageAnimations;
+ float imageAnimationSpeed;
+
+ texCoordGen_t tcGen;
+ vec3_t tcGenVectors[2];
+
+ int numTexMods;
+ texModInfo_t *texMods;
+
+ int videoMapHandle;
+ qboolean isLightmap;
+ qboolean vertexLightmap;
+ qboolean isVideoMap;
+} textureBundle_t;
+
+#define NUM_TEXTURE_BUNDLES 2
+
+typedef struct {
+ qboolean active;
+
+ textureBundle_t bundle[NUM_TEXTURE_BUNDLES];
+
+ waveForm_t rgbWave;
+ colorGen_t rgbGen;
+
+ waveForm_t alphaWave;
+ alphaGen_t alphaGen;
+
+ byte constantColor[4]; // for CGEN_CONST and AGEN_CONST
+
+ unsigned stateBits; // GLS_xxxx mask
+
+ acff_t adjustColorsForFog;
+
+ qboolean isDetail;
+} shaderStage_t;
+
+struct shaderCommands_s;
+
+// any change in the LIGHTMAP_* defines here MUST be reflected in
+// R_FindShader() in tr_bsp.c
+#define LIGHTMAP_2D -4 // shader is for 2D rendering
+#define LIGHTMAP_BY_VERTEX -3 // pre-lit triangle models
+#define LIGHTMAP_WHITEIMAGE -2
+#define LIGHTMAP_NONE -1
+
+typedef enum {
+ CT_FRONT_SIDED,
+ CT_BACK_SIDED,
+ CT_TWO_SIDED
+} cullType_t;
+
+typedef enum {
+ FP_NONE, // surface is translucent and will just be adjusted properly
+ FP_EQUAL, // surface is opaque but possibly alpha tested
+ FP_LE // surface is trnaslucent, but still needs a fog pass (fog surface)
+} fogPass_t;
+
+typedef struct {
+ float cloudHeight;
+ image_t *outerbox[6], *innerbox[6];
+} skyParms_t;
+
+typedef struct {
+ vec3_t color;
+ float depthForOpaque;
+} fogParms_t;
+
+
+typedef struct shader_s {
+ char name[MAX_QPATH]; // game path, including extension
+ int lightmapIndex; // for a shader to match, both name and lightmapIndex must match
+
+ int index; // this shader == tr.shaders[index]
+ int sortedIndex; // this shader == tr.sortedShaders[sortedIndex]
+
+ float sort; // lower numbered shaders draw before higher numbered
+
+ qboolean defaultShader; // we want to return index 0 if the shader failed to
+ // load for some reason, but R_FindShader should
+ // still keep a name allocated for it, so if
+ // something calls RE_RegisterShader again with
+ // the same name, we don't try looking for it again
+
+ qboolean explicitlyDefined; // found in a .shader file
+
+ int surfaceFlags; // if explicitlyDefined, this will have SURF_* flags
+ int contentFlags;
+
+ qboolean entityMergable; // merge across entites optimizable (smoke, blood)
+
+ qboolean isSky;
+ skyParms_t sky;
+ fogParms_t fogParms;
+
+ float portalRange; // distance to fog out at
+
+ int multitextureEnv; // 0, GL_MODULATE, GL_ADD (FIXME: put in stage)
+
+ cullType_t cullType; // CT_FRONT_SIDED, CT_BACK_SIDED, or CT_TWO_SIDED
+ qboolean polygonOffset; // set for decals and other items that must be offset
+ qboolean noMipMaps; // for console fonts, 2D elements, etc.
+ qboolean noPicMip; // for images that must always be full resolution
+
+ fogPass_t fogPass; // draw a blended pass, possibly with depth test equals
+
+ qboolean needsNormal; // not all shaders will need all data to be gathered
+ qboolean needsST1;
+ qboolean needsST2;
+ qboolean needsColor;
+
+ int numDeforms;
+ deformStage_t deforms[MAX_SHADER_DEFORMS];
+
+ int numUnfoggedPasses;
+ shaderStage_t *stages[MAX_SHADER_STAGES];
+
+ void (*optimalStageIteratorFunc)( void );
+
+ float clampTime; // time this shader is clamped to
+ float timeOffset; // current time offset for this shader
+
+ int numStates; // if non-zero this is a state shader
+ struct shader_s *currentShader; // current state if this is a state shader
+ struct shader_s *parentShader; // current state if this is a state shader
+ int currentState; // current state index for cycle purposes
+ long expireTime; // time in milliseconds this expires
+
+ struct shader_s *remappedShader; // current shader this one is remapped too
+
+ int shaderStates[MAX_STATES_PER_SHADER]; // index to valid shader states
+
+ struct shader_s *next;
+} shader_t;
+
+typedef struct shaderState_s {
+ char shaderName[MAX_QPATH]; // name of shader this state belongs to
+ char name[MAX_STATE_NAME]; // name of this state
+ char stateShader[MAX_QPATH]; // shader this name invokes
+ int cycleTime; // time this cycle lasts, <= 0 is forever
+ shader_t *shader;
+} shaderState_t;
+
+
+// trRefdef_t holds everything that comes in refdef_t,
+// as well as the locally generated scene information
+typedef struct {
+ int x, y, width, height;
+ float fov_x, fov_y;
+ vec3_t vieworg;
+ vec3_t viewaxis[3]; // transformation matrix
+
+ stereoFrame_t stereoFrame;
+
+ int time; // time in milliseconds for shader effects and other time dependent rendering issues
+ int rdflags; // RDF_NOWORLDMODEL, etc
+
+ // 1 bits will prevent the associated area from rendering at all
+ byte areamask[MAX_MAP_AREA_BYTES];
+ qboolean areamaskModified; // qtrue if areamask changed since last scene
+
+ float floatTime; // tr.refdef.time / 1000.0
+
+ // text messages for deform text shaders
+ char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH];
+
+ int num_entities;
+ trRefEntity_t *entities;
+
+ int num_dlights;
+ struct dlight_s *dlights;
+
+ int numPolys;
+ struct srfPoly_s *polys;
+
+ int numDrawSurfs;
+ struct drawSurf_s *drawSurfs;
+
+
+} trRefdef_t;
+
+
+//=================================================================================
+
+// skins allow models to be retextured without modifying the model file
+typedef struct {
+ char name[MAX_QPATH];
+ shader_t *shader;
+} skinSurface_t;
+
+typedef struct skin_s {
+ char name[MAX_QPATH]; // game path, including extension
+ int numSurfaces;
+ skinSurface_t *surfaces[MD3_MAX_SURFACES];
+} skin_t;
+
+
+typedef struct {
+ int originalBrushNumber;
+ vec3_t bounds[2];
+
+ unsigned colorInt; // in packed byte format
+ float tcScale; // texture coordinate vector scales
+ fogParms_t parms;
+
+ // for clipping distance in fog when outside
+ qboolean hasSurface;
+ float surface[4];
+} fog_t;
+
+typedef struct {
+ orientationr_t or;
+ orientationr_t world;
+ vec3_t pvsOrigin; // may be different than or.origin for portals
+ qboolean isPortal; // true if this view is through a portal
+ qboolean isMirror; // the portal is a mirror, invert the face culling
+ int frameSceneNum; // copied from tr.frameSceneNum
+ int frameCount; // copied from tr.frameCount
+ cplane_t portalPlane; // clip anything behind this if mirroring
+ int viewportX, viewportY, viewportWidth, viewportHeight;
+ float fovX, fovY;
+ float projectionMatrix[16];
+ cplane_t frustum[4];
+ vec3_t visBounds[2];
+ float zFar;
+ stereoFrame_t stereoFrame;
+} viewParms_t;
+
+
+/*
+==============================================================================
+
+SURFACES
+
+==============================================================================
+*/
+
+// any changes in surfaceType must be mirrored in rb_surfaceTable[]
+typedef enum {
+ SF_BAD,
+ SF_SKIP, // ignore
+ SF_FACE,
+ SF_GRID,
+ SF_TRIANGLES,
+ SF_POLY,
+ SF_MD3,
+ SF_MD4,
+#ifdef RAVENMD4
+ SF_MDR,
+#endif
+ SF_FLARE,
+ SF_ENTITY, // beams, rails, lightning, etc that can be determined by entity
+ SF_DISPLAY_LIST,
+
+ SF_NUM_SURFACE_TYPES,
+ SF_MAX = 0x7fffffff // ensures that sizeof( surfaceType_t ) == sizeof( int )
+} surfaceType_t;
+
+typedef struct drawSurf_s {
+ unsigned sort; // bit combination for fast compares
+ surfaceType_t *surface; // any of surface*_t
+} drawSurf_t;
+
+#define MAX_FACE_POINTS 64
+
+#define MAX_PATCH_SIZE 32 // max dimensions of a patch mesh in map file
+#define MAX_GRID_SIZE 65 // max dimensions of a grid mesh in memory
+
+// when cgame directly specifies a polygon, it becomes a srfPoly_t
+// as soon as it is called
+typedef struct srfPoly_s {
+ surfaceType_t surfaceType;
+ qhandle_t hShader;
+ int fogIndex;
+ int numVerts;
+ polyVert_t *verts;
+} srfPoly_t;
+
+typedef struct srfDisplayList_s {
+ surfaceType_t surfaceType;
+ int listNum;
+} srfDisplayList_t;
+
+
+typedef struct srfFlare_s {
+ surfaceType_t surfaceType;
+ vec3_t origin;
+ vec3_t normal;
+ vec3_t color;
+} srfFlare_t;
+
+typedef struct srfGridMesh_s {
+ surfaceType_t surfaceType;
+
+ // dynamic lighting information
+ int dlightBits[SMP_FRAMES];
+
+ // culling information
+ vec3_t meshBounds[2];
+ vec3_t localOrigin;
+ float meshRadius;
+
+ // lod information, which may be different
+ // than the culling information to allow for
+ // groups of curves that LOD as a unit
+ vec3_t lodOrigin;
+ float lodRadius;
+ int lodFixed;
+ int lodStitched;
+
+ // vertexes
+ int width, height;
+ float *widthLodError;
+ float *heightLodError;
+ drawVert_t verts[1]; // variable sized
+} srfGridMesh_t;
+
+
+
+#define VERTEXSIZE 8
+typedef struct {
+ surfaceType_t surfaceType;
+ cplane_t plane;
+
+ // dynamic lighting information
+ int dlightBits[SMP_FRAMES];
+
+ // triangle definitions (no normals at points)
+ int numPoints;
+ int numIndices;
+ int ofsIndices;
+ float points[1][VERTEXSIZE]; // variable sized
+ // there is a variable length list of indices here also
+} srfSurfaceFace_t;
+
+
+// misc_models in maps are turned into direct geometry by q3map
+typedef struct {
+ surfaceType_t surfaceType;
+
+ // dynamic lighting information
+ int dlightBits[SMP_FRAMES];
+
+ // culling information (FIXME: use this!)
+ vec3_t bounds[2];
+ vec3_t localOrigin;
+ float radius;
+
+ // triangle definitions
+ int numIndexes;
+ int *indexes;
+
+ int numVerts;
+ drawVert_t *verts;
+} srfTriangles_t;
+
+
+extern void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])(void *);
+
+/*
+==============================================================================
+
+BRUSH MODELS
+
+==============================================================================
+*/
+
+
+//
+// in memory representation
+//
+
+#define SIDE_FRONT 0
+#define SIDE_BACK 1
+#define SIDE_ON 2
+
+typedef struct msurface_s {
+ int viewCount; // if == tr.viewCount, already added
+ struct shader_s *shader;
+ int fogIndex;
+
+ surfaceType_t *data; // any of srf*_t
+} msurface_t;
+
+
+
+#define CONTENTS_NODE -1
+typedef struct mnode_s {
+ // common with leaf and node
+ int contents; // -1 for nodes, to differentiate from leafs
+ int visframe; // node needs to be traversed if current
+ vec3_t mins, maxs; // for bounding box culling
+ struct mnode_s *parent;
+
+ // node specific
+ cplane_t *plane;
+ struct mnode_s *children[2];
+
+ // leaf specific
+ int cluster;
+ int area;
+
+ msurface_t **firstmarksurface;
+ int nummarksurfaces;
+} mnode_t;
+
+typedef struct {
+ vec3_t bounds[2]; // for culling
+ msurface_t *firstSurface;
+ int numSurfaces;
+} bmodel_t;
+
+typedef struct {
+ char name[MAX_QPATH]; // ie: maps/tim_dm2.bsp
+ char baseName[MAX_QPATH]; // ie: tim_dm2
+
+ int dataSize;
+
+ int numShaders;
+ dshader_t *shaders;
+
+ bmodel_t *bmodels;
+
+ int numplanes;
+ cplane_t *planes;
+
+ int numnodes; // includes leafs
+ int numDecisionNodes;
+ mnode_t *nodes;
+
+ int numsurfaces;
+ msurface_t *surfaces;
+
+ int nummarksurfaces;
+ msurface_t **marksurfaces;
+
+ int numfogs;
+ fog_t *fogs;
+
+ vec3_t lightGridOrigin;
+ vec3_t lightGridSize;
+ vec3_t lightGridInverseSize;
+ int lightGridBounds[3];
+ byte *lightGridData;
+
+
+ int numClusters;
+ int clusterBytes;
+ const byte *vis; // may be passed in by CM_LoadMap to save space
+
+ byte *novis; // clusterBytes of 0xff
+
+ char *entityString;
+ char *entityParsePoint;
+} world_t;
+
+//======================================================================
+
+typedef enum {
+ MOD_BAD,
+ MOD_BRUSH,
+ MOD_MESH,
+ MOD_MD4,
+#ifdef RAVENMD4
+ MOD_MDR
+#endif
+} modtype_t;
+
+typedef struct model_s {
+ char name[MAX_QPATH];
+ modtype_t type;
+ int index; // model = tr.models[model->index]
+
+ int dataSize; // just for listing purposes
+ bmodel_t *bmodel; // only if type == MOD_BRUSH
+ md3Header_t *md3[MD3_MAX_LODS]; // only if type == MOD_MESH
+ void *md4; // only if type == (MOD_MD4 | MOD_MDR)
+
+ int numLods;
+} model_t;
+
+
+#define MAX_MOD_KNOWN 1024
+
+void R_ModelInit (void);
+model_t *R_GetModelByHandle( qhandle_t hModel );
+int R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame,
+ float frac, const char *tagName );
+void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs );
+
+void R_Modellist_f (void);
+
+//====================================================
+extern refimport_t ri;
+
+#define MAX_DRAWIMAGES 2048
+#define MAX_LIGHTMAPS 256
+#define MAX_SKINS 1024
+
+
+#define MAX_DRAWSURFS 0x10000
+#define DRAWSURF_MASK (MAX_DRAWSURFS-1)
+
+/*
+
+the drawsurf sort data is packed into a single 32 bit value so it can be
+compared quickly during the qsorting process
+
+the bits are allocated as follows:
+
+21 - 31 : sorted shader index
+11 - 20 : entity index
+2 - 6 : fog index
+//2 : used to be clipped flag REMOVED - 03.21.00 rad
+0 - 1 : dlightmap index
+
+ TTimo - 1.32
+17-31 : sorted shader index
+7-16 : entity index
+2-6 : fog index
+0-1 : dlightmap index
+*/
+#define QSORT_SHADERNUM_SHIFT 17
+#define QSORT_ENTITYNUM_SHIFT 7
+#define QSORT_FOGNUM_SHIFT 2
+
+extern int gl_filter_min, gl_filter_max;
+
+/*
+** performanceCounters_t
+*/
+typedef struct {
+ int c_sphere_cull_patch_in, c_sphere_cull_patch_clip, c_sphere_cull_patch_out;
+ int c_box_cull_patch_in, c_box_cull_patch_clip, c_box_cull_patch_out;
+ int c_sphere_cull_md3_in, c_sphere_cull_md3_clip, c_sphere_cull_md3_out;
+ int c_box_cull_md3_in, c_box_cull_md3_clip, c_box_cull_md3_out;
+
+ int c_leafs;
+ int c_dlightSurfaces;
+ int c_dlightSurfacesCulled;
+} frontEndCounters_t;
+
+#define FOG_TABLE_SIZE 256
+#define FUNCTABLE_SIZE 1024
+#define FUNCTABLE_SIZE2 10
+#define FUNCTABLE_MASK (FUNCTABLE_SIZE-1)
+
+
+// the renderer front end should never modify glstate_t
+typedef struct {
+ int currenttextures[2];
+ int currenttmu;
+ qboolean finishCalled;
+ int texEnv[2];
+ int faceCulling;
+ unsigned long glStateBits;
+} glstate_t;
+
+
+typedef struct {
+ int c_surfaces, c_shaders, c_vertexes, c_indexes, c_totalIndexes;
+ float c_overDraw;
+
+ int c_dlightVertexes;
+ int c_dlightIndexes;
+
+ int c_flareAdds;
+ int c_flareTests;
+ int c_flareRenders;
+
+ int msec; // total msec for backend run
+} backEndCounters_t;
+
+// all state modified by the back end is seperated
+// from the front end state
+typedef struct {
+ int smpFrame;
+ trRefdef_t refdef;
+ viewParms_t viewParms;
+ orientationr_t or;
+ backEndCounters_t pc;
+ qboolean isHyperspace;
+ trRefEntity_t *currentEntity;
+ qboolean skyRenderedThisView; // flag for drawing sun
+
+ qboolean projection2D; // if qtrue, drawstretchpic doesn't need to change modes
+ byte color2D[4];
+ qboolean vertexes2D; // shader needs to be finished
+ trRefEntity_t entity2D; // currentEntity will point at this when doing 2D rendering
+} backEndState_t;
+
+/*
+** trGlobals_t
+**
+** Most renderer globals are defined here.
+** backend functions should never modify any of these fields,
+** but may read fields that aren't dynamically modified
+** by the frontend.
+*/
+typedef struct {
+ qboolean registered; // cleared at shutdown, set at beginRegistration
+
+ int visCount; // incremented every time a new vis cluster is entered
+ int frameCount; // incremented every frame
+ int sceneCount; // incremented every scene
+ int viewCount; // incremented every view (twice a scene if portaled)
+ // and every R_MarkFragments call
+
+ int smpFrame; // toggles from 0 to 1 every endFrame
+
+ int frameSceneNum; // zeroed at RE_BeginFrame
+
+ qboolean worldMapLoaded;
+ world_t *world;
+
+ const byte *externalVisData; // from RE_SetWorldVisData, shared with CM_Load
+
+ image_t *defaultImage;
+ image_t *scratchImage[32];
+ image_t *fogImage;
+ image_t *dlightImage; // inverse-quare highlight for projective adding
+ image_t *flareImage;
+ image_t *whiteImage; // full of 0xff
+ image_t *identityLightImage; // full of tr.identityLightByte
+
+ shader_t *defaultShader;
+ shader_t *shadowShader;
+ shader_t *projectionShadowShader;
+
+ shader_t *flareShader;
+ shader_t *sunShader;
+
+ int numLightmaps;
+ image_t *lightmaps[MAX_LIGHTMAPS];
+
+ trRefEntity_t *currentEntity;
+ trRefEntity_t worldEntity; // point currentEntity at this when rendering world
+ int currentEntityNum;
+ int shiftedEntityNum; // currentEntityNum << QSORT_ENTITYNUM_SHIFT
+ model_t *currentModel;
+
+ viewParms_t viewParms;
+
+ float identityLight; // 1.0 / ( 1 << overbrightBits )
+ int identityLightByte; // identityLight * 255
+ int overbrightBits; // r_overbrightBits->integer, but set to 0 if no hw gamma
+
+ orientationr_t or; // for current entity
+
+ trRefdef_t refdef;
+
+ int viewCluster;
+
+ vec3_t sunLight; // from the sky shader for this level
+ vec3_t sunDirection;
+
+ frontEndCounters_t pc;
+ int frontEndMsec; // not in pc due to clearing issue
+
+ //
+ // put large tables at the end, so most elements will be
+ // within the +/32K indexed range on risc processors
+ //
+ model_t *models[MAX_MOD_KNOWN];
+ int numModels;
+
+ int numImages;
+ image_t *images[MAX_DRAWIMAGES];
+
+ // shader indexes from other modules will be looked up in tr.shaders[]
+ // shader indexes from drawsurfs will be looked up in sortedShaders[]
+ // lower indexed sortedShaders must be rendered first (opaque surfaces before translucent)
+ int numShaders;
+ shader_t *shaders[MAX_SHADERS];
+ shader_t *sortedShaders[MAX_SHADERS];
+
+ int numSkins;
+ skin_t *skins[MAX_SKINS];
+
+ float sinTable[FUNCTABLE_SIZE];
+ float squareTable[FUNCTABLE_SIZE];
+ float triangleTable[FUNCTABLE_SIZE];
+ float sawToothTable[FUNCTABLE_SIZE];
+ float inverseSawToothTable[FUNCTABLE_SIZE];
+ float fogTable[FOG_TABLE_SIZE];
+} trGlobals_t;
+
+extern backEndState_t backEnd;
+extern trGlobals_t tr;
+extern glconfig_t glConfig; // outside of TR since it shouldn't be cleared during ref re-init
+extern glstate_t glState; // outside of TR since it shouldn't be cleared during ref re-init
+
+// These two variables should live inside glConfig but can't because of compatibility issues to the original ID vms.
+// If you release a stand-alone game and your mod uses tr_types.h from this build you can safely move them to
+// the glconfig_t struct.
+extern qboolean textureFilterAnisotropic;
+extern int maxAnisotropy;
+extern float displayAspect;
+
+
+//
+// cvars
+//
+extern cvar_t *r_flareSize;
+extern cvar_t *r_flareFade;
+// coefficient for the flare intensity falloff function.
+#define FLARE_STDCOEFF "150"
+extern cvar_t *r_flareCoeff;
+
+extern cvar_t *r_railWidth;
+extern cvar_t *r_railCoreWidth;
+extern cvar_t *r_railSegmentLength;
+
+extern cvar_t *r_ignore; // used for debugging anything
+extern cvar_t *r_verbose; // used for verbose debug spew
+extern cvar_t *r_ignoreFastPath; // allows us to ignore our Tess fast paths
+
+extern cvar_t *r_znear; // near Z clip plane
+extern cvar_t *r_zproj; // z distance of projection plane
+extern cvar_t *r_stereoSeparation; // separation of cameras for stereo rendering
+
+extern cvar_t *r_stencilbits; // number of desired stencil bits
+extern cvar_t *r_depthbits; // number of desired depth bits
+extern cvar_t *r_colorbits; // number of desired color bits, only relevant for fullscreen
+extern cvar_t *r_texturebits; // number of desired texture bits
+extern cvar_t *r_ext_multisample;
+ // 0 = use framebuffer depth
+ // 16 = use 16-bit textures
+ // 32 = use 32-bit textures
+ // all else = error
+
+extern cvar_t *r_measureOverdraw; // enables stencil buffer overdraw measurement
+
+extern cvar_t *r_lodbias; // push/pull LOD transitions
+extern cvar_t *r_lodscale;
+
+extern cvar_t *r_primitives; // "0" = based on compiled vertex array existance
+ // "1" = glDrawElemet tristrips
+ // "2" = glDrawElements triangles
+ // "-1" = no drawing
+
+extern cvar_t *r_inGameVideo; // controls whether in game video should be draw
+extern cvar_t *r_fastsky; // controls whether sky should be cleared or drawn
+extern cvar_t *r_drawSun; // controls drawing of sun quad
+extern cvar_t *r_dynamiclight; // dynamic lights enabled/disabled
+extern cvar_t *r_dlightBacks; // dlight non-facing surfaces for continuity
+
+extern cvar_t *r_norefresh; // bypasses the ref rendering
+extern cvar_t *r_drawentities; // disable/enable entity rendering
+extern cvar_t *r_drawworld; // disable/enable world rendering
+extern cvar_t *r_speeds; // various levels of information display
+extern cvar_t *r_detailTextures; // enables/disables detail texturing stages
+extern cvar_t *r_novis; // disable/enable usage of PVS
+extern cvar_t *r_nocull;
+extern cvar_t *r_facePlaneCull; // enables culling of planar surfaces with back side test
+extern cvar_t *r_nocurves;
+extern cvar_t *r_showcluster;
+
+extern cvar_t *r_mode; // video mode
+extern cvar_t *r_fullscreen;
+extern cvar_t *r_noborder;
+extern cvar_t *r_gamma;
+extern cvar_t *r_ignorehwgamma; // overrides hardware gamma capabilities
+
+extern cvar_t *r_allowExtensions; // global enable/disable of OpenGL extensions
+extern cvar_t *r_ext_compressed_textures; // these control use of specific extensions
+extern cvar_t *r_ext_multitexture;
+extern cvar_t *r_ext_compiled_vertex_array;
+extern cvar_t *r_ext_texture_env_add;
+
+extern cvar_t *r_ext_texture_filter_anisotropic;
+extern cvar_t *r_ext_max_anisotropy;
+
+extern cvar_t *r_nobind; // turns off binding to appropriate textures
+extern cvar_t *r_singleShader; // make most world faces use default shader
+extern cvar_t *r_roundImagesDown;
+extern cvar_t *r_colorMipLevels; // development aid to see texture mip usage
+extern cvar_t *r_picmip; // controls picmip values
+extern cvar_t *r_finish;
+extern cvar_t *r_drawBuffer;
+extern cvar_t *r_glDriver;
+extern cvar_t *r_swapInterval;
+extern cvar_t *r_textureMode;
+extern cvar_t *r_offsetFactor;
+extern cvar_t *r_offsetUnits;
+
+extern cvar_t *r_fullbright; // avoid lightmap pass
+extern cvar_t *r_lightmap; // render lightmaps only
+extern cvar_t *r_vertexLight; // vertex lighting mode for better performance
+extern cvar_t *r_uiFullScreen; // ui is running fullscreen
+
+extern cvar_t *r_logFile; // number of frames to emit GL logs
+extern cvar_t *r_showtris; // enables wireframe rendering of the world
+extern cvar_t *r_showsky; // forces sky in front of all surfaces
+extern cvar_t *r_shownormals; // draws wireframe normals
+extern cvar_t *r_clear; // force screen clear every frame
+
+extern cvar_t *r_shadows; // controls shadows: 0 = none, 1 = blur, 2 = stencil, 3 = black planar projection
+extern cvar_t *r_flares; // light flares
+
+extern cvar_t *r_intensity;
+
+extern cvar_t *r_lockpvs;
+extern cvar_t *r_noportals;
+extern cvar_t *r_portalOnly;
+
+extern cvar_t *r_subdivisions;
+extern cvar_t *r_lodCurveError;
+extern cvar_t *r_smp;
+extern cvar_t *r_showSmp;
+extern cvar_t *r_skipBackEnd;
+
+extern cvar_t *r_stereoEnabled;
+extern cvar_t *r_anaglyphMode;
+
+extern cvar_t *r_greyscale;
+
+extern cvar_t *r_ignoreGLErrors;
+
+extern cvar_t *r_overBrightBits;
+extern cvar_t *r_mapOverBrightBits;
+
+extern cvar_t *r_debugSurface;
+extern cvar_t *r_simpleMipMaps;
+
+extern cvar_t *r_showImages;
+extern cvar_t *r_debugSort;
+
+extern cvar_t *r_printShaders;
+extern cvar_t *r_saveFontData;
+
+extern cvar_t *r_marksOnTriangleMeshes;
+
+extern cvar_t *r_GLlibCoolDownMsec;
+
+//====================================================================
+
+float R_NoiseGet4f( float x, float y, float z, float t );
+void R_NoiseInit( void );
+
+void R_SwapBuffers( int );
+
+void R_RenderView( viewParms_t *parms );
+
+void R_AddMD3Surfaces( trRefEntity_t *e );
+void R_AddNullModelSurfaces( trRefEntity_t *e );
+void R_AddBeamSurfaces( trRefEntity_t *e );
+void R_AddRailSurfaces( trRefEntity_t *e, qboolean isUnderwater );
+void R_AddLightningBoltSurfaces( trRefEntity_t *e );
+
+void R_AddPolygonSurfaces( void );
+
+void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader,
+ int *fogNum, int *dlightMap );
+
+void R_AddDrawSurf( surfaceType_t *surface, shader_t *shader, int fogIndex, int dlightMap );
+
+
+#define CULL_IN 0 // completely unclipped
+#define CULL_CLIP 1 // clipped by one or more planes
+#define CULL_OUT 2 // completely outside the clipping planes
+void R_LocalNormalToWorld (vec3_t local, vec3_t world);
+void R_LocalPointToWorld (vec3_t local, vec3_t world);
+int R_CullLocalBox (vec3_t bounds[2]);
+int R_CullPointAndRadius( vec3_t origin, float radius );
+int R_CullLocalPointAndRadius( vec3_t origin, float radius );
+
+void R_SetupProjection(viewParms_t *dest, float zProj, qboolean computeFrustum);
+void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms, orientationr_t *or );
+
+/*
+** GL wrapper/helper functions
+*/
+void GL_Bind( image_t *image );
+void GL_SetDefaultState (void);
+void GL_SelectTexture( int unit );
+void GL_TextureMode( const char *string );
+void GL_CheckErrors( void );
+void GL_State( unsigned long stateVector );
+void GL_TexEnv( int env );
+void GL_Cull( int cullType );
+
+#define GLS_SRCBLEND_ZERO 0x00000001
+#define GLS_SRCBLEND_ONE 0x00000002
+#define GLS_SRCBLEND_DST_COLOR 0x00000003
+#define GLS_SRCBLEND_ONE_MINUS_DST_COLOR 0x00000004
+#define GLS_SRCBLEND_SRC_ALPHA 0x00000005
+#define GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA 0x00000006
+#define GLS_SRCBLEND_DST_ALPHA 0x00000007
+#define GLS_SRCBLEND_ONE_MINUS_DST_ALPHA 0x00000008
+#define GLS_SRCBLEND_ALPHA_SATURATE 0x00000009
+#define GLS_SRCBLEND_BITS 0x0000000f
+
+#define GLS_DSTBLEND_ZERO 0x00000010
+#define GLS_DSTBLEND_ONE 0x00000020
+#define GLS_DSTBLEND_SRC_COLOR 0x00000030
+#define GLS_DSTBLEND_ONE_MINUS_SRC_COLOR 0x00000040
+#define GLS_DSTBLEND_SRC_ALPHA 0x00000050
+#define GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA 0x00000060
+#define GLS_DSTBLEND_DST_ALPHA 0x00000070
+#define GLS_DSTBLEND_ONE_MINUS_DST_ALPHA 0x00000080
+#define GLS_DSTBLEND_BITS 0x000000f0
+
+#define GLS_DEPTHMASK_TRUE 0x00000100
+
+#define GLS_POLYMODE_LINE 0x00001000
+
+#define GLS_DEPTHTEST_DISABLE 0x00010000
+#define GLS_DEPTHFUNC_EQUAL 0x00020000
+
+#define GLS_ATEST_GT_0 0x10000000
+#define GLS_ATEST_LT_80 0x20000000
+#define GLS_ATEST_GE_80 0x40000000
+#define GLS_ATEST_BITS 0x70000000
+
+#define GLS_DEFAULT GLS_DEPTHMASK_TRUE
+
+void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty);
+void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty);
+
+void RE_BeginFrame( stereoFrame_t stereoFrame );
+void RE_BeginRegistration( glconfig_t *glconfig );
+void RE_LoadWorldMap( const char *mapname );
+void RE_SetWorldVisData( const byte *vis );
+qhandle_t RE_RegisterModel( const char *name );
+qhandle_t RE_RegisterSkin( const char *name );
+void RE_Shutdown( qboolean destroyWindow );
+
+qboolean R_GetEntityToken( char *buffer, int size );
+
+model_t *R_AllocModel( void );
+
+void R_Init( void );
+image_t *R_FindImageFile( const char *name, qboolean mipmap, qboolean allowPicmip, int glWrapClampMode );
+
+image_t *R_CreateImage( const char *name, const byte *pic, int width, int height, qboolean mipmap
+ , qboolean allowPicmip, int wrapClampMode );
+qboolean R_GetModeInfo( int *width, int *height, float *windowAspect, int mode );
+
+void R_SetColorMappings( void );
+void R_GammaCorrect( byte *buffer, int bufSize );
+
+void R_ImageList_f( void );
+void R_SkinList_f( void );
+// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=516
+const void *RB_TakeScreenshotCmd( const void *data );
+void R_ScreenShot_f( void );
+
+void R_InitFogTable( void );
+float R_FogFactor( float s, float t );
+void R_InitImages( void );
+void R_DeleteTextures( void );
+int R_SumOfUsedImages( void );
+void R_InitSkins( void );
+skin_t *R_GetSkinByHandle( qhandle_t hSkin );
+
+int R_ComputeLOD( trRefEntity_t *ent );
+
+const void *RB_TakeVideoFrameCmd( const void *data );
+
+//
+// tr_shader.c
+//
+qhandle_t RE_RegisterShaderLightMap( const char *name, int lightmapIndex );
+qhandle_t RE_RegisterShader( const char *name );
+qhandle_t RE_RegisterShaderNoMip( const char *name );
+qhandle_t RE_RegisterShaderFromImage(const char *name, int lightmapIndex, image_t *image, qboolean mipRawImage);
+
+shader_t *R_FindShader( const char *name, int lightmapIndex, qboolean mipRawImage );
+shader_t *R_GetShaderByHandle( qhandle_t hShader );
+shader_t *R_GetShaderByState( int index, long *cycleTime );
+shader_t *R_FindShaderByName( const char *name );
+void R_InitShaders( void );
+void R_ShaderList_f( void );
+void R_RemapShader(const char *oldShader, const char *newShader, const char *timeOffset);
+
+/*
+====================================================================
+
+IMPLEMENTATION SPECIFIC FUNCTIONS
+
+====================================================================
+*/
+
+void GLimp_Init( void );
+void GLimp_Shutdown( void );
+void GLimp_EndFrame( void );
+
+qboolean GLimp_SpawnRenderThread( void (*function)( void ) );
+void *GLimp_RendererSleep( void );
+void GLimp_FrontEndSleep( void );
+void GLimp_WakeRenderer( void *data );
+
+void GLimp_LogComment( char *comment );
+
+// NOTE TTimo linux works with float gamma value, not the gamma table
+// the params won't be used, getting the r_gamma cvar directly
+void GLimp_SetGamma( unsigned char red[256],
+ unsigned char green[256],
+ unsigned char blue[256] );
+
+
+/*
+====================================================================
+
+TESSELATOR/SHADER DECLARATIONS
+
+====================================================================
+*/
+typedef byte color4ub_t[4];
+
+typedef struct stageVars
+{
+ color4ub_t colors[SHADER_MAX_VERTEXES];
+ vec2_t texcoords[NUM_TEXTURE_BUNDLES][SHADER_MAX_VERTEXES];
+} stageVars_t;
+
+
+typedef struct shaderCommands_s
+{
+ glIndex_t indexes[SHADER_MAX_INDEXES] ALIGN(16);
+ vec4_t xyz[SHADER_MAX_VERTEXES] ALIGN(16);
+ vec4_t normal[SHADER_MAX_VERTEXES] ALIGN(16);
+ vec2_t texCoords[SHADER_MAX_VERTEXES][2] ALIGN(16);
+ color4ub_t vertexColors[SHADER_MAX_VERTEXES] ALIGN(16);
+ int vertexDlightBits[SHADER_MAX_VERTEXES] ALIGN(16);
+
+ stageVars_t svars ALIGN(16);
+
+ color4ub_t constantColor255[SHADER_MAX_VERTEXES] ALIGN(16);
+
+ shader_t *shader;
+ float shaderTime;
+ int fogNum;
+
+ int dlightBits; // or together of all vertexDlightBits
+
+ int numIndexes;
+ int numVertexes;
+
+ // info extracted from current shader
+ int numPasses;
+ void (*currentStageIteratorFunc)( void );
+ shaderStage_t **xstages;
+} shaderCommands_t;
+
+extern shaderCommands_t tess;
+
+void RB_BeginSurface(shader_t *shader, int fogNum );
+void RB_EndSurface(void);
+void RB_CheckOverflow( int verts, int indexes );
+#define RB_CHECKOVERFLOW(v,i) if (tess.numVertexes + (v) >= SHADER_MAX_VERTEXES || tess.numIndexes + (i) >= SHADER_MAX_INDEXES ) {RB_CheckOverflow(v,i);}
+
+void RB_StageIteratorGeneric( void );
+void RB_StageIteratorSky( void );
+void RB_StageIteratorVertexLitTexture( void );
+void RB_StageIteratorLightmappedMultitexture( void );
+
+void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, byte *color );
+void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, byte *color, float s1, float t1, float s2, float t2 );
+
+void RB_ShowImages( void );
+
+
+/*
+============================================================
+
+WORLD MAP
+
+============================================================
+*/
+
+void R_AddBrushModelSurfaces( trRefEntity_t *e );
+void R_AddWorldSurfaces( void );
+qboolean R_inPVS( const vec3_t p1, const vec3_t p2 );
+
+
+/*
+============================================================
+
+FLARES
+
+============================================================
+*/
+
+void R_ClearFlares( void );
+
+void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal );
+void RB_AddDlightFlares( void );
+void RB_RenderFlares (void);
+
+/*
+============================================================
+
+LIGHTS
+
+============================================================
+*/
+
+void R_DlightBmodel( bmodel_t *bmodel );
+void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent );
+void R_TransformDlights( int count, dlight_t *dl, orientationr_t *or );
+int R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir );
+
+
+/*
+============================================================
+
+SHADOWS
+
+============================================================
+*/
+
+void RB_ShadowTessEnd( void );
+void RB_ShadowFinish( void );
+void RB_ProjectionShadowDeform( void );
+
+/*
+============================================================
+
+SKIES
+
+============================================================
+*/
+
+void R_BuildCloudData( shaderCommands_t *shader );
+void R_InitSkyTexCoords( float cloudLayerHeight );
+void R_DrawSkyBox( shaderCommands_t *shader );
+void RB_DrawSun( void );
+void RB_ClipSkyPolygons( shaderCommands_t *shader );
+
+/*
+============================================================
+
+CURVE TESSELATION
+
+============================================================
+*/
+
+#define PATCH_STITCHING
+
+srfGridMesh_t *R_SubdividePatchToGrid( int width, int height,
+ drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] );
+srfGridMesh_t *R_GridInsertColumn( srfGridMesh_t *grid, int column, int row, vec3_t point, float loderror );
+srfGridMesh_t *R_GridInsertRow( srfGridMesh_t *grid, int row, int column, vec3_t point, float loderror );
+void R_FreeSurfaceGridMesh( srfGridMesh_t *grid );
+
+/*
+============================================================
+
+MARKERS, POLYGON PROJECTION ON WORLD POLYGONS
+
+============================================================
+*/
+
+int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection,
+ int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer );
+
+
+/*
+============================================================
+
+SCENE GENERATION
+
+============================================================
+*/
+
+void R_ToggleSmpFrame( void );
+
+void RE_ClearScene( void );
+void RE_AddRefEntityToScene( const refEntity_t *ent );
+void RE_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num );
+void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b );
+void RE_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b );
+void RE_RenderScene( const refdef_t *fd );
+
+#ifdef RAVENMD4
+/*
+=============================================================
+
+UNCOMPRESSING BONES
+
+=============================================================
+*/
+
+#define MC_BITS_X (16)
+#define MC_BITS_Y (16)
+#define MC_BITS_Z (16)
+#define MC_BITS_VECT (16)
+
+#define MC_SCALE_X (1.0f/64)
+#define MC_SCALE_Y (1.0f/64)
+#define MC_SCALE_Z (1.0f/64)
+
+void MC_UnCompress(float mat[3][4],const unsigned char * comp);
+#endif
+
+/*
+=============================================================
+
+ANIMATED MODELS
+
+=============================================================
+*/
+
+// void R_MakeAnimModel( model_t *model ); haven't seen this one really, so not needed I guess.
+void R_AddAnimSurfaces( trRefEntity_t *ent );
+void RB_SurfaceAnim( md4Surface_t *surfType );
+#ifdef RAVENMD4
+void R_MDRAddAnimSurfaces( trRefEntity_t *ent );
+void RB_MDRSurfaceAnim( md4Surface_t *surface );
+#endif
+
+/*
+=============================================================
+
+IMAGE LOADERS
+
+=============================================================
+*/
+
+void R_LoadBMP( const char *name, byte **pic, int *width, int *height );
+void R_LoadJPG( const char *name, byte **pic, int *width, int *height );
+void R_LoadPCX( const char *name, byte **pic, int *width, int *height );
+void R_LoadPNG( const char *name, byte **pic, int *width, int *height );
+void R_LoadTGA( const char *name, byte **pic, int *width, int *height );
+
+/*
+=============================================================
+=============================================================
+*/
+void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix,
+ vec4_t eye, vec4_t dst );
+void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window );
+
+void RB_DeformTessGeometry( void );
+
+void RB_CalcEnvironmentTexCoords( float *dstTexCoords );
+void RB_CalcFogTexCoords( float *dstTexCoords );
+void RB_CalcScrollTexCoords( const float scroll[2], float *dstTexCoords );
+void RB_CalcRotateTexCoords( float rotSpeed, float *dstTexCoords );
+void RB_CalcScaleTexCoords( const float scale[2], float *dstTexCoords );
+void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *dstTexCoords );
+void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *dstTexCoords );
+void RB_CalcModulateColorsByFog( unsigned char *dstColors );
+void RB_CalcModulateAlphasByFog( unsigned char *dstColors );
+void RB_CalcModulateRGBAsByFog( unsigned char *dstColors );
+void RB_CalcWaveAlpha( const waveForm_t *wf, unsigned char *dstColors );
+void RB_CalcWaveColor( const waveForm_t *wf, unsigned char *dstColors );
+void RB_CalcAlphaFromEntity( unsigned char *dstColors );
+void RB_CalcAlphaFromOneMinusEntity( unsigned char *dstColors );
+void RB_CalcStretchTexCoords( const waveForm_t *wf, float *texCoords );
+void RB_CalcColorFromEntity( unsigned char *dstColors );
+void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors );
+void RB_CalcSpecularAlpha( unsigned char *alphas );
+void RB_CalcDiffuseColor( unsigned char *colors );
+
+/*
+=============================================================
+
+RENDERER BACK END FUNCTIONS
+
+=============================================================
+*/
+
+void RB_RenderThread( void );
+void RB_ExecuteRenderCommands( const void *data );
+
+/*
+=============================================================
+
+RENDERER BACK END COMMAND QUEUE
+
+=============================================================
+*/
+
+#define MAX_RENDER_COMMANDS 0x40000
+
+typedef struct {
+ byte cmds[MAX_RENDER_COMMANDS];
+ int used;
+} renderCommandList_t;
+
+typedef struct {
+ int commandId;
+ float color[4];
+} setColorCommand_t;
+
+typedef struct {
+ int commandId;
+ int buffer;
+} drawBufferCommand_t;
+
+typedef struct {
+ int commandId;
+ image_t *image;
+ int width;
+ int height;
+ void *data;
+} subImageCommand_t;
+
+typedef struct {
+ int commandId;
+} swapBuffersCommand_t;
+
+typedef struct {
+ int commandId;
+ int buffer;
+} endFrameCommand_t;
+
+typedef struct {
+ int commandId;
+ shader_t *shader;
+ float x, y;
+ float w, h;
+ float s1, t1;
+ float s2, t2;
+} stretchPicCommand_t;
+
+typedef struct {
+ int commandId;
+ trRefdef_t refdef;
+ viewParms_t viewParms;
+ drawSurf_t *drawSurfs;
+ int numDrawSurfs;
+} drawSurfsCommand_t;
+
+typedef struct {
+ int commandId;
+ int x;
+ int y;
+ int width;
+ int height;
+ char *fileName;
+ qboolean jpeg;
+} screenshotCommand_t;
+
+typedef struct {
+ int commandId;
+ int width;
+ int height;
+ byte *captureBuffer;
+ byte *encodeBuffer;
+ qboolean motionJpeg;
+} videoFrameCommand_t;
+
+typedef struct
+{
+ int commandId;
+
+ GLboolean rgba[4];
+} colorMaskCommand_t;
+
+typedef struct
+{
+ int commandId;
+} clearDepthCommand_t;
+
+typedef enum {
+ RC_END_OF_LIST,
+ RC_SET_COLOR,
+ RC_STRETCH_PIC,
+ RC_DRAW_SURFS,
+ RC_DRAW_BUFFER,
+ RC_SWAP_BUFFERS,
+ RC_SCREENSHOT,
+ RC_VIDEOFRAME,
+ RC_COLORMASK,
+ RC_CLEARDEPTH
+} renderCommand_t;
+
+
+// these are sort of arbitrary limits.
+// the limits apply to the sum of all scenes in a frame --
+// the main view, all the 3D icons, etc
+#define MAX_POLYS 600
+#define MAX_POLYVERTS 3000
+
+// all of the information needed by the back end must be
+// contained in a backEndData_t. This entire structure is
+// duplicated so the front and back end can run in parallel
+// on an SMP machine
+typedef struct {
+ drawSurf_t drawSurfs[MAX_DRAWSURFS];
+ dlight_t dlights[MAX_DLIGHTS];
+ trRefEntity_t entities[MAX_ENTITIES];
+ srfPoly_t *polys;//[MAX_POLYS];
+ polyVert_t *polyVerts;//[MAX_POLYVERTS];
+ renderCommandList_t commands;
+} backEndData_t;
+
+extern int max_polys;
+extern int max_polyverts;
+
+extern backEndData_t *backEndData[SMP_FRAMES]; // the second one may not be allocated
+
+extern volatile renderCommandList_t *renderCommandList;
+
+extern volatile qboolean renderThreadActive;
+
+
+void *R_GetCommandBuffer( int bytes );
+void RB_ExecuteRenderCommands( const void *data );
+
+void R_InitCommandBuffers( void );
+void R_ShutdownCommandBuffers( void );
+
+void R_SyncRenderThread( void );
+
+void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs );
+
+void RE_SetColor( const float *rgba );
+void RE_StretchPic ( float x, float y, float w, float h,
+ float s1, float t1, float s2, float t2, qhandle_t hShader );
+void RE_BeginFrame( stereoFrame_t stereoFrame );
+void RE_EndFrame( int *frontEndMsec, int *backEndMsec );
+void SaveJPG(char * filename, int quality, int image_width, int image_height, unsigned char *image_buffer);
+int SaveJPGToBuffer( byte *buffer, int quality,
+ int image_width, int image_height,
+ byte *image_buffer );
+void RE_TakeVideoFrame( int width, int height,
+ byte *captureBuffer, byte *encodeBuffer, qboolean motionJpeg );
+
+// font stuff
+void R_InitFreeType( void );
+void R_DoneFreeType( void );
+void RE_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font);
+
+
+#endif //TR_LOCAL_H
diff --git a/code/renderer/tr_main.c b/code/renderer/tr_main.c
new file mode 100644
index 0000000..8ad4419
--- /dev/null
+++ b/code/renderer/tr_main.c
@@ -0,0 +1,1404 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// tr_main.c -- main control flow for each frame
+
+#include "tr_local.h"
+
+#include <string.h> // memcpy
+
+trGlobals_t tr;
+
+static float s_flipMatrix[16] = {
+ // convert from our coordinate system (looking down X)
+ // to OpenGL's coordinate system (looking down -Z)
+ 0, 0, -1, 0,
+ -1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 0, 1
+};
+
+
+refimport_t ri;
+
+// entities that will have procedurally generated surfaces will just
+// point at this for their sorting surface
+surfaceType_t entitySurface = SF_ENTITY;
+
+/*
+=================
+R_CullLocalBox
+
+Returns CULL_IN, CULL_CLIP, or CULL_OUT
+=================
+*/
+int R_CullLocalBox (vec3_t bounds[2]) {
+ int i, j;
+ vec3_t transformed[8];
+ float dists[8];
+ vec3_t v;
+ cplane_t *frust;
+ int anyBack;
+ int front, back;
+
+ if ( r_nocull->integer ) {
+ return CULL_CLIP;
+ }
+
+ // transform into world space
+ for (i = 0 ; i < 8 ; i++) {
+ v[0] = bounds[i&1][0];
+ v[1] = bounds[(i>>1)&1][1];
+ v[2] = bounds[(i>>2)&1][2];
+
+ VectorCopy( tr.or.origin, transformed[i] );
+ VectorMA( transformed[i], v[0], tr.or.axis[0], transformed[i] );
+ VectorMA( transformed[i], v[1], tr.or.axis[1], transformed[i] );
+ VectorMA( transformed[i], v[2], tr.or.axis[2], transformed[i] );
+ }
+
+ // check against frustum planes
+ anyBack = 0;
+ for (i = 0 ; i < 4 ; i++) {
+ frust = &tr.viewParms.frustum[i];
+
+ front = back = 0;
+ for (j = 0 ; j < 8 ; j++) {
+ dists[j] = DotProduct(transformed[j], frust->normal);
+ if ( dists[j] > frust->dist ) {
+ front = 1;
+ if ( back ) {
+ break; // a point is in front
+ }
+ } else {
+ back = 1;
+ }
+ }
+ if ( !front ) {
+ // all points were behind one of the planes
+ return CULL_OUT;
+ }
+ anyBack |= back;
+ }
+
+ if ( !anyBack ) {
+ return CULL_IN; // completely inside frustum
+ }
+
+ return CULL_CLIP; // partially clipped
+}
+
+/*
+** R_CullLocalPointAndRadius
+*/
+int R_CullLocalPointAndRadius( vec3_t pt, float radius )
+{
+ vec3_t transformed;
+
+ R_LocalPointToWorld( pt, transformed );
+
+ return R_CullPointAndRadius( transformed, radius );
+}
+
+/*
+** R_CullPointAndRadius
+*/
+int R_CullPointAndRadius( vec3_t pt, float radius )
+{
+ int i;
+ float dist;
+ cplane_t *frust;
+ qboolean mightBeClipped = qfalse;
+
+ if ( r_nocull->integer ) {
+ return CULL_CLIP;
+ }
+
+ // check against frustum planes
+ for (i = 0 ; i < 4 ; i++)
+ {
+ frust = &tr.viewParms.frustum[i];
+
+ dist = DotProduct( pt, frust->normal) - frust->dist;
+ if ( dist < -radius )
+ {
+ return CULL_OUT;
+ }
+ else if ( dist <= radius )
+ {
+ mightBeClipped = qtrue;
+ }
+ }
+
+ if ( mightBeClipped )
+ {
+ return CULL_CLIP;
+ }
+
+ return CULL_IN; // completely inside frustum
+}
+
+
+/*
+=================
+R_LocalNormalToWorld
+
+=================
+*/
+void R_LocalNormalToWorld (vec3_t local, vec3_t world) {
+ world[0] = local[0] * tr.or.axis[0][0] + local[1] * tr.or.axis[1][0] + local[2] * tr.or.axis[2][0];
+ world[1] = local[0] * tr.or.axis[0][1] + local[1] * tr.or.axis[1][1] + local[2] * tr.or.axis[2][1];
+ world[2] = local[0] * tr.or.axis[0][2] + local[1] * tr.or.axis[1][2] + local[2] * tr.or.axis[2][2];
+}
+
+/*
+=================
+R_LocalPointToWorld
+
+=================
+*/
+void R_LocalPointToWorld (vec3_t local, vec3_t world) {
+ world[0] = local[0] * tr.or.axis[0][0] + local[1] * tr.or.axis[1][0] + local[2] * tr.or.axis[2][0] + tr.or.origin[0];
+ world[1] = local[0] * tr.or.axis[0][1] + local[1] * tr.or.axis[1][1] + local[2] * tr.or.axis[2][1] + tr.or.origin[1];
+ world[2] = local[0] * tr.or.axis[0][2] + local[1] * tr.or.axis[1][2] + local[2] * tr.or.axis[2][2] + tr.or.origin[2];
+}
+
+/*
+=================
+R_WorldToLocal
+
+=================
+*/
+void R_WorldToLocal (vec3_t world, vec3_t local) {
+ local[0] = DotProduct(world, tr.or.axis[0]);
+ local[1] = DotProduct(world, tr.or.axis[1]);
+ local[2] = DotProduct(world, tr.or.axis[2]);
+}
+
+/*
+==========================
+R_TransformModelToClip
+
+==========================
+*/
+void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix,
+ vec4_t eye, vec4_t dst ) {
+ int i;
+
+ for ( i = 0 ; i < 4 ; i++ ) {
+ eye[i] =
+ src[0] * modelMatrix[ i + 0 * 4 ] +
+ src[1] * modelMatrix[ i + 1 * 4 ] +
+ src[2] * modelMatrix[ i + 2 * 4 ] +
+ 1 * modelMatrix[ i + 3 * 4 ];
+ }
+
+ for ( i = 0 ; i < 4 ; i++ ) {
+ dst[i] =
+ eye[0] * projectionMatrix[ i + 0 * 4 ] +
+ eye[1] * projectionMatrix[ i + 1 * 4 ] +
+ eye[2] * projectionMatrix[ i + 2 * 4 ] +
+ eye[3] * projectionMatrix[ i + 3 * 4 ];
+ }
+}
+
+/*
+==========================
+R_TransformClipToWindow
+
+==========================
+*/
+void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ) {
+ normalized[0] = clip[0] / clip[3];
+ normalized[1] = clip[1] / clip[3];
+ normalized[2] = ( clip[2] + clip[3] ) / ( 2 * clip[3] );
+
+ window[0] = 0.5f * ( 1.0f + normalized[0] ) * view->viewportWidth;
+ window[1] = 0.5f * ( 1.0f + normalized[1] ) * view->viewportHeight;
+ window[2] = normalized[2];
+
+ window[0] = (int) ( window[0] + 0.5 );
+ window[1] = (int) ( window[1] + 0.5 );
+}
+
+
+/*
+==========================
+myGlMultMatrix
+
+==========================
+*/
+void myGlMultMatrix( const float *a, const float *b, float *out ) {
+ int i, j;
+
+ for ( i = 0 ; i < 4 ; i++ ) {
+ for ( j = 0 ; j < 4 ; j++ ) {
+ out[ i * 4 + j ] =
+ a [ i * 4 + 0 ] * b [ 0 * 4 + j ]
+ + a [ i * 4 + 1 ] * b [ 1 * 4 + j ]
+ + a [ i * 4 + 2 ] * b [ 2 * 4 + j ]
+ + a [ i * 4 + 3 ] * b [ 3 * 4 + j ];
+ }
+ }
+}
+
+/*
+=================
+R_RotateForEntity
+
+Generates an orientation for an entity and viewParms
+Does NOT produce any GL calls
+Called by both the front end and the back end
+=================
+*/
+void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms,
+ orientationr_t *or ) {
+ float glMatrix[16];
+ vec3_t delta;
+ float axisLength;
+
+ if ( ent->e.reType != RT_MODEL ) {
+ *or = viewParms->world;
+ return;
+ }
+
+ VectorCopy( ent->e.origin, or->origin );
+
+ VectorCopy( ent->e.axis[0], or->axis[0] );
+ VectorCopy( ent->e.axis[1], or->axis[1] );
+ VectorCopy( ent->e.axis[2], or->axis[2] );
+
+ glMatrix[0] = or->axis[0][0];
+ glMatrix[4] = or->axis[1][0];
+ glMatrix[8] = or->axis[2][0];
+ glMatrix[12] = or->origin[0];
+
+ glMatrix[1] = or->axis[0][1];
+ glMatrix[5] = or->axis[1][1];
+ glMatrix[9] = or->axis[2][1];
+ glMatrix[13] = or->origin[1];
+
+ glMatrix[2] = or->axis[0][2];
+ glMatrix[6] = or->axis[1][2];
+ glMatrix[10] = or->axis[2][2];
+ glMatrix[14] = or->origin[2];
+
+ glMatrix[3] = 0;
+ glMatrix[7] = 0;
+ glMatrix[11] = 0;
+ glMatrix[15] = 1;
+
+ myGlMultMatrix( glMatrix, viewParms->world.modelMatrix, or->modelMatrix );
+
+ // calculate the viewer origin in the model's space
+ // needed for fog, specular, and environment mapping
+ VectorSubtract( viewParms->or.origin, or->origin, delta );
+
+ // compensate for scale in the axes if necessary
+ if ( ent->e.nonNormalizedAxes ) {
+ axisLength = VectorLength( ent->e.axis[0] );
+ if ( !axisLength ) {
+ axisLength = 0;
+ } else {
+ axisLength = 1.0f / axisLength;
+ }
+ } else {
+ axisLength = 1.0f;
+ }
+
+ or->viewOrigin[0] = DotProduct( delta, or->axis[0] ) * axisLength;
+ or->viewOrigin[1] = DotProduct( delta, or->axis[1] ) * axisLength;
+ or->viewOrigin[2] = DotProduct( delta, or->axis[2] ) * axisLength;
+}
+
+/*
+=================
+R_RotateForViewer
+
+Sets up the modelview matrix for a given viewParm
+=================
+*/
+void R_RotateForViewer (void)
+{
+ float viewerMatrix[16];
+ vec3_t origin;
+
+ Com_Memset (&tr.or, 0, sizeof(tr.or));
+ tr.or.axis[0][0] = 1;
+ tr.or.axis[1][1] = 1;
+ tr.or.axis[2][2] = 1;
+ VectorCopy (tr.viewParms.or.origin, tr.or.viewOrigin);
+
+ // transform by the camera placement
+ VectorCopy( tr.viewParms.or.origin, origin );
+
+ viewerMatrix[0] = tr.viewParms.or.axis[0][0];
+ viewerMatrix[4] = tr.viewParms.or.axis[0][1];
+ viewerMatrix[8] = tr.viewParms.or.axis[0][2];
+ viewerMatrix[12] = -origin[0] * viewerMatrix[0] + -origin[1] * viewerMatrix[4] + -origin[2] * viewerMatrix[8];
+
+ viewerMatrix[1] = tr.viewParms.or.axis[1][0];
+ viewerMatrix[5] = tr.viewParms.or.axis[1][1];
+ viewerMatrix[9] = tr.viewParms.or.axis[1][2];
+ viewerMatrix[13] = -origin[0] * viewerMatrix[1] + -origin[1] * viewerMatrix[5] + -origin[2] * viewerMatrix[9];
+
+ viewerMatrix[2] = tr.viewParms.or.axis[2][0];
+ viewerMatrix[6] = tr.viewParms.or.axis[2][1];
+ viewerMatrix[10] = tr.viewParms.or.axis[2][2];
+ viewerMatrix[14] = -origin[0] * viewerMatrix[2] + -origin[1] * viewerMatrix[6] + -origin[2] * viewerMatrix[10];
+
+ viewerMatrix[3] = 0;
+ viewerMatrix[7] = 0;
+ viewerMatrix[11] = 0;
+ viewerMatrix[15] = 1;
+
+ // convert from our coordinate system (looking down X)
+ // to OpenGL's coordinate system (looking down -Z)
+ myGlMultMatrix( viewerMatrix, s_flipMatrix, tr.or.modelMatrix );
+
+ tr.viewParms.world = tr.or;
+
+}
+
+/*
+** SetFarClip
+*/
+static void R_SetFarClip( void )
+{
+ float farthestCornerDistance = 0;
+ int i;
+
+ // if not rendering the world (icons, menus, etc)
+ // set a 2k far clip plane
+ if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) {
+ tr.viewParms.zFar = 2048;
+ return;
+ }
+
+ //
+ // set far clipping planes dynamically
+ //
+ farthestCornerDistance = 0;
+ for ( i = 0; i < 8; i++ )
+ {
+ vec3_t v;
+ vec3_t vecTo;
+ float distance;
+
+ if ( i & 1 )
+ {
+ v[0] = tr.viewParms.visBounds[0][0];
+ }
+ else
+ {
+ v[0] = tr.viewParms.visBounds[1][0];
+ }
+
+ if ( i & 2 )
+ {
+ v[1] = tr.viewParms.visBounds[0][1];
+ }
+ else
+ {
+ v[1] = tr.viewParms.visBounds[1][1];
+ }
+
+ if ( i & 4 )
+ {
+ v[2] = tr.viewParms.visBounds[0][2];
+ }
+ else
+ {
+ v[2] = tr.viewParms.visBounds[1][2];
+ }
+
+ VectorSubtract( v, tr.viewParms.or.origin, vecTo );
+
+ distance = vecTo[0] * vecTo[0] + vecTo[1] * vecTo[1] + vecTo[2] * vecTo[2];
+
+ if ( distance > farthestCornerDistance )
+ {
+ farthestCornerDistance = distance;
+ }
+ }
+ tr.viewParms.zFar = sqrt( farthestCornerDistance );
+}
+
+/*
+=================
+R_SetupFrustum
+
+Set up the culling frustum planes for the current view using the results we got from computing the first two rows of
+the projection matrix.
+=================
+*/
+void R_SetupFrustum (viewParms_t *dest, float xmin, float xmax, float ymax, float zProj, float stereoSep)
+{
+ vec3_t ofsorigin;
+ float oppleg, adjleg, length;
+ int i;
+
+ if(stereoSep == 0 && xmin == -xmax)
+ {
+ // symmetric case can be simplified
+ VectorCopy(dest->or.origin, ofsorigin);
+
+ length = sqrt(xmax * xmax + zProj * zProj);
+ oppleg = xmax / length;
+ adjleg = zProj / length;
+
+ VectorScale(dest->or.axis[0], oppleg, dest->frustum[0].normal);
+ VectorMA(dest->frustum[0].normal, adjleg, dest->or.axis[1], dest->frustum[0].normal);
+
+ VectorScale(dest->or.axis[0], oppleg, dest->frustum[1].normal);
+ VectorMA(dest->frustum[1].normal, -adjleg, dest->or.axis[1], dest->frustum[1].normal);
+ }
+ else
+ {
+ // In stereo rendering, due to the modification of the projection matrix, dest->or.origin is not the
+ // actual origin that we're rendering so offset the tip of the view pyramid.
+ VectorMA(dest->or.origin, stereoSep, dest->or.axis[1], ofsorigin);
+
+ oppleg = xmax + stereoSep;
+ length = sqrt(oppleg * oppleg + zProj * zProj);
+ VectorScale(dest->or.axis[0], oppleg / length, dest->frustum[0].normal);
+ VectorMA(dest->frustum[0].normal, zProj / length, dest->or.axis[1], dest->frustum[0].normal);
+
+ oppleg = xmin + stereoSep;
+ length = sqrt(oppleg * oppleg + zProj * zProj);
+ VectorScale(dest->or.axis[0], -oppleg / length, dest->frustum[1].normal);
+ VectorMA(dest->frustum[1].normal, -zProj / length, dest->or.axis[1], dest->frustum[1].normal);
+ }
+
+ length = sqrt(ymax * ymax + zProj * zProj);
+ oppleg = ymax / length;
+ adjleg = zProj / length;
+
+ VectorScale(dest->or.axis[0], oppleg, dest->frustum[2].normal);
+ VectorMA(dest->frustum[2].normal, adjleg, dest->or.axis[2], dest->frustum[2].normal);
+
+ VectorScale(dest->or.axis[0], oppleg, dest->frustum[3].normal);
+ VectorMA(dest->frustum[3].normal, -adjleg, dest->or.axis[2], dest->frustum[3].normal);
+
+ for (i=0 ; i<4 ; i++) {
+ dest->frustum[i].type = PLANE_NON_AXIAL;
+ dest->frustum[i].dist = DotProduct (ofsorigin, dest->frustum[i].normal);
+ SetPlaneSignbits( &dest->frustum[i] );
+ }
+}
+
+/*
+===============
+R_SetupProjection
+===============
+*/
+void R_SetupProjection(viewParms_t *dest, float zProj, qboolean computeFrustum)
+{
+ float xmin, xmax, ymin, ymax;
+ float width, height, stereoSep = r_stereoSeparation->value;
+
+ /*
+ * offset the view origin of the viewer for stereo rendering
+ * by setting the projection matrix appropriately.
+ */
+
+ if(stereoSep != 0)
+ {
+ if(dest->stereoFrame == STEREO_LEFT)
+ stereoSep = zProj / stereoSep;
+ else if(dest->stereoFrame == STEREO_RIGHT)
+ stereoSep = zProj / -stereoSep;
+ else
+ stereoSep = 0;
+ }
+
+ ymax = zProj * tan(dest->fovY * M_PI / 360.0f);
+ ymin = -ymax;
+
+ xmax = zProj * tan(dest->fovX * M_PI / 360.0f);
+ xmin = -xmax;
+
+ width = xmax - xmin;
+ height = ymax - ymin;
+
+ dest->projectionMatrix[0] = 2 * zProj / width;
+ dest->projectionMatrix[4] = 0;
+ dest->projectionMatrix[8] = (xmax + xmin + 2 * stereoSep) / width;
+ dest->projectionMatrix[12] = 2 * zProj * stereoSep / width;
+
+ dest->projectionMatrix[1] = 0;
+ dest->projectionMatrix[5] = 2 * zProj / height;
+ dest->projectionMatrix[9] = ( ymax + ymin ) / height; // normally 0
+ dest->projectionMatrix[13] = 0;
+
+ dest->projectionMatrix[3] = 0;
+ dest->projectionMatrix[7] = 0;
+ dest->projectionMatrix[11] = -1;
+ dest->projectionMatrix[15] = 0;
+
+ // Now that we have all the data for the projection matrix we can also setup the view frustum.
+ if(computeFrustum)
+ R_SetupFrustum(dest, xmin, xmax, ymax, zProj, stereoSep);
+}
+
+/*
+===============
+R_SetupProjectionZ
+
+Sets the z-component transformation part in the projection matrix
+===============
+*/
+void R_SetupProjectionZ(viewParms_t *dest)
+{
+ float zNear, zFar, depth;
+
+ zNear = r_znear->value;
+ zFar = dest->zFar;
+ depth = zFar - zNear;
+
+ dest->projectionMatrix[2] = 0;
+ dest->projectionMatrix[6] = 0;
+ dest->projectionMatrix[10] = -( zFar + zNear ) / depth;
+ dest->projectionMatrix[14] = -2 * zFar * zNear / depth;
+}
+
+/*
+=================
+R_MirrorPoint
+=================
+*/
+void R_MirrorPoint (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) {
+ int i;
+ vec3_t local;
+ vec3_t transformed;
+ float d;
+
+ VectorSubtract( in, surface->origin, local );
+
+ VectorClear( transformed );
+ for ( i = 0 ; i < 3 ; i++ ) {
+ d = DotProduct(local, surface->axis[i]);
+ VectorMA( transformed, d, camera->axis[i], transformed );
+ }
+
+ VectorAdd( transformed, camera->origin, out );
+}
+
+void R_MirrorVector (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) {
+ int i;
+ float d;
+
+ VectorClear( out );
+ for ( i = 0 ; i < 3 ; i++ ) {
+ d = DotProduct(in, surface->axis[i]);
+ VectorMA( out, d, camera->axis[i], out );
+ }
+}
+
+
+/*
+=============
+R_PlaneForSurface
+=============
+*/
+void R_PlaneForSurface (surfaceType_t *surfType, cplane_t *plane) {
+ srfTriangles_t *tri;
+ srfPoly_t *poly;
+ drawVert_t *v1, *v2, *v3;
+ vec4_t plane4;
+
+ if (!surfType) {
+ Com_Memset (plane, 0, sizeof(*plane));
+ plane->normal[0] = 1;
+ return;
+ }
+ switch (*surfType) {
+ case SF_FACE:
+ *plane = ((srfSurfaceFace_t *)surfType)->plane;
+ return;
+ case SF_TRIANGLES:
+ tri = (srfTriangles_t *)surfType;
+ v1 = tri->verts + tri->indexes[0];
+ v2 = tri->verts + tri->indexes[1];
+ v3 = tri->verts + tri->indexes[2];
+ PlaneFromPoints( plane4, v1->xyz, v2->xyz, v3->xyz );
+ VectorCopy( plane4, plane->normal );
+ plane->dist = plane4[3];
+ return;
+ case SF_POLY:
+ poly = (srfPoly_t *)surfType;
+ PlaneFromPoints( plane4, poly->verts[0].xyz, poly->verts[1].xyz, poly->verts[2].xyz );
+ VectorCopy( plane4, plane->normal );
+ plane->dist = plane4[3];
+ return;
+ default:
+ Com_Memset (plane, 0, sizeof(*plane));
+ plane->normal[0] = 1;
+ return;
+ }
+}
+
+/*
+=================
+R_GetPortalOrientation
+
+entityNum is the entity that the portal surface is a part of, which may
+be moving and rotating.
+
+Returns qtrue if it should be mirrored
+=================
+*/
+qboolean R_GetPortalOrientations( drawSurf_t *drawSurf, int entityNum,
+ orientation_t *surface, orientation_t *camera,
+ vec3_t pvsOrigin, qboolean *mirror ) {
+ int i;
+ cplane_t originalPlane, plane;
+ trRefEntity_t *e;
+ float d;
+ vec3_t transformed;
+
+ // create plane axis for the portal we are seeing
+ R_PlaneForSurface( drawSurf->surface, &originalPlane );
+
+ // rotate the plane if necessary
+ if ( entityNum != ENTITYNUM_WORLD ) {
+ tr.currentEntityNum = entityNum;
+ tr.currentEntity = &tr.refdef.entities[entityNum];
+
+ // get the orientation of the entity
+ R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.or );
+
+ // rotate the plane, but keep the non-rotated version for matching
+ // against the portalSurface entities
+ R_LocalNormalToWorld( originalPlane.normal, plane.normal );
+ plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.or.origin );
+
+ // translate the original plane
+ originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.or.origin );
+ } else {
+ plane = originalPlane;
+ }
+
+ VectorCopy( plane.normal, surface->axis[0] );
+ PerpendicularVector( surface->axis[1], surface->axis[0] );
+ CrossProduct( surface->axis[0], surface->axis[1], surface->axis[2] );
+
+ // locate the portal entity closest to this plane.
+ // origin will be the origin of the portal, origin2 will be
+ // the origin of the camera
+ for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) {
+ e = &tr.refdef.entities[i];
+ if ( e->e.reType != RT_PORTALSURFACE ) {
+ continue;
+ }
+
+ d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist;
+ if ( d > 64 || d < -64) {
+ continue;
+ }
+
+ // get the pvsOrigin from the entity
+ VectorCopy( e->e.oldorigin, pvsOrigin );
+
+ // if the entity is just a mirror, don't use as a camera point
+ if ( e->e.oldorigin[0] == e->e.origin[0] &&
+ e->e.oldorigin[1] == e->e.origin[1] &&
+ e->e.oldorigin[2] == e->e.origin[2] ) {
+ VectorScale( plane.normal, plane.dist, surface->origin );
+ VectorCopy( surface->origin, camera->origin );
+ VectorSubtract( vec3_origin, surface->axis[0], camera->axis[0] );
+ VectorCopy( surface->axis[1], camera->axis[1] );
+ VectorCopy( surface->axis[2], camera->axis[2] );
+
+ *mirror = qtrue;
+ return qtrue;
+ }
+
+ // project the origin onto the surface plane to get
+ // an origin point we can rotate around
+ d = DotProduct( e->e.origin, plane.normal ) - plane.dist;
+ VectorMA( e->e.origin, -d, surface->axis[0], surface->origin );
+
+ // now get the camera origin and orientation
+ VectorCopy( e->e.oldorigin, camera->origin );
+ AxisCopy( e->e.axis, camera->axis );
+ VectorSubtract( vec3_origin, camera->axis[0], camera->axis[0] );
+ VectorSubtract( vec3_origin, camera->axis[1], camera->axis[1] );
+
+ // optionally rotate
+ if ( e->e.oldframe ) {
+ // if a speed is specified
+ if ( e->e.frame ) {
+ // continuous rotate
+ d = (tr.refdef.time/1000.0f) * e->e.frame;
+ VectorCopy( camera->axis[1], transformed );
+ RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d );
+ CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] );
+ } else {
+ // bobbing rotate, with skinNum being the rotation offset
+ d = sin( tr.refdef.time * 0.003f );
+ d = e->e.skinNum + d * 4;
+ VectorCopy( camera->axis[1], transformed );
+ RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d );
+ CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] );
+ }
+ }
+ else if ( e->e.skinNum ) {
+ d = e->e.skinNum;
+ VectorCopy( camera->axis[1], transformed );
+ RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d );
+ CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] );
+ }
+ *mirror = qfalse;
+ return qtrue;
+ }
+
+ // if we didn't locate a portal entity, don't render anything.
+ // We don't want to just treat it as a mirror, because without a
+ // portal entity the server won't have communicated a proper entity set
+ // in the snapshot
+
+ // unfortunately, with local movement prediction it is easily possible
+ // to see a surface before the server has communicated the matching
+ // portal surface entity, so we don't want to print anything here...
+
+ //ri.Printf( PRINT_ALL, "Portal surface without a portal entity\n" );
+
+ return qfalse;
+}
+
+static qboolean IsMirror( const drawSurf_t *drawSurf, int entityNum )
+{
+ int i;
+ cplane_t originalPlane, plane;
+ trRefEntity_t *e;
+ float d;
+
+ // create plane axis for the portal we are seeing
+ R_PlaneForSurface( drawSurf->surface, &originalPlane );
+
+ // rotate the plane if necessary
+ if ( entityNum != ENTITYNUM_WORLD )
+ {
+ tr.currentEntityNum = entityNum;
+ tr.currentEntity = &tr.refdef.entities[entityNum];
+
+ // get the orientation of the entity
+ R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.or );
+
+ // rotate the plane, but keep the non-rotated version for matching
+ // against the portalSurface entities
+ R_LocalNormalToWorld( originalPlane.normal, plane.normal );
+ plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.or.origin );
+
+ // translate the original plane
+ originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.or.origin );
+ }
+ else
+ {
+ plane = originalPlane;
+ }
+
+ // locate the portal entity closest to this plane.
+ // origin will be the origin of the portal, origin2 will be
+ // the origin of the camera
+ for ( i = 0 ; i < tr.refdef.num_entities ; i++ )
+ {
+ e = &tr.refdef.entities[i];
+ if ( e->e.reType != RT_PORTALSURFACE ) {
+ continue;
+ }
+
+ d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist;
+ if ( d > 64 || d < -64) {
+ continue;
+ }
+
+ // if the entity is just a mirror, don't use as a camera point
+ if ( e->e.oldorigin[0] == e->e.origin[0] &&
+ e->e.oldorigin[1] == e->e.origin[1] &&
+ e->e.oldorigin[2] == e->e.origin[2] )
+ {
+ return qtrue;
+ }
+
+ return qfalse;
+ }
+ return qfalse;
+}
+
+/*
+** SurfIsOffscreen
+**
+** Determines if a surface is completely offscreen.
+*/
+static qboolean SurfIsOffscreen( const drawSurf_t *drawSurf, vec4_t clipDest[128] ) {
+ float shortest = 100000000;
+ int entityNum;
+ int numTriangles;
+ shader_t *shader;
+ int fogNum;
+ int dlighted;
+ vec4_t clip, eye;
+ int i;
+ unsigned int pointOr = 0;
+ unsigned int pointAnd = (unsigned int)~0;
+
+ if ( glConfig.smpActive ) { // FIXME! we can't do RB_BeginSurface/RB_EndSurface stuff with smp!
+ return qfalse;
+ }
+
+ R_RotateForViewer();
+
+ R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted );
+ RB_BeginSurface( shader, fogNum );
+ rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface );
+
+ assert( tess.numVertexes < 128 );
+
+ for ( i = 0; i < tess.numVertexes; i++ )
+ {
+ int j;
+ unsigned int pointFlags = 0;
+
+ R_TransformModelToClip( tess.xyz[i], tr.or.modelMatrix, tr.viewParms.projectionMatrix, eye, clip );
+
+ for ( j = 0; j < 3; j++ )
+ {
+ if ( clip[j] >= clip[3] )
+ {
+ pointFlags |= (1 << (j*2));
+ }
+ else if ( clip[j] <= -clip[3] )
+ {
+ pointFlags |= ( 1 << (j*2+1));
+ }
+ }
+ pointAnd &= pointFlags;
+ pointOr |= pointFlags;
+ }
+
+ // trivially reject
+ if ( pointAnd )
+ {
+ return qtrue;
+ }
+
+ // determine if this surface is backfaced and also determine the distance
+ // to the nearest vertex so we can cull based on portal range. Culling
+ // based on vertex distance isn't 100% correct (we should be checking for
+ // range to the surface), but it's good enough for the types of portals
+ // we have in the game right now.
+ numTriangles = tess.numIndexes / 3;
+
+ for ( i = 0; i < tess.numIndexes; i += 3 )
+ {
+ vec3_t normal;
+ float dot;
+ float len;
+
+ VectorSubtract( tess.xyz[tess.indexes[i]], tr.viewParms.or.origin, normal );
+
+ len = VectorLengthSquared( normal ); // lose the sqrt
+ if ( len < shortest )
+ {
+ shortest = len;
+ }
+
+ if ( ( dot = DotProduct( normal, tess.normal[tess.indexes[i]] ) ) >= 0 )
+ {
+ numTriangles--;
+ }
+ }
+ if ( !numTriangles )
+ {
+ return qtrue;
+ }
+
+ // mirrors can early out at this point, since we don't do a fade over distance
+ // with them (although we could)
+ if ( IsMirror( drawSurf, entityNum ) )
+ {
+ return qfalse;
+ }
+
+ if ( shortest > (tess.shader->portalRange*tess.shader->portalRange) )
+ {
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+/*
+========================
+R_MirrorViewBySurface
+
+Returns qtrue if another view has been rendered
+========================
+*/
+qboolean R_MirrorViewBySurface (drawSurf_t *drawSurf, int entityNum) {
+ vec4_t clipDest[128];
+ viewParms_t newParms;
+ viewParms_t oldParms;
+ orientation_t surface, camera;
+
+ // don't recursively mirror
+ if (tr.viewParms.isPortal) {
+ ri.Printf( PRINT_DEVELOPER, "WARNING: recursive mirror/portal found\n" );
+ return qfalse;
+ }
+
+ if ( r_noportals->integer || (r_fastsky->integer == 1) ) {
+ return qfalse;
+ }
+
+ // trivially reject portal/mirror
+ if ( SurfIsOffscreen( drawSurf, clipDest ) ) {
+ return qfalse;
+ }
+
+ // save old viewParms so we can return to it after the mirror view
+ oldParms = tr.viewParms;
+
+ newParms = tr.viewParms;
+ newParms.isPortal = qtrue;
+ if ( !R_GetPortalOrientations( drawSurf, entityNum, &surface, &camera,
+ newParms.pvsOrigin, &newParms.isMirror ) ) {
+ return qfalse; // bad portal, no portalentity
+ }
+
+ R_MirrorPoint (oldParms.or.origin, &surface, &camera, newParms.or.origin );
+
+ VectorSubtract( vec3_origin, camera.axis[0], newParms.portalPlane.normal );
+ newParms.portalPlane.dist = DotProduct( camera.origin, newParms.portalPlane.normal );
+
+ R_MirrorVector (oldParms.or.axis[0], &surface, &camera, newParms.or.axis[0]);
+ R_MirrorVector (oldParms.or.axis[1], &surface, &camera, newParms.or.axis[1]);
+ R_MirrorVector (oldParms.or.axis[2], &surface, &camera, newParms.or.axis[2]);
+
+ // OPTIMIZE: restrict the viewport on the mirrored view
+
+ // render the mirror view
+ R_RenderView (&newParms);
+
+ tr.viewParms = oldParms;
+
+ return qtrue;
+}
+
+/*
+=================
+R_SpriteFogNum
+
+See if a sprite is inside a fog volume
+=================
+*/
+int R_SpriteFogNum( trRefEntity_t *ent ) {
+ int i, j;
+ fog_t *fog;
+
+ if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) {
+ return 0;
+ }
+
+ for ( i = 1 ; i < tr.world->numfogs ; i++ ) {
+ fog = &tr.world->fogs[i];
+ for ( j = 0 ; j < 3 ; j++ ) {
+ if ( ent->e.origin[j] - ent->e.radius >= fog->bounds[1][j] ) {
+ break;
+ }
+ if ( ent->e.origin[j] + ent->e.radius <= fog->bounds[0][j] ) {
+ break;
+ }
+ }
+ if ( j == 3 ) {
+ return i;
+ }
+ }
+
+ return 0;
+}
+
+/*
+==========================================================================================
+
+DRAWSURF SORTING
+
+==========================================================================================
+*/
+
+/*
+===============
+R_Radix
+===============
+*/
+static ID_INLINE void R_Radix( int byte, int size, drawSurf_t *source, drawSurf_t *dest )
+{
+ int count[ 256 ] = { 0 };
+ int index[ 256 ];
+ int i;
+ unsigned char *sortKey = NULL;
+ unsigned char *end = NULL;
+
+ sortKey = ( (unsigned char *)&source[ 0 ].sort ) + byte;
+ end = sortKey + ( size * sizeof( drawSurf_t ) );
+ for( ; sortKey < end; sortKey += sizeof( drawSurf_t ) )
+ ++count[ *sortKey ];
+
+ index[ 0 ] = 0;
+
+ for( i = 1; i < 256; ++i )
+ index[ i ] = index[ i - 1 ] + count[ i - 1 ];
+
+ sortKey = ( (unsigned char *)&source[ 0 ].sort ) + byte;
+ for( i = 0; i < size; ++i, sortKey += sizeof( drawSurf_t ) )
+ dest[ index[ *sortKey ]++ ] = source[ i ];
+}
+
+/*
+===============
+R_RadixSort
+
+Radix sort with 4 byte size buckets
+===============
+*/
+static void R_RadixSort( drawSurf_t *source, int size )
+{
+ static drawSurf_t scratch[ MAX_DRAWSURFS ];
+#ifdef Q3_LITTLE_ENDIAN
+ R_Radix( 0, size, source, scratch );
+ R_Radix( 1, size, scratch, source );
+ R_Radix( 2, size, source, scratch );
+ R_Radix( 3, size, scratch, source );
+#else
+ R_Radix( 3, size, source, scratch );
+ R_Radix( 2, size, scratch, source );
+ R_Radix( 1, size, source, scratch );
+ R_Radix( 0, size, scratch, source );
+#endif //Q3_LITTLE_ENDIAN
+}
+
+//==========================================================================================
+
+/*
+=================
+R_AddDrawSurf
+=================
+*/
+void R_AddDrawSurf( surfaceType_t *surface, shader_t *shader,
+ int fogIndex, int dlightMap ) {
+ int index;
+
+ // instead of checking for overflow, we just mask the index
+ // so it wraps around
+ index = tr.refdef.numDrawSurfs & DRAWSURF_MASK;
+ // the sort data is packed into a single 32 bit value so it can be
+ // compared quickly during the qsorting process
+ tr.refdef.drawSurfs[index].sort = (shader->sortedIndex << QSORT_SHADERNUM_SHIFT)
+ | tr.shiftedEntityNum | ( fogIndex << QSORT_FOGNUM_SHIFT ) | (int)dlightMap;
+ tr.refdef.drawSurfs[index].surface = surface;
+ tr.refdef.numDrawSurfs++;
+}
+
+/*
+=================
+R_DecomposeSort
+=================
+*/
+void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader,
+ int *fogNum, int *dlightMap ) {
+ *fogNum = ( sort >> QSORT_FOGNUM_SHIFT ) & 31;
+ *shader = tr.sortedShaders[ ( sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1) ];
+ *entityNum = ( sort >> QSORT_ENTITYNUM_SHIFT ) & 1023;
+ *dlightMap = sort & 3;
+}
+
+/*
+=================
+R_SortDrawSurfs
+=================
+*/
+void R_SortDrawSurfs( drawSurf_t *drawSurfs, int numDrawSurfs ) {
+ shader_t *shader;
+ int fogNum;
+ int entityNum;
+ int dlighted;
+ int i;
+
+ // it is possible for some views to not have any surfaces
+ if ( numDrawSurfs < 1 ) {
+ // we still need to add it for hyperspace cases
+ R_AddDrawSurfCmd( drawSurfs, numDrawSurfs );
+ return;
+ }
+
+ // if we overflowed MAX_DRAWSURFS, the drawsurfs
+ // wrapped around in the buffer and we will be missing
+ // the first surfaces, not the last ones
+ if ( numDrawSurfs > MAX_DRAWSURFS ) {
+ numDrawSurfs = MAX_DRAWSURFS;
+ }
+
+ // sort the drawsurfs by sort type, then orientation, then shader
+ R_RadixSort( drawSurfs, numDrawSurfs );
+
+ // check for any pass through drawing, which
+ // may cause another view to be rendered first
+ for ( i = 0 ; i < numDrawSurfs ; i++ ) {
+ R_DecomposeSort( (drawSurfs+i)->sort, &entityNum, &shader, &fogNum, &dlighted );
+
+ if ( shader->sort > SS_PORTAL ) {
+ break;
+ }
+
+ // no shader should ever have this sort type
+ if ( shader->sort == SS_BAD ) {
+ ri.Error (ERR_DROP, "Shader '%s'with sort == SS_BAD", shader->name );
+ }
+
+ // if the mirror was completely clipped away, we may need to check another surface
+ if ( R_MirrorViewBySurface( (drawSurfs+i), entityNum) ) {
+ // this is a debug option to see exactly what is being mirrored
+ if ( r_portalOnly->integer ) {
+ return;
+ }
+ break; // only one mirror view at a time
+ }
+ }
+
+ R_AddDrawSurfCmd( drawSurfs, numDrawSurfs );
+}
+
+/*
+=============
+R_AddEntitySurfaces
+=============
+*/
+void R_AddEntitySurfaces (void) {
+ trRefEntity_t *ent;
+ shader_t *shader;
+
+ if ( !r_drawentities->integer ) {
+ return;
+ }
+
+ for ( tr.currentEntityNum = 0;
+ tr.currentEntityNum < tr.refdef.num_entities;
+ tr.currentEntityNum++ ) {
+ ent = tr.currentEntity = &tr.refdef.entities[tr.currentEntityNum];
+
+ ent->needDlights = qfalse;
+
+ // preshift the value we are going to OR into the drawsurf sort
+ tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT;
+
+ //
+ // the weapon model must be handled special --
+ // we don't want the hacked weapon position showing in
+ // mirrors, because the true body position will already be drawn
+ //
+ if ( (ent->e.renderfx & RF_FIRST_PERSON) && tr.viewParms.isPortal) {
+ continue;
+ }
+
+ // simple generated models, like sprites and beams, are not culled
+ switch ( ent->e.reType ) {
+ case RT_PORTALSURFACE:
+ break; // don't draw anything
+ case RT_SPRITE:
+ case RT_BEAM:
+ case RT_LIGHTNING:
+ case RT_RAIL_CORE:
+ case RT_RAIL_RINGS:
+ // self blood sprites, talk balloons, etc should not be drawn in the primary
+ // view. We can't just do this check for all entities, because md3
+ // entities may still want to cast shadows from them
+ if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) {
+ continue;
+ }
+ shader = R_GetShaderByHandle( ent->e.customShader );
+ R_AddDrawSurf( &entitySurface, shader, R_SpriteFogNum( ent ), 0 );
+ break;
+
+ case RT_MODEL:
+ // we must set up parts of tr.or for model culling
+ R_RotateForEntity( ent, &tr.viewParms, &tr.or );
+
+ tr.currentModel = R_GetModelByHandle( ent->e.hModel );
+ if (!tr.currentModel) {
+ R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0 );
+ } else {
+ switch ( tr.currentModel->type ) {
+ case MOD_MESH:
+ R_AddMD3Surfaces( ent );
+ break;
+ case MOD_MD4:
+ R_AddAnimSurfaces( ent );
+ break;
+#ifdef RAVENMD4
+ case MOD_MDR:
+ R_MDRAddAnimSurfaces( ent );
+ break;
+#endif
+ case MOD_BRUSH:
+ R_AddBrushModelSurfaces( ent );
+ break;
+ case MOD_BAD: // null model axis
+ if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) {
+ break;
+ }
+ shader = R_GetShaderByHandle( ent->e.customShader );
+ R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0 );
+ break;
+ default:
+ ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad modeltype" );
+ break;
+ }
+ }
+ break;
+ default:
+ ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad reType" );
+ }
+ }
+
+}
+
+
+/*
+====================
+R_GenerateDrawSurfs
+====================
+*/
+void R_GenerateDrawSurfs( void ) {
+ R_AddWorldSurfaces ();
+
+ R_AddPolygonSurfaces();
+
+ // set the projection matrix with the minimum zfar
+ // now that we have the world bounded
+ // this needs to be done before entities are
+ // added, because they use the projection
+ // matrix for lod calculation
+
+ // dynamically compute far clip plane distance
+ R_SetFarClip();
+
+ // we know the size of the clipping volume. Now set the rest of the projection matrix.
+ R_SetupProjectionZ (&tr.viewParms);
+
+ R_AddEntitySurfaces ();
+}
+
+/*
+================
+R_DebugPolygon
+================
+*/
+void R_DebugPolygon( int color, int numPoints, float *points ) {
+#ifndef PANDORA
+ int i;
+
+ GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE );
+
+ // draw solid shade
+
+ qglColor3f( color&1, (color>>1)&1, (color>>2)&1 );
+ qglBegin( GL_POLYGON );
+ for ( i = 0 ; i < numPoints ; i++ ) {
+ qglVertex3fv( points + i * 3 );
+ }
+ qglEnd();
+
+ // draw wireframe outline
+ GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE );
+ qglDepthRange( 0, 0 );
+ qglColor3f( 1, 1, 1 );
+ qglBegin( GL_POLYGON );
+ for ( i = 0 ; i < numPoints ; i++ ) {
+ qglVertex3fv( points + i * 3 );
+ }
+ qglEnd();
+ qglDepthRange( 0, 1 );
+#endif
+}
+
+/*
+====================
+R_DebugGraphics
+
+Visualization aid for movement clipping debugging
+====================
+*/
+void R_DebugGraphics( void ) {
+ if ( !r_debugSurface->integer ) {
+ return;
+ }
+
+ // the render thread can't make callbacks to the main thread
+ R_SyncRenderThread();
+
+ GL_Bind( tr.whiteImage);
+ GL_Cull( CT_FRONT_SIDED );
+ ri.CM_DrawDebugSurface( R_DebugPolygon );
+}
+
+
+/*
+================
+R_RenderView
+
+A view may be either the actual camera view,
+or a mirror / remote location
+================
+*/
+void R_RenderView (viewParms_t *parms) {
+ int firstDrawSurf;
+
+ if ( parms->viewportWidth <= 0 || parms->viewportHeight <= 0 ) {
+ return;
+ }
+
+ tr.viewCount++;
+
+ tr.viewParms = *parms;
+ tr.viewParms.frameSceneNum = tr.frameSceneNum;
+ tr.viewParms.frameCount = tr.frameCount;
+
+ firstDrawSurf = tr.refdef.numDrawSurfs;
+
+ tr.viewCount++;
+
+ // set viewParms.world
+ R_RotateForViewer ();
+
+ R_SetupProjection(&tr.viewParms, r_zproj->value, qtrue);
+
+ R_GenerateDrawSurfs();
+
+ R_SortDrawSurfs( tr.refdef.drawSurfs + firstDrawSurf, tr.refdef.numDrawSurfs - firstDrawSurf );
+
+ // draw main system development information (surface outlines, etc)
+ R_DebugGraphics();
+}
+
+
+
diff --git a/code/renderer/tr_marks.c b/code/renderer/tr_marks.c
new file mode 100644
index 0000000..b83c77b
--- /dev/null
+++ b/code/renderer/tr_marks.c
@@ -0,0 +1,454 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// tr_marks.c -- polygon projection on the world polygons
+
+#include "tr_local.h"
+//#include "assert.h"
+
+#define MAX_VERTS_ON_POLY 64
+
+#define MARKER_OFFSET 0 // 1
+
+/*
+=============
+R_ChopPolyBehindPlane
+
+Out must have space for two more vertexes than in
+=============
+*/
+#define SIDE_FRONT 0
+#define SIDE_BACK 1
+#define SIDE_ON 2
+static void R_ChopPolyBehindPlane( int numInPoints, vec3_t inPoints[MAX_VERTS_ON_POLY],
+ int *numOutPoints, vec3_t outPoints[MAX_VERTS_ON_POLY],
+ vec3_t normal, vec_t dist, vec_t epsilon) {
+ float dists[MAX_VERTS_ON_POLY+4];
+ int sides[MAX_VERTS_ON_POLY+4];
+ int counts[3];
+ float dot;
+ int i, j;
+ float *p1, *p2, *clip;
+ float d;
+
+ // don't clip if it might overflow
+ if ( numInPoints >= MAX_VERTS_ON_POLY - 2 ) {
+ *numOutPoints = 0;
+ return;
+ }
+
+ counts[0] = counts[1] = counts[2] = 0;
+
+ // determine sides for each point
+ for ( i = 0 ; i < numInPoints ; i++ ) {
+ dot = DotProduct( inPoints[i], normal );
+ dot -= dist;
+ dists[i] = dot;
+ if ( dot > epsilon ) {
+ sides[i] = SIDE_FRONT;
+ } else if ( dot < -epsilon ) {
+ sides[i] = SIDE_BACK;
+ } else {
+ sides[i] = SIDE_ON;
+ }
+ counts[sides[i]]++;
+ }
+ sides[i] = sides[0];
+ dists[i] = dists[0];
+
+ *numOutPoints = 0;
+
+ if ( !counts[0] ) {
+ return;
+ }
+ if ( !counts[1] ) {
+ *numOutPoints = numInPoints;
+ Com_Memcpy( outPoints, inPoints, numInPoints * sizeof(vec3_t) );
+ return;
+ }
+
+ for ( i = 0 ; i < numInPoints ; i++ ) {
+ p1 = inPoints[i];
+ clip = outPoints[ *numOutPoints ];
+
+ if ( sides[i] == SIDE_ON ) {
+ VectorCopy( p1, clip );
+ (*numOutPoints)++;
+ continue;
+ }
+
+ if ( sides[i] == SIDE_FRONT ) {
+ VectorCopy( p1, clip );
+ (*numOutPoints)++;
+ clip = outPoints[ *numOutPoints ];
+ }
+
+ if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) {
+ continue;
+ }
+
+ // generate a split point
+ p2 = inPoints[ (i+1) % numInPoints ];
+
+ d = dists[i] - dists[i+1];
+ if ( d == 0 ) {
+ dot = 0;
+ } else {
+ dot = dists[i] / d;
+ }
+
+ // clip xyz
+
+ for (j=0 ; j<3 ; j++) {
+ clip[j] = p1[j] + dot * ( p2[j] - p1[j] );
+ }
+
+ (*numOutPoints)++;
+ }
+}
+
+/*
+=================
+R_BoxSurfaces_r
+
+=================
+*/
+void R_BoxSurfaces_r(mnode_t *node, vec3_t mins, vec3_t maxs, surfaceType_t **list, int listsize, int *listlength, vec3_t dir) {
+
+ int s, c;
+ msurface_t *surf, **mark;
+
+ // do the tail recursion in a loop
+ while ( node->contents == -1 ) {
+ s = BoxOnPlaneSide( mins, maxs, node->plane );
+ if (s == 1) {
+ node = node->children[0];
+ } else if (s == 2) {
+ node = node->children[1];
+ } else {
+ R_BoxSurfaces_r(node->children[0], mins, maxs, list, listsize, listlength, dir);
+ node = node->children[1];
+ }
+ }
+
+ // add the individual surfaces
+ mark = node->firstmarksurface;
+ c = node->nummarksurfaces;
+ while (c--) {
+ //
+ if (*listlength >= listsize) break;
+ //
+ surf = *mark;
+ // check if the surface has NOIMPACT or NOMARKS set
+ if ( ( surf->shader->surfaceFlags & ( SURF_NOIMPACT | SURF_NOMARKS ) )
+ || ( surf->shader->contentFlags & CONTENTS_FOG ) ) {
+ surf->viewCount = tr.viewCount;
+ }
+ // extra check for surfaces to avoid list overflows
+ else if (*(surf->data) == SF_FACE) {
+ // the face plane should go through the box
+ s = BoxOnPlaneSide( mins, maxs, &(( srfSurfaceFace_t * ) surf->data)->plane );
+ if (s == 1 || s == 2) {
+ surf->viewCount = tr.viewCount;
+ } else if (DotProduct((( srfSurfaceFace_t * ) surf->data)->plane.normal, dir) > -0.5) {
+ // don't add faces that make sharp angles with the projection direction
+ surf->viewCount = tr.viewCount;
+ }
+ }
+ else if (*(surfaceType_t *) (surf->data) != SF_GRID &&
+ *(surfaceType_t *) (surf->data) != SF_TRIANGLES)
+ surf->viewCount = tr.viewCount;
+ // check the viewCount because the surface may have
+ // already been added if it spans multiple leafs
+ if (surf->viewCount != tr.viewCount) {
+ surf->viewCount = tr.viewCount;
+ list[*listlength] = (surfaceType_t *) surf->data;
+ (*listlength)++;
+ }
+ mark++;
+ }
+}
+
+/*
+=================
+R_AddMarkFragments
+
+=================
+*/
+void R_AddMarkFragments(int numClipPoints, vec3_t clipPoints[2][MAX_VERTS_ON_POLY],
+ int numPlanes, vec3_t *normals, float *dists,
+ int maxPoints, vec3_t pointBuffer,
+ int maxFragments, markFragment_t *fragmentBuffer,
+ int *returnedPoints, int *returnedFragments,
+ vec3_t mins, vec3_t maxs) {
+ int pingPong, i;
+ markFragment_t *mf;
+
+ // chop the surface by all the bounding planes of the to be projected polygon
+ pingPong = 0;
+
+ for ( i = 0 ; i < numPlanes ; i++ ) {
+
+ R_ChopPolyBehindPlane( numClipPoints, clipPoints[pingPong],
+ &numClipPoints, clipPoints[!pingPong],
+ normals[i], dists[i], 0.5 );
+ pingPong ^= 1;
+ if ( numClipPoints == 0 ) {
+ break;
+ }
+ }
+ // completely clipped away?
+ if ( numClipPoints == 0 ) {
+ return;
+ }
+
+ // add this fragment to the returned list
+ if ( numClipPoints + (*returnedPoints) > maxPoints ) {
+ return; // not enough space for this polygon
+ }
+ /*
+ // all the clip points should be within the bounding box
+ for ( i = 0 ; i < numClipPoints ; i++ ) {
+ int j;
+ for ( j = 0 ; j < 3 ; j++ ) {
+ if (clipPoints[pingPong][i][j] < mins[j] - 0.5) break;
+ if (clipPoints[pingPong][i][j] > maxs[j] + 0.5) break;
+ }
+ if (j < 3) break;
+ }
+ if (i < numClipPoints) return;
+ */
+
+ mf = fragmentBuffer + (*returnedFragments);
+ mf->firstPoint = (*returnedPoints);
+ mf->numPoints = numClipPoints;
+ Com_Memcpy( pointBuffer + (*returnedPoints) * 3, clipPoints[pingPong], numClipPoints * sizeof(vec3_t) );
+
+ (*returnedPoints) += numClipPoints;
+ (*returnedFragments)++;
+}
+
+/*
+=================
+R_MarkFragments
+
+=================
+*/
+int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection,
+ int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ) {
+ int numsurfaces, numPlanes;
+ int i, j, k, m, n;
+ surfaceType_t *surfaces[64];
+ vec3_t mins, maxs;
+ int returnedFragments;
+ int returnedPoints;
+ vec3_t normals[MAX_VERTS_ON_POLY+2];
+ float dists[MAX_VERTS_ON_POLY+2];
+ vec3_t clipPoints[2][MAX_VERTS_ON_POLY];
+ int numClipPoints;
+ float *v;
+ srfGridMesh_t *cv;
+ drawVert_t *dv;
+ vec3_t normal;
+ vec3_t projectionDir;
+ vec3_t v1, v2;
+ int *indexes;
+
+ //increment view count for double check prevention
+ tr.viewCount++;
+
+ //
+ VectorNormalize2( projection, projectionDir );
+ // find all the brushes that are to be considered
+ ClearBounds( mins, maxs );
+ for ( i = 0 ; i < numPoints ; i++ ) {
+ vec3_t temp;
+
+ AddPointToBounds( points[i], mins, maxs );
+ VectorAdd( points[i], projection, temp );
+ AddPointToBounds( temp, mins, maxs );
+ // make sure we get all the leafs (also the one(s) in front of the hit surface)
+ VectorMA( points[i], -20, projectionDir, temp );
+ AddPointToBounds( temp, mins, maxs );
+ }
+
+ if (numPoints > MAX_VERTS_ON_POLY) numPoints = MAX_VERTS_ON_POLY;
+ // create the bounding planes for the to be projected polygon
+ for ( i = 0 ; i < numPoints ; i++ ) {
+ VectorSubtract(points[(i+1)%numPoints], points[i], v1);
+ VectorAdd(points[i], projection, v2);
+ VectorSubtract(points[i], v2, v2);
+ CrossProduct(v1, v2, normals[i]);
+ VectorNormalizeFast(normals[i]);
+ dists[i] = DotProduct(normals[i], points[i]);
+ }
+ // add near and far clipping planes for projection
+ VectorCopy(projectionDir, normals[numPoints]);
+ dists[numPoints] = DotProduct(normals[numPoints], points[0]) - 32;
+ VectorCopy(projectionDir, normals[numPoints+1]);
+ VectorInverse(normals[numPoints+1]);
+ dists[numPoints+1] = DotProduct(normals[numPoints+1], points[0]) - 20;
+ numPlanes = numPoints + 2;
+
+ numsurfaces = 0;
+ R_BoxSurfaces_r(tr.world->nodes, mins, maxs, surfaces, 64, &numsurfaces, projectionDir);
+ //assert(numsurfaces <= 64);
+ //assert(numsurfaces != 64);
+
+ returnedPoints = 0;
+ returnedFragments = 0;
+
+ for ( i = 0 ; i < numsurfaces ; i++ ) {
+
+ if (*surfaces[i] == SF_GRID) {
+
+ cv = (srfGridMesh_t *) surfaces[i];
+ for ( m = 0 ; m < cv->height - 1 ; m++ ) {
+ for ( n = 0 ; n < cv->width - 1 ; n++ ) {
+ // We triangulate the grid and chop all triangles within
+ // the bounding planes of the to be projected polygon.
+ // LOD is not taken into account, not such a big deal though.
+ //
+ // It's probably much nicer to chop the grid itself and deal
+ // with this grid as a normal SF_GRID surface so LOD will
+ // be applied. However the LOD of that chopped grid must
+ // be synced with the LOD of the original curve.
+ // One way to do this; the chopped grid shares vertices with
+ // the original curve. When LOD is applied to the original
+ // curve the unused vertices are flagged. Now the chopped curve
+ // should skip the flagged vertices. This still leaves the
+ // problems with the vertices at the chopped grid edges.
+ //
+ // To avoid issues when LOD applied to "hollow curves" (like
+ // the ones around many jump pads) we now just add a 2 unit
+ // offset to the triangle vertices.
+ // The offset is added in the vertex normal vector direction
+ // so all triangles will still fit together.
+ // The 2 unit offset should avoid pretty much all LOD problems.
+
+ numClipPoints = 3;
+
+ dv = cv->verts + m * cv->width + n;
+
+ VectorCopy(dv[0].xyz, clipPoints[0][0]);
+ VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[0].normal, clipPoints[0][0]);
+ VectorCopy(dv[cv->width].xyz, clipPoints[0][1]);
+ VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]);
+ VectorCopy(dv[1].xyz, clipPoints[0][2]);
+ VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[1].normal, clipPoints[0][2]);
+ // check the normal of this triangle
+ VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1);
+ VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2);
+ CrossProduct(v1, v2, normal);
+ VectorNormalizeFast(normal);
+ if (DotProduct(normal, projectionDir) < -0.1) {
+ // add the fragments of this triangle
+ R_AddMarkFragments(numClipPoints, clipPoints,
+ numPlanes, normals, dists,
+ maxPoints, pointBuffer,
+ maxFragments, fragmentBuffer,
+ &returnedPoints, &returnedFragments, mins, maxs);
+
+ if ( returnedFragments == maxFragments ) {
+ return returnedFragments; // not enough space for more fragments
+ }
+ }
+
+ VectorCopy(dv[1].xyz, clipPoints[0][0]);
+ VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[1].normal, clipPoints[0][0]);
+ VectorCopy(dv[cv->width].xyz, clipPoints[0][1]);
+ VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]);
+ VectorCopy(dv[cv->width+1].xyz, clipPoints[0][2]);
+ VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[cv->width+1].normal, clipPoints[0][2]);
+ // check the normal of this triangle
+ VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1);
+ VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2);
+ CrossProduct(v1, v2, normal);
+ VectorNormalizeFast(normal);
+ if (DotProduct(normal, projectionDir) < -0.05) {
+ // add the fragments of this triangle
+ R_AddMarkFragments(numClipPoints, clipPoints,
+ numPlanes, normals, dists,
+ maxPoints, pointBuffer,
+ maxFragments, fragmentBuffer,
+ &returnedPoints, &returnedFragments, mins, maxs);
+
+ if ( returnedFragments == maxFragments ) {
+ return returnedFragments; // not enough space for more fragments
+ }
+ }
+ }
+ }
+ }
+ else if (*surfaces[i] == SF_FACE) {
+
+ srfSurfaceFace_t *surf = ( srfSurfaceFace_t * ) surfaces[i];
+
+ // check the normal of this face
+ if (DotProduct(surf->plane.normal, projectionDir) > -0.5) {
+ continue;
+ }
+
+ indexes = (int *)( (byte *)surf + surf->ofsIndices );
+ for ( k = 0 ; k < surf->numIndices ; k += 3 ) {
+ for ( j = 0 ; j < 3 ; j++ ) {
+ v = surf->points[0] + VERTEXSIZE * indexes[k+j];;
+ VectorMA( v, MARKER_OFFSET, surf->plane.normal, clipPoints[0][j] );
+ }
+
+ // add the fragments of this face
+ R_AddMarkFragments( 3 , clipPoints,
+ numPlanes, normals, dists,
+ maxPoints, pointBuffer,
+ maxFragments, fragmentBuffer,
+ &returnedPoints, &returnedFragments, mins, maxs);
+ if ( returnedFragments == maxFragments ) {
+ return returnedFragments; // not enough space for more fragments
+ }
+ }
+ }
+ else if(*surfaces[i] == SF_TRIANGLES && r_marksOnTriangleMeshes->integer) {
+
+ srfTriangles_t *surf = (srfTriangles_t *) surfaces[i];
+
+ for (k = 0; k < surf->numIndexes; k += 3)
+ {
+ for(j = 0; j < 3; j++)
+ {
+ v = surf->verts[surf->indexes[k + j]].xyz;
+ VectorMA(v, MARKER_OFFSET, surf->verts[surf->indexes[k + j]].normal, clipPoints[0][j]);
+ }
+
+ // add the fragments of this face
+ R_AddMarkFragments(3, clipPoints,
+ numPlanes, normals, dists,
+ maxPoints, pointBuffer,
+ maxFragments, fragmentBuffer, &returnedPoints, &returnedFragments, mins, maxs);
+ if(returnedFragments == maxFragments)
+ {
+ return returnedFragments; // not enough space for more fragments
+ }
+ }
+ }
+ }
+ return returnedFragments;
+}
+
diff --git a/code/renderer/tr_mesh.c b/code/renderer/tr_mesh.c
new file mode 100644
index 0000000..9b60348
--- /dev/null
+++ b/code/renderer/tr_mesh.c
@@ -0,0 +1,418 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// tr_mesh.c: triangle model functions
+
+#include "tr_local.h"
+
+static float ProjectRadius( float r, vec3_t location )
+{
+ float pr;
+ float dist;
+ float c;
+ vec3_t p;
+ float projected[4];
+
+ c = DotProduct( tr.viewParms.or.axis[0], tr.viewParms.or.origin );
+ dist = DotProduct( tr.viewParms.or.axis[0], location ) - c;
+
+ if ( dist <= 0 )
+ return 0;
+
+ p[0] = 0;
+ p[1] = fabs( r );
+ p[2] = -dist;
+
+ projected[0] = p[0] * tr.viewParms.projectionMatrix[0] +
+ p[1] * tr.viewParms.projectionMatrix[4] +
+ p[2] * tr.viewParms.projectionMatrix[8] +
+ tr.viewParms.projectionMatrix[12];
+
+ projected[1] = p[0] * tr.viewParms.projectionMatrix[1] +
+ p[1] * tr.viewParms.projectionMatrix[5] +
+ p[2] * tr.viewParms.projectionMatrix[9] +
+ tr.viewParms.projectionMatrix[13];
+
+ projected[2] = p[0] * tr.viewParms.projectionMatrix[2] +
+ p[1] * tr.viewParms.projectionMatrix[6] +
+ p[2] * tr.viewParms.projectionMatrix[10] +
+ tr.viewParms.projectionMatrix[14];
+
+ projected[3] = p[0] * tr.viewParms.projectionMatrix[3] +
+ p[1] * tr.viewParms.projectionMatrix[7] +
+ p[2] * tr.viewParms.projectionMatrix[11] +
+ tr.viewParms.projectionMatrix[15];
+
+
+ pr = projected[1] / projected[3];
+
+ if ( pr > 1.0f )
+ pr = 1.0f;
+
+ return pr;
+}
+
+/*
+=============
+R_CullModel
+=============
+*/
+static int R_CullModel( md3Header_t *header, trRefEntity_t *ent ) {
+ vec3_t bounds[2];
+ md3Frame_t *oldFrame, *newFrame;
+ int i;
+
+ // compute frame pointers
+ newFrame = ( md3Frame_t * ) ( ( byte * ) header + header->ofsFrames ) + ent->e.frame;
+ oldFrame = ( md3Frame_t * ) ( ( byte * ) header + header->ofsFrames ) + ent->e.oldframe;
+
+ // cull bounding sphere ONLY if this is not an upscaled entity
+ if ( !ent->e.nonNormalizedAxes )
+ {
+ if ( ent->e.frame == ent->e.oldframe )
+ {
+ switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) )
+ {
+ case CULL_OUT:
+ tr.pc.c_sphere_cull_md3_out++;
+ return CULL_OUT;
+
+ case CULL_IN:
+ tr.pc.c_sphere_cull_md3_in++;
+ return CULL_IN;
+
+ case CULL_CLIP:
+ tr.pc.c_sphere_cull_md3_clip++;
+ break;
+ }
+ }
+ else
+ {
+ int sphereCull, sphereCullB;
+
+ sphereCull = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius );
+ if ( newFrame == oldFrame ) {
+ sphereCullB = sphereCull;
+ } else {
+ sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius );
+ }
+
+ if ( sphereCull == sphereCullB )
+ {
+ if ( sphereCull == CULL_OUT )
+ {
+ tr.pc.c_sphere_cull_md3_out++;
+ return CULL_OUT;
+ }
+ else if ( sphereCull == CULL_IN )
+ {
+ tr.pc.c_sphere_cull_md3_in++;
+ return CULL_IN;
+ }
+ else
+ {
+ tr.pc.c_sphere_cull_md3_clip++;
+ }
+ }
+ }
+ }
+
+ // calculate a bounding box in the current coordinate system
+ for (i = 0 ; i < 3 ; i++) {
+ bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i];
+ bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i];
+ }
+
+ switch ( R_CullLocalBox( bounds ) )
+ {
+ case CULL_IN:
+ tr.pc.c_box_cull_md3_in++;
+ return CULL_IN;
+ case CULL_CLIP:
+ tr.pc.c_box_cull_md3_clip++;
+ return CULL_CLIP;
+ case CULL_OUT:
+ default:
+ tr.pc.c_box_cull_md3_out++;
+ return CULL_OUT;
+ }
+}
+
+
+/*
+=================
+R_ComputeLOD
+
+=================
+*/
+int R_ComputeLOD( trRefEntity_t *ent ) {
+ float radius;
+ float flod, lodscale;
+ float projectedRadius;
+ md3Frame_t *frame;
+#ifdef RAVENMD4
+ mdrHeader_t *mdr;
+ mdrFrame_t *mdrframe;
+#endif
+ int lod;
+
+ if ( tr.currentModel->numLods < 2 )
+ {
+ // model has only 1 LOD level, skip computations and bias
+ lod = 0;
+ }
+ else
+ {
+ // multiple LODs exist, so compute projected bounding sphere
+ // and use that as a criteria for selecting LOD
+
+#ifdef RAVENMD4
+ // This is an MDR model.
+
+ if(tr.currentModel->md4)
+ {
+ int frameSize;
+ mdr = (mdrHeader_t *) tr.currentModel->md4;
+ frameSize = (size_t) (&((mdrFrame_t *)0)->bones[mdr->numBones]);
+
+ mdrframe = (mdrFrame_t *) ((byte *) mdr + mdr->ofsFrames + frameSize * ent->e.frame);
+
+ radius = RadiusFromBounds(mdrframe->bounds[0], mdrframe->bounds[1]);
+ }
+ else
+#endif
+ {
+ frame = ( md3Frame_t * ) ( ( ( unsigned char * ) tr.currentModel->md3[0] ) + tr.currentModel->md3[0]->ofsFrames );
+
+ frame += ent->e.frame;
+
+ radius = RadiusFromBounds( frame->bounds[0], frame->bounds[1] );
+ }
+
+ if ( ( projectedRadius = ProjectRadius( radius, ent->e.origin ) ) != 0 )
+ {
+ lodscale = r_lodscale->value;
+ if (lodscale > 20) lodscale = 20;
+ flod = 1.0f - projectedRadius * lodscale;
+ }
+ else
+ {
+ // object intersects near view plane, e.g. view weapon
+ flod = 0;
+ }
+
+ flod *= tr.currentModel->numLods;
+ lod = myftol( flod );
+
+ if ( lod < 0 )
+ {
+ lod = 0;
+ }
+ else if ( lod >= tr.currentModel->numLods )
+ {
+ lod = tr.currentModel->numLods - 1;
+ }
+ }
+
+ lod += r_lodbias->integer;
+
+ if ( lod >= tr.currentModel->numLods )
+ lod = tr.currentModel->numLods - 1;
+ if ( lod < 0 )
+ lod = 0;
+
+ return lod;
+}
+
+/*
+=================
+R_ComputeFogNum
+
+=================
+*/
+int R_ComputeFogNum( md3Header_t *header, trRefEntity_t *ent ) {
+ int i, j;
+ fog_t *fog;
+ md3Frame_t *md3Frame;
+ vec3_t localOrigin;
+
+ if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) {
+ return 0;
+ }
+
+ // FIXME: non-normalized axis issues
+ md3Frame = ( md3Frame_t * ) ( ( byte * ) header + header->ofsFrames ) + ent->e.frame;
+ VectorAdd( ent->e.origin, md3Frame->localOrigin, localOrigin );
+ for ( i = 1 ; i < tr.world->numfogs ; i++ ) {
+ fog = &tr.world->fogs[i];
+ for ( j = 0 ; j < 3 ; j++ ) {
+ if ( localOrigin[j] - md3Frame->radius >= fog->bounds[1][j] ) {
+ break;
+ }
+ if ( localOrigin[j] + md3Frame->radius <= fog->bounds[0][j] ) {
+ break;
+ }
+ }
+ if ( j == 3 ) {
+ return i;
+ }
+ }
+
+ return 0;
+}
+
+/*
+=================
+R_AddMD3Surfaces
+
+=================
+*/
+void R_AddMD3Surfaces( trRefEntity_t *ent ) {
+ int i;
+ md3Header_t *header = NULL;
+ md3Surface_t *surface = NULL;
+ md3Shader_t *md3Shader = NULL;
+ shader_t *shader = NULL;
+ int cull;
+ int lod;
+ int fogNum;
+ qboolean personalModel;
+
+ // don't add third_person objects if not in a portal
+ personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal;
+
+ if ( ent->e.renderfx & RF_WRAP_FRAMES ) {
+ ent->e.frame %= tr.currentModel->md3[0]->numFrames;
+ ent->e.oldframe %= tr.currentModel->md3[0]->numFrames;
+ }
+
+ //
+ // Validate the frames so there is no chance of a crash.
+ // This will write directly into the entity structure, so
+ // when the surfaces are rendered, they don't need to be
+ // range checked again.
+ //
+ if ( (ent->e.frame >= tr.currentModel->md3[0]->numFrames)
+ || (ent->e.frame < 0)
+ || (ent->e.oldframe >= tr.currentModel->md3[0]->numFrames)
+ || (ent->e.oldframe < 0) ) {
+ ri.Printf( PRINT_DEVELOPER, "R_AddMD3Surfaces: no such frame %d to %d for '%s'\n",
+ ent->e.oldframe, ent->e.frame,
+ tr.currentModel->name );
+ ent->e.frame = 0;
+ ent->e.oldframe = 0;
+ }
+
+ //
+ // compute LOD
+ //
+ lod = R_ComputeLOD( ent );
+
+ header = tr.currentModel->md3[lod];
+
+ //
+ // cull the entire model if merged bounding box of both frames
+ // is outside the view frustum.
+ //
+ cull = R_CullModel ( header, ent );
+ if ( cull == CULL_OUT ) {
+ return;
+ }
+
+ //
+ // set up lighting now that we know we aren't culled
+ //
+ if ( !personalModel || r_shadows->integer > 1 ) {
+ R_SetupEntityLighting( &tr.refdef, ent );
+ }
+
+ //
+ // see if we are in a fog volume
+ //
+ fogNum = R_ComputeFogNum( header, ent );
+
+ //
+ // draw all surfaces
+ //
+ surface = (md3Surface_t *)( (byte *)header + header->ofsSurfaces );
+ for ( i = 0 ; i < header->numSurfaces ; i++ ) {
+
+ if ( ent->e.customShader ) {
+ shader = R_GetShaderByHandle( ent->e.customShader );
+ } else if ( ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins ) {
+ skin_t *skin;
+ int j;
+
+ skin = R_GetSkinByHandle( ent->e.customSkin );
+
+ // match the surface name to something in the skin file
+ shader = tr.defaultShader;
+ for ( j = 0 ; j < skin->numSurfaces ; j++ ) {
+ // the names have both been lowercased
+ if ( !strcmp( skin->surfaces[j]->name, surface->name ) ) {
+ shader = skin->surfaces[j]->shader;
+ break;
+ }
+ }
+ if (shader == tr.defaultShader) {
+ ri.Printf( PRINT_DEVELOPER, "WARNING: no shader for surface %s in skin %s\n", surface->name, skin->name);
+ }
+ else if (shader->defaultShader) {
+ ri.Printf( PRINT_DEVELOPER, "WARNING: shader %s in skin %s not found\n", shader->name, skin->name);
+ }
+ } else if ( surface->numShaders <= 0 ) {
+ shader = tr.defaultShader;
+ } else {
+ md3Shader = (md3Shader_t *) ( (byte *)surface + surface->ofsShaders );
+ md3Shader += ent->e.skinNum % surface->numShaders;
+ shader = tr.shaders[ md3Shader->shaderIndex ];
+ }
+
+
+ // we will add shadows even if the main object isn't visible in the view
+
+ // stencil shadows can't do personal models unless I polyhedron clip
+ if ( !personalModel
+ && r_shadows->integer == 2
+ && fogNum == 0
+ && !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) )
+ && shader->sort == SS_OPAQUE ) {
+ R_AddDrawSurf( (void *)surface, tr.shadowShader, 0, qfalse );
+ }
+
+ // projection shadows work fine with personal models
+ if ( r_shadows->integer == 3
+ && fogNum == 0
+ && (ent->e.renderfx & RF_SHADOW_PLANE )
+ && shader->sort == SS_OPAQUE ) {
+ R_AddDrawSurf( (void *)surface, tr.projectionShadowShader, 0, qfalse );
+ }
+
+ // don't add third_person objects if not viewing through a portal
+ if ( !personalModel ) {
+ R_AddDrawSurf( (void *)surface, shader, fogNum, qfalse );
+ }
+
+ surface = (md3Surface_t *)( (byte *)surface + surface->ofsEnd );
+ }
+
+}
+
diff --git a/code/renderer/tr_model.c b/code/renderer/tr_model.c
new file mode 100644
index 0000000..5355580
--- /dev/null
+++ b/code/renderer/tr_model.c
@@ -0,0 +1,1148 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// tr_models.c -- model loading and caching
+
+#include "tr_local.h"
+
+#define LL(x) x=LittleLong(x)
+
+static qboolean R_LoadMD3 (model_t *mod, int lod, void *buffer, const char *name );
+static qboolean R_LoadMD4 (model_t *mod, void *buffer, const char *name );
+#ifdef RAVENMD4
+static qboolean R_LoadMDR (model_t *mod, void *buffer, int filesize, const char *name );
+#endif
+
+model_t *loadmodel;
+
+/*
+** R_GetModelByHandle
+*/
+model_t *R_GetModelByHandle( qhandle_t index ) {
+ model_t *mod;
+
+ // out of range gets the defualt model
+ if ( index < 1 || index >= tr.numModels ) {
+ return tr.models[0];
+ }
+
+ mod = tr.models[index];
+
+ return mod;
+}
+
+//===============================================================================
+
+/*
+** R_AllocModel
+*/
+model_t *R_AllocModel( void ) {
+ model_t *mod;
+
+ if ( tr.numModels == MAX_MOD_KNOWN ) {
+ return NULL;
+ }
+
+ mod = ri.Hunk_Alloc( sizeof( *tr.models[tr.numModels] ), h_low );
+ mod->index = tr.numModels;
+ tr.models[tr.numModels] = mod;
+ tr.numModels++;
+
+ return mod;
+}
+
+/*
+====================
+RE_RegisterModel
+
+Loads in a model for the given name
+
+Zero will be returned if the model fails to load.
+An entry will be retained for failed models as an
+optimization to prevent disk rescanning if they are
+asked for again.
+====================
+*/
+qhandle_t RE_RegisterModel( const char *name ) {
+ model_t *mod;
+ union {
+ unsigned *u;
+ void *v;
+ } buf;
+ int lod;
+ int ident;
+ qboolean loaded = qfalse;
+ qhandle_t hModel;
+ int numLoaded;
+ char *fext, defex[] = "md3", filename[MAX_QPATH], namebuf[MAX_QPATH+20];
+
+ if ( !name || !name[0] ) {
+ ri.Printf( PRINT_ALL, "RE_RegisterModel: NULL name\n" );
+ return 0;
+ }
+
+ if ( strlen( name ) >= MAX_QPATH ) {
+ Com_Printf( "Model name exceeds MAX_QPATH\n" );
+ return 0;
+ }
+
+ //
+ // search the currently loaded models
+ //
+ for ( hModel = 1 ; hModel < tr.numModels; hModel++ ) {
+ mod = tr.models[hModel];
+ if ( !strcmp( mod->name, name ) ) {
+ if( mod->type == MOD_BAD ) {
+ return 0;
+ }
+ return hModel;
+ }
+ }
+
+ // allocate a new model_t
+
+ if ( ( mod = R_AllocModel() ) == NULL ) {
+ ri.Printf( PRINT_WARNING, "RE_RegisterModel: R_AllocModel() failed for '%s'\n", name);
+ return 0;
+ }
+
+ // only set the name after the model has been successfully loaded
+ Q_strncpyz( mod->name, name, sizeof( mod->name ) );
+
+
+ // make sure the render thread is stopped
+ R_SyncRenderThread();
+
+ mod->numLods = 0;
+
+ //
+ // load the files
+ //
+ numLoaded = 0;
+
+ strcpy(filename, name);
+
+ fext = strchr(filename, '.');
+ if(!fext)
+ fext = defex;
+ else
+ {
+ *fext = '\0';
+ fext++;
+ }
+
+#ifdef RAVENMD4
+ if(!Q_stricmp(fext, "mdr"))
+ {
+ int filesize;
+
+ filesize = ri.FS_ReadFile(name, (void **) &buf.v);
+ if(!buf.u)
+ {
+ ri.Printf (PRINT_WARNING,"RE_RegisterModel: couldn't load %s\n", name);
+ mod->type = MOD_BAD;
+ return 0;
+ }
+
+ ident = LittleLong(*(unsigned *)buf.u);
+ if(ident == MDR_IDENT)
+ loaded = R_LoadMDR(mod, buf.u, filesize, name);
+
+ ri.FS_FreeFile (buf.v);
+
+ if(!loaded)
+ {
+ ri.Printf(PRINT_WARNING,"RE_RegisterModel: couldn't load mdr file %s\n", name);
+ mod->type = MOD_BAD;
+ return 0;
+ }
+
+ return mod->index;
+ }
+#endif
+
+ fext = defex;
+
+ for ( lod = MD3_MAX_LODS - 1 ; lod >= 0 ; lod-- ) {
+ if ( lod )
+ Com_sprintf(namebuf, sizeof(namebuf), "%s_%d.%s", filename, lod, fext);
+ else
+ Com_sprintf(namebuf, sizeof(namebuf), "%s.%s", filename, fext);
+
+ ri.FS_ReadFile( namebuf, &buf.v );
+ if ( !buf.u ) {
+ continue;
+ }
+
+ loadmodel = mod;
+
+ ident = LittleLong(*(unsigned *)buf.u);
+ if ( ident == MD4_IDENT ) {
+ loaded = R_LoadMD4( mod, buf.u, name );
+ } else {
+ if ( ident != MD3_IDENT ) {
+ ri.Printf (PRINT_WARNING,"RE_RegisterModel: unknown fileid for %s\n", name);
+ goto fail;
+ }
+
+ loaded = R_LoadMD3( mod, lod, buf.u, name );
+ }
+
+ ri.FS_FreeFile (buf.v);
+
+ if ( !loaded ) {
+ if ( lod == 0 ) {
+ goto fail;
+ } else {
+ break;
+ }
+ } else {
+ mod->numLods++;
+ numLoaded++;
+ // if we have a valid model and are biased
+ // so that we won't see any higher detail ones,
+ // stop loading them
+// if ( lod <= r_lodbias->integer ) {
+// break;
+// }
+ }
+ }
+
+ if ( numLoaded ) {
+ // duplicate into higher lod spots that weren't
+ // loaded, in case the user changes r_lodbias on the fly
+ for ( lod-- ; lod >= 0 ; lod-- ) {
+ mod->numLods++;
+ mod->md3[lod] = mod->md3[lod+1];
+ }
+
+ return mod->index;
+ }
+#ifdef _DEBUG
+ else {
+ ri.Printf (PRINT_WARNING,"RE_RegisterModel: couldn't load %s\n", name);
+ }
+#endif
+
+fail:
+ // we still keep the model_t around, so if the model name is asked for
+ // again, we won't bother scanning the filesystem
+ mod->type = MOD_BAD;
+ return 0;
+}
+
+
+/*
+=================
+R_LoadMD3
+=================
+*/
+static qboolean R_LoadMD3 (model_t *mod, int lod, void *buffer, const char *mod_name ) {
+ int i, j;
+ md3Header_t *pinmodel;
+ md3Frame_t *frame;
+ md3Surface_t *surf;
+ md3Shader_t *shader;
+ md3Triangle_t *tri;
+ md3St_t *st;
+ md3XyzNormal_t *xyz;
+ md3Tag_t *tag;
+ int version;
+ int size;
+
+ pinmodel = (md3Header_t *)buffer;
+
+ version = LittleLong (pinmodel->version);
+ if (version != MD3_VERSION) {
+ ri.Printf( PRINT_WARNING, "R_LoadMD3: %s has wrong version (%i should be %i)\n",
+ mod_name, version, MD3_VERSION);
+ return qfalse;
+ }
+
+ mod->type = MOD_MESH;
+ size = LittleLong(pinmodel->ofsEnd);
+ mod->dataSize += size;
+ mod->md3[lod] = ri.Hunk_Alloc( size, h_low );
+
+ Com_Memcpy (mod->md3[lod], buffer, LittleLong(pinmodel->ofsEnd) );
+
+ LL(mod->md3[lod]->ident);
+ LL(mod->md3[lod]->version);
+ LL(mod->md3[lod]->numFrames);
+ LL(mod->md3[lod]->numTags);
+ LL(mod->md3[lod]->numSurfaces);
+ LL(mod->md3[lod]->ofsFrames);
+ LL(mod->md3[lod]->ofsTags);
+ LL(mod->md3[lod]->ofsSurfaces);
+ LL(mod->md3[lod]->ofsEnd);
+
+ if ( mod->md3[lod]->numFrames < 1 ) {
+ ri.Printf( PRINT_WARNING, "R_LoadMD3: %s has no frames\n", mod_name );
+ return qfalse;
+ }
+
+ // swap all the frames
+ frame = (md3Frame_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsFrames );
+ for ( i = 0 ; i < mod->md3[lod]->numFrames ; i++, frame++) {
+ frame->radius = LittleFloat( frame->radius );
+ for ( j = 0 ; j < 3 ; j++ ) {
+ frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] );
+ frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] );
+ frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] );
+ }
+ }
+
+ // swap all the tags
+ tag = (md3Tag_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsTags );
+ for ( i = 0 ; i < mod->md3[lod]->numTags * mod->md3[lod]->numFrames ; i++, tag++) {
+ for ( j = 0 ; j < 3 ; j++ ) {
+ tag->origin[j] = LittleFloat( tag->origin[j] );
+ tag->axis[0][j] = LittleFloat( tag->axis[0][j] );
+ tag->axis[1][j] = LittleFloat( tag->axis[1][j] );
+ tag->axis[2][j] = LittleFloat( tag->axis[2][j] );
+ }
+ }
+
+ // swap all the surfaces
+ surf = (md3Surface_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsSurfaces );
+ for ( i = 0 ; i < mod->md3[lod]->numSurfaces ; i++) {
+
+ LL(surf->ident);
+ LL(surf->flags);
+ LL(surf->numFrames);
+ LL(surf->numShaders);
+ LL(surf->numTriangles);
+ LL(surf->ofsTriangles);
+ LL(surf->numVerts);
+ LL(surf->ofsShaders);
+ LL(surf->ofsSt);
+ LL(surf->ofsXyzNormals);
+ LL(surf->ofsEnd);
+
+ if ( surf->numVerts > SHADER_MAX_VERTEXES ) {
+ ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i verts on a surface (%i)",
+ mod_name, SHADER_MAX_VERTEXES, surf->numVerts );
+ }
+ if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) {
+ ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i triangles on a surface (%i)",
+ mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles );
+ }
+
+ // change to surface identifier
+ surf->ident = SF_MD3;
+
+ // lowercase the surface name so skin compares are faster
+ Q_strlwr( surf->name );
+
+ // strip off a trailing _1 or _2
+ // this is a crutch for q3data being a mess
+ j = strlen( surf->name );
+ if ( j > 2 && surf->name[j-2] == '_' ) {
+ surf->name[j-2] = 0;
+ }
+
+ // register the shaders
+ shader = (md3Shader_t *) ( (byte *)surf + surf->ofsShaders );
+ for ( j = 0 ; j < surf->numShaders ; j++, shader++ ) {
+ shader_t *sh;
+
+ sh = R_FindShader( shader->name, LIGHTMAP_NONE, qtrue );
+ if ( sh->defaultShader ) {
+ shader->shaderIndex = 0;
+ } else {
+ shader->shaderIndex = sh->index;
+ }
+ }
+
+ // swap all the triangles
+ tri = (md3Triangle_t *) ( (byte *)surf + surf->ofsTriangles );
+ for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) {
+ LL(tri->indexes[0]);
+ LL(tri->indexes[1]);
+ LL(tri->indexes[2]);
+ }
+
+ // swap all the ST
+ st = (md3St_t *) ( (byte *)surf + surf->ofsSt );
+ for ( j = 0 ; j < surf->numVerts ; j++, st++ ) {
+ st->st[0] = LittleFloat( st->st[0] );
+ st->st[1] = LittleFloat( st->st[1] );
+ }
+
+ // swap all the XyzNormals
+ xyz = (md3XyzNormal_t *) ( (byte *)surf + surf->ofsXyzNormals );
+ for ( j = 0 ; j < surf->numVerts * surf->numFrames ; j++, xyz++ )
+ {
+ xyz->xyz[0] = LittleShort( xyz->xyz[0] );
+ xyz->xyz[1] = LittleShort( xyz->xyz[1] );
+ xyz->xyz[2] = LittleShort( xyz->xyz[2] );
+
+ xyz->normal = LittleShort( xyz->normal );
+ }
+
+
+ // find the next surface
+ surf = (md3Surface_t *)( (byte *)surf + surf->ofsEnd );
+ }
+
+ return qtrue;
+}
+
+
+#ifdef RAVENMD4
+
+/*
+=================
+R_LoadMDR
+=================
+*/
+static qboolean R_LoadMDR( model_t *mod, void *buffer, int filesize, const char *mod_name )
+{
+ int i, j, k, l;
+ mdrHeader_t *pinmodel, *mdr;
+ mdrFrame_t *frame;
+ mdrLOD_t *lod, *curlod;
+ mdrSurface_t *surf, *cursurf;
+ mdrTriangle_t *tri, *curtri;
+ mdrVertex_t *v, *curv;
+ mdrWeight_t *weight, *curweight;
+ mdrTag_t *tag, *curtag;
+ int size;
+ shader_t *sh;
+
+ pinmodel = (mdrHeader_t *)buffer;
+
+ pinmodel->version = LittleLong(pinmodel->version);
+ if (pinmodel->version != MDR_VERSION)
+ {
+ ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has wrong version (%i should be %i)\n", mod_name, pinmodel->version, MDR_VERSION);
+ return qfalse;
+ }
+
+ size = LittleLong(pinmodel->ofsEnd);
+
+ if(size > filesize)
+ {
+ ri.Printf(PRINT_WARNING, "R_LoadMDR: Header of %s is broken. Wrong filesize declared!\n", mod_name);
+ return qfalse;
+ }
+
+ mod->type = MOD_MDR;
+
+ LL(pinmodel->numFrames);
+ LL(pinmodel->numBones);
+ LL(pinmodel->ofsFrames);
+
+ // This is a model that uses some type of compressed Bones. We don't want to uncompress every bone for each rendered frame
+ // over and over again, we'll uncompress it in this function already, so we must adjust the size of the target md4.
+ if(pinmodel->ofsFrames < 0)
+ {
+ // mdrFrame_t is larger than mdrCompFrame_t:
+ size += pinmodel->numFrames * sizeof(frame->name);
+ // now add enough space for the uncompressed bones.
+ size += pinmodel->numFrames * pinmodel->numBones * ((sizeof(mdrBone_t) - sizeof(mdrCompBone_t)));
+ }
+
+ // simple bounds check
+ if(pinmodel->numBones < 0 ||
+ sizeof(*mdr) + pinmodel->numFrames * (sizeof(*frame) + (pinmodel->numBones - 1) * sizeof(*frame->bones)) > size)
+ {
+ ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
+ return qfalse;
+ }
+
+ mod->dataSize += size;
+ mod->md4 = mdr = ri.Hunk_Alloc( size, h_low );
+
+ // Copy all the values over from the file and fix endian issues in the process, if necessary.
+
+ mdr->ident = LittleLong(pinmodel->ident);
+ mdr->version = pinmodel->version; // Don't need to swap byte order on this one, we already did above.
+ Q_strncpyz(mdr->name, pinmodel->name, sizeof(mdr->name));
+ mdr->numFrames = pinmodel->numFrames;
+ mdr->numBones = pinmodel->numBones;
+ mdr->numLODs = LittleLong(pinmodel->numLODs);
+ mdr->numTags = LittleLong(pinmodel->numTags);
+ // We don't care about the other offset values, we'll generate them ourselves while loading.
+
+ mod->numLods = mdr->numLODs;
+
+ if ( mdr->numFrames < 1 )
+ {
+ ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has no frames\n", mod_name);
+ return qfalse;
+ }
+
+ /* The first frame will be put into the first free space after the header */
+ frame = (mdrFrame_t *)(mdr + 1);
+ mdr->ofsFrames = (int)((byte *) frame - (byte *) mdr);
+
+ if (pinmodel->ofsFrames < 0)
+ {
+ mdrCompFrame_t *cframe;
+
+ // compressed model...
+ cframe = (mdrCompFrame_t *)((byte *) pinmodel - pinmodel->ofsFrames);
+
+ for(i = 0; i < mdr->numFrames; i++)
+ {
+ for(j = 0; j < 3; j++)
+ {
+ frame->bounds[0][j] = LittleFloat(cframe->bounds[0][j]);
+ frame->bounds[1][j] = LittleFloat(cframe->bounds[1][j]);
+ frame->localOrigin[j] = LittleFloat(cframe->localOrigin[j]);
+ }
+
+ frame->radius = LittleFloat(cframe->radius);
+ frame->name[0] = '\0'; // No name supplied in the compressed version.
+
+ for(j = 0; j < mdr->numBones; j++)
+ {
+ for(k = 0; k < (sizeof(cframe->bones[j].Comp) / 2); k++)
+ {
+ // Do swapping for the uncompressing functions. They seem to use shorts
+ // values only, so I assume this will work. Never tested it on other
+ // platforms, though.
+
+ ((unsigned short *)(cframe->bones[j].Comp))[k] =
+ LittleShort( ((unsigned short *)(cframe->bones[j].Comp))[k] );
+ }
+
+ /* Now do the actual uncompressing */
+ MC_UnCompress(frame->bones[j].matrix, cframe->bones[j].Comp);
+ }
+
+ // Next Frame...
+ cframe = (mdrCompFrame_t *) &cframe->bones[j];
+ frame = (mdrFrame_t *) &frame->bones[j];
+ }
+ }
+ else
+ {
+ mdrFrame_t *curframe;
+
+ // uncompressed model...
+ //
+
+ curframe = (mdrFrame_t *)((byte *) pinmodel + pinmodel->ofsFrames);
+
+ // swap all the frames
+ for ( i = 0 ; i < mdr->numFrames ; i++)
+ {
+ for(j = 0; j < 3; j++)
+ {
+ frame->bounds[0][j] = LittleFloat(curframe->bounds[0][j]);
+ frame->bounds[1][j] = LittleFloat(curframe->bounds[1][j]);
+ frame->localOrigin[j] = LittleFloat(curframe->localOrigin[j]);
+ }
+
+ frame->radius = LittleFloat(curframe->radius);
+ Q_strncpyz(frame->name, curframe->name, sizeof(frame->name));
+
+ for (j = 0; j < (int) (mdr->numBones * sizeof(mdrBone_t) / 4); j++)
+ {
+ ((float *)frame->bones)[j] = LittleFloat( ((float *)curframe->bones)[j] );
+ }
+
+ curframe++;
+ frame++;
+ }
+ }
+
+ // frame should now point to the first free address after all frames.
+ lod = (mdrLOD_t *) frame;
+ mdr->ofsLODs = (int) ((byte *) lod - (byte *)mdr);
+
+ curlod = (mdrLOD_t *)((byte *) pinmodel + LittleLong(pinmodel->ofsLODs));
+
+ // swap all the LOD's
+ for ( l = 0 ; l < mdr->numLODs ; l++)
+ {
+ // simple bounds check
+ if((byte *) (lod + 1) > (byte *) mdr + size)
+ {
+ ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
+ return qfalse;
+ }
+
+ lod->numSurfaces = LittleLong(curlod->numSurfaces);
+
+ // swap all the surfaces
+ surf = (mdrSurface_t *) (lod + 1);
+ lod->ofsSurfaces = (int)((byte *) surf - (byte *) lod);
+ cursurf = (mdrSurface_t *) ((byte *)curlod + LittleLong(curlod->ofsSurfaces));
+
+ for ( i = 0 ; i < lod->numSurfaces ; i++)
+ {
+ // simple bounds check
+ if((byte *) (surf + 1) > (byte *) mdr + size)
+ {
+ ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
+ return qfalse;
+ }
+
+ // first do some copying stuff
+
+ surf->ident = SF_MDR;
+ Q_strncpyz(surf->name, cursurf->name, sizeof(surf->name));
+ Q_strncpyz(surf->shader, cursurf->shader, sizeof(surf->shader));
+
+ surf->ofsHeader = (byte *) mdr - (byte *) surf;
+
+ surf->numVerts = LittleLong(cursurf->numVerts);
+ surf->numTriangles = LittleLong(cursurf->numTriangles);
+ // numBoneReferences and BoneReferences generally seem to be unused
+
+ // now do the checks that may fail.
+ if ( surf->numVerts > SHADER_MAX_VERTEXES )
+ {
+ ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has more than %i verts on a surface (%i)",
+ mod_name, SHADER_MAX_VERTEXES, surf->numVerts );
+ return qfalse;
+ }
+ if ( surf->numTriangles*3 > SHADER_MAX_INDEXES )
+ {
+ ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has more than %i triangles on a surface (%i)",
+ mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles );
+ return qfalse;
+ }
+ // lowercase the surface name so skin compares are faster
+ Q_strlwr( surf->name );
+
+ // register the shaders
+ sh = R_FindShader(surf->shader, LIGHTMAP_NONE, qtrue);
+ if ( sh->defaultShader ) {
+ surf->shaderIndex = 0;
+ } else {
+ surf->shaderIndex = sh->index;
+ }
+
+ // now copy the vertexes.
+ v = (mdrVertex_t *) (surf + 1);
+ surf->ofsVerts = (int)((byte *) v - (byte *) surf);
+ curv = (mdrVertex_t *) ((byte *)cursurf + LittleLong(cursurf->ofsVerts));
+
+ for(j = 0; j < surf->numVerts; j++)
+ {
+ LL(curv->numWeights);
+
+ // simple bounds check
+ if(curv->numWeights < 0 || (byte *) (v + 1) + (curv->numWeights - 1) * sizeof(*weight) > (byte *) mdr + size)
+ {
+ ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
+ return qfalse;
+ }
+
+ v->normal[0] = LittleFloat(curv->normal[0]);
+ v->normal[1] = LittleFloat(curv->normal[1]);
+ v->normal[2] = LittleFloat(curv->normal[2]);
+
+ v->texCoords[0] = LittleFloat(curv->texCoords[0]);
+ v->texCoords[1] = LittleFloat(curv->texCoords[1]);
+
+ v->numWeights = curv->numWeights;
+ weight = &v->weights[0];
+ curweight = &curv->weights[0];
+
+ // Now copy all the weights
+ for(k = 0; k < v->numWeights; k++)
+ {
+ weight->boneIndex = LittleLong(curweight->boneIndex);
+ weight->boneWeight = LittleFloat(curweight->boneWeight);
+
+ weight->offset[0] = LittleFloat(curweight->offset[0]);
+ weight->offset[1] = LittleFloat(curweight->offset[1]);
+ weight->offset[2] = LittleFloat(curweight->offset[2]);
+
+ weight++;
+ curweight++;
+ }
+
+ v = (mdrVertex_t *) weight;
+ curv = (mdrVertex_t *) curweight;
+ }
+
+ // we know the offset to the triangles now:
+ tri = (mdrTriangle_t *) v;
+ surf->ofsTriangles = (int)((byte *) tri - (byte *) surf);
+ curtri = (mdrTriangle_t *)((byte *) cursurf + LittleLong(cursurf->ofsTriangles));
+
+ // simple bounds check
+ if(surf->numTriangles < 0 || (byte *) (tri + surf->numTriangles) > (byte *) mdr + size)
+ {
+ ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
+ return qfalse;
+ }
+
+ for(j = 0; j < surf->numTriangles; j++)
+ {
+ tri->indexes[0] = LittleLong(curtri->indexes[0]);
+ tri->indexes[1] = LittleLong(curtri->indexes[1]);
+ tri->indexes[2] = LittleLong(curtri->indexes[2]);
+
+ tri++;
+ curtri++;
+ }
+
+ // tri now points to the end of the surface.
+ surf->ofsEnd = (byte *) tri - (byte *) surf;
+ surf = (mdrSurface_t *) tri;
+
+ // find the next surface.
+ cursurf = (mdrSurface_t *) ((byte *) cursurf + LittleLong(cursurf->ofsEnd));
+ }
+
+ // surf points to the next lod now.
+ lod->ofsEnd = (int)((byte *) surf - (byte *) lod);
+ lod = (mdrLOD_t *) surf;
+
+ // find the next LOD.
+ curlod = (mdrLOD_t *)((byte *) curlod + LittleLong(curlod->ofsEnd));
+ }
+
+ // lod points to the first tag now, so update the offset too.
+ tag = (mdrTag_t *) lod;
+ mdr->ofsTags = (int)((byte *) tag - (byte *) mdr);
+ curtag = (mdrTag_t *) ((byte *)pinmodel + LittleLong(pinmodel->ofsTags));
+
+ // simple bounds check
+ if(mdr->numTags < 0 || (byte *) (tag + mdr->numTags) > (byte *) mdr + size)
+ {
+ ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name);
+ return qfalse;
+ }
+
+ for (i = 0 ; i < mdr->numTags ; i++)
+ {
+ tag->boneIndex = LittleLong(curtag->boneIndex);
+ Q_strncpyz(tag->name, curtag->name, sizeof(tag->name));
+
+ tag++;
+ curtag++;
+ }
+
+ // And finally we know the real offset to the end.
+ mdr->ofsEnd = (int)((byte *) tag - (byte *) mdr);
+
+ // phew! we're done.
+
+ return qtrue;
+}
+#endif
+
+/*
+=================
+R_LoadMD4
+=================
+*/
+
+static qboolean R_LoadMD4( model_t *mod, void *buffer, const char *mod_name ) {
+ int i, j, k, lodindex;
+ md4Header_t *pinmodel, *md4;
+ md4Frame_t *frame;
+ md4LOD_t *lod;
+ md4Surface_t *surf;
+ md4Triangle_t *tri;
+ md4Vertex_t *v;
+ int version;
+ int size;
+ shader_t *sh;
+ int frameSize;
+
+ pinmodel = (md4Header_t *)buffer;
+
+ version = LittleLong (pinmodel->version);
+ if (version != MD4_VERSION) {
+ ri.Printf( PRINT_WARNING, "R_LoadMD4: %s has wrong version (%i should be %i)\n",
+ mod_name, version, MD4_VERSION);
+ return qfalse;
+ }
+
+ mod->type = MOD_MD4;
+ size = LittleLong(pinmodel->ofsEnd);
+ mod->dataSize += size;
+ md4 = mod->md4 = ri.Hunk_Alloc( size, h_low );
+
+ Com_Memcpy(md4, buffer, size);
+
+ LL(md4->ident);
+ LL(md4->version);
+ LL(md4->numFrames);
+ LL(md4->numBones);
+ LL(md4->numLODs);
+ LL(md4->ofsFrames);
+ LL(md4->ofsLODs);
+ md4->ofsEnd = size;
+
+ if ( md4->numFrames < 1 ) {
+ ri.Printf( PRINT_WARNING, "R_LoadMD4: %s has no frames\n", mod_name );
+ return qfalse;
+ }
+
+ // we don't need to swap tags in the renderer, they aren't used
+
+ // swap all the frames
+ frameSize = (size_t)( &((md4Frame_t *)0)->bones[ md4->numBones ] );
+ for ( i = 0 ; i < md4->numFrames ; i++, frame++) {
+ frame = (md4Frame_t *) ( (byte *)md4 + md4->ofsFrames + i * frameSize );
+ frame->radius = LittleFloat( frame->radius );
+ for ( j = 0 ; j < 3 ; j++ ) {
+ frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] );
+ frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] );
+ frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] );
+ }
+ for ( j = 0 ; j < md4->numBones * sizeof( md4Bone_t ) / 4 ; j++ ) {
+ ((float *)frame->bones)[j] = LittleFloat( ((float *)frame->bones)[j] );
+ }
+ }
+
+ // swap all the LOD's
+ lod = (md4LOD_t *) ( (byte *)md4 + md4->ofsLODs );
+ for ( lodindex = 0 ; lodindex < md4->numLODs ; lodindex++ ) {
+
+ // swap all the surfaces
+ surf = (md4Surface_t *) ( (byte *)lod + lod->ofsSurfaces );
+ for ( i = 0 ; i < lod->numSurfaces ; i++) {
+ LL(surf->ident);
+ LL(surf->numTriangles);
+ LL(surf->ofsTriangles);
+ LL(surf->numVerts);
+ LL(surf->ofsVerts);
+ LL(surf->ofsEnd);
+
+ if ( surf->numVerts > SHADER_MAX_VERTEXES ) {
+ ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i verts on a surface (%i)",
+ mod_name, SHADER_MAX_VERTEXES, surf->numVerts );
+ }
+ if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) {
+ ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i triangles on a surface (%i)",
+ mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles );
+ }
+
+ // change to surface identifier
+ surf->ident = SF_MD4;
+
+ // lowercase the surface name so skin compares are faster
+ Q_strlwr( surf->name );
+
+ // register the shaders
+ sh = R_FindShader( surf->shader, LIGHTMAP_NONE, qtrue );
+ if ( sh->defaultShader ) {
+ surf->shaderIndex = 0;
+ } else {
+ surf->shaderIndex = sh->index;
+ }
+
+ // swap all the triangles
+ tri = (md4Triangle_t *) ( (byte *)surf + surf->ofsTriangles );
+ for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) {
+ LL(tri->indexes[0]);
+ LL(tri->indexes[1]);
+ LL(tri->indexes[2]);
+ }
+
+ // swap all the vertexes
+ // FIXME
+ // This makes TFC's skeletons work. Shouldn't be necessary anymore, but left
+ // in for reference.
+ //v = (md4Vertex_t *) ( (byte *)surf + surf->ofsVerts + 12);
+ v = (md4Vertex_t *) ( (byte *)surf + surf->ofsVerts);
+ for ( j = 0 ; j < surf->numVerts ; j++ ) {
+ v->normal[0] = LittleFloat( v->normal[0] );
+ v->normal[1] = LittleFloat( v->normal[1] );
+ v->normal[2] = LittleFloat( v->normal[2] );
+
+ v->texCoords[0] = LittleFloat( v->texCoords[0] );
+ v->texCoords[1] = LittleFloat( v->texCoords[1] );
+
+ v->numWeights = LittleLong( v->numWeights );
+
+ for ( k = 0 ; k < v->numWeights ; k++ ) {
+ v->weights[k].boneIndex = LittleLong( v->weights[k].boneIndex );
+ v->weights[k].boneWeight = LittleFloat( v->weights[k].boneWeight );
+ v->weights[k].offset[0] = LittleFloat( v->weights[k].offset[0] );
+ v->weights[k].offset[1] = LittleFloat( v->weights[k].offset[1] );
+ v->weights[k].offset[2] = LittleFloat( v->weights[k].offset[2] );
+ }
+ // FIXME
+ // This makes TFC's skeletons work. Shouldn't be necessary anymore, but left
+ // in for reference.
+ //v = (md4Vertex_t *)( ( byte * )&v->weights[v->numWeights] + 12 );
+ v = (md4Vertex_t *)( ( byte * )&v->weights[v->numWeights]);
+ }
+
+ // find the next surface
+ surf = (md4Surface_t *)( (byte *)surf + surf->ofsEnd );
+ }
+
+ // find the next LOD
+ lod = (md4LOD_t *)( (byte *)lod + lod->ofsEnd );
+ }
+
+ return qtrue;
+}
+
+
+
+//=============================================================================
+
+/*
+** RE_BeginRegistration
+*/
+void RE_BeginRegistration( glconfig_t *glconfigOut ) {
+
+ R_Init();
+
+ *glconfigOut = glConfig;
+
+ R_SyncRenderThread();
+
+ tr.viewCluster = -1; // force markleafs to regenerate
+ R_ClearFlares();
+ RE_ClearScene();
+
+ tr.registered = qtrue;
+
+ // NOTE: this sucks, for some reason the first stretch pic is never drawn
+ // without this we'd see a white flash on a level load because the very
+ // first time the level shot would not be drawn
+// RE_StretchPic(0, 0, 0, 0, 0, 0, 1, 1, 0);
+}
+
+//=============================================================================
+
+/*
+===============
+R_ModelInit
+===============
+*/
+void R_ModelInit( void ) {
+ model_t *mod;
+
+ // leave a space for NULL model
+ tr.numModels = 0;
+
+ mod = R_AllocModel();
+ mod->type = MOD_BAD;
+}
+
+
+/*
+================
+R_Modellist_f
+================
+*/
+void R_Modellist_f( void ) {
+ int i, j;
+ model_t *mod;
+ int total;
+ int lods;
+
+ total = 0;
+ for ( i = 1 ; i < tr.numModels; i++ ) {
+ mod = tr.models[i];
+ lods = 1;
+ for ( j = 1 ; j < MD3_MAX_LODS ; j++ ) {
+ if ( mod->md3[j] && mod->md3[j] != mod->md3[j-1] ) {
+ lods++;
+ }
+ }
+ ri.Printf( PRINT_ALL, "%8i : (%i) %s\n",mod->dataSize, lods, mod->name );
+ total += mod->dataSize;
+ }
+ ri.Printf( PRINT_ALL, "%8i : Total models\n", total );
+
+#if 0 // not working right with new hunk
+ if ( tr.world ) {
+ ri.Printf( PRINT_ALL, "\n%8i : %s\n", tr.world->dataSize, tr.world->name );
+ }
+#endif
+}
+
+
+//=============================================================================
+
+
+/*
+================
+R_GetTag
+================
+*/
+static md3Tag_t *R_GetTag( md3Header_t *mod, int frame, const char *tagName ) {
+ md3Tag_t *tag;
+ int i;
+
+ if ( frame >= mod->numFrames ) {
+ // it is possible to have a bad frame while changing models, so don't error
+ frame = mod->numFrames - 1;
+ }
+
+ tag = (md3Tag_t *)((byte *)mod + mod->ofsTags) + frame * mod->numTags;
+ for ( i = 0 ; i < mod->numTags ; i++, tag++ ) {
+ if ( !strcmp( tag->name, tagName ) ) {
+ return tag; // found it
+ }
+ }
+
+ return NULL;
+}
+
+#ifdef RAVENMD4
+void R_GetAnimTag( mdrHeader_t *mod, int framenum, const char *tagName, md3Tag_t * dest)
+{
+ int i, j, k;
+ int frameSize;
+ mdrFrame_t *frame;
+ mdrTag_t *tag;
+
+ if ( framenum >= mod->numFrames )
+ {
+ // it is possible to have a bad frame while changing models, so don't error
+ framenum = mod->numFrames - 1;
+ }
+
+ tag = (mdrTag_t *)((byte *)mod + mod->ofsTags);
+ for ( i = 0 ; i < mod->numTags ; i++, tag++ )
+ {
+ if ( !strcmp( tag->name, tagName ) )
+ {
+ Q_strncpyz(dest->name, tag->name, sizeof(dest->name));
+
+ // uncompressed model...
+ //
+ frameSize = (long)( &((mdrFrame_t *)0)->bones[ mod->numBones ] );
+ frame = (mdrFrame_t *)((byte *)mod + mod->ofsFrames + framenum * frameSize );
+
+ for (j = 0; j < 3; j++)
+ {
+ for (k = 0; k < 3; k++)
+ dest->axis[j][k]=frame->bones[tag->boneIndex].matrix[k][j];
+ }
+
+ dest->origin[0]=frame->bones[tag->boneIndex].matrix[0][3];
+ dest->origin[1]=frame->bones[tag->boneIndex].matrix[1][3];
+ dest->origin[2]=frame->bones[tag->boneIndex].matrix[2][3];
+
+ return;
+ }
+ }
+
+ AxisClear( dest->axis );
+ VectorClear( dest->origin );
+ strcpy(dest->name,"");
+}
+#endif
+
+/*
+================
+R_LerpTag
+================
+*/
+int R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame,
+ float frac, const char *tagName ) {
+ md3Tag_t *start, *end;
+#ifdef RAVENMD4
+ md3Tag_t start_space, end_space;
+#endif
+ int i;
+ float frontLerp, backLerp;
+ model_t *model;
+
+ model = R_GetModelByHandle( handle );
+ if ( !model->md3[0] )
+ {
+#ifdef RAVENMD4
+ if(model->md4)
+ {
+ start = &start_space;
+ end = &end_space;
+ R_GetAnimTag((mdrHeader_t *) model->md4, startFrame, tagName, start);
+ R_GetAnimTag((mdrHeader_t *) model->md4, endFrame, tagName, end);
+ }
+ else
+#endif
+ {
+
+ AxisClear( tag->axis );
+ VectorClear( tag->origin );
+ return qfalse;
+
+ }
+ }
+ else
+ {
+ start = R_GetTag( model->md3[0], startFrame, tagName );
+ end = R_GetTag( model->md3[0], endFrame, tagName );
+ if ( !start || !end ) {
+ AxisClear( tag->axis );
+ VectorClear( tag->origin );
+ return qfalse;
+ }
+ }
+
+ frontLerp = frac;
+ backLerp = 1.0f - frac;
+
+ for ( i = 0 ; i < 3 ; i++ ) {
+ tag->origin[i] = start->origin[i] * backLerp + end->origin[i] * frontLerp;
+ tag->axis[0][i] = start->axis[0][i] * backLerp + end->axis[0][i] * frontLerp;
+ tag->axis[1][i] = start->axis[1][i] * backLerp + end->axis[1][i] * frontLerp;
+ tag->axis[2][i] = start->axis[2][i] * backLerp + end->axis[2][i] * frontLerp;
+ }
+ VectorNormalize( tag->axis[0] );
+ VectorNormalize( tag->axis[1] );
+ VectorNormalize( tag->axis[2] );
+ return qtrue;
+}
+
+
+/*
+====================
+R_ModelBounds
+====================
+*/
+void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ) {
+ model_t *model;
+ md3Header_t *header;
+ md3Frame_t *frame;
+
+ model = R_GetModelByHandle( handle );
+
+ if ( model->bmodel ) {
+ VectorCopy( model->bmodel->bounds[0], mins );
+ VectorCopy( model->bmodel->bounds[1], maxs );
+ return;
+ }
+
+ if ( !model->md3[0] ) {
+ VectorClear( mins );
+ VectorClear( maxs );
+ return;
+ }
+
+ header = model->md3[0];
+
+ frame = (md3Frame_t *)( (byte *)header + header->ofsFrames );
+
+ VectorCopy( frame->bounds[0], mins );
+ VectorCopy( frame->bounds[1], maxs );
+}
+
diff --git a/code/renderer/tr_noise.c b/code/renderer/tr_noise.c
new file mode 100644
index 0000000..af2f84e
--- /dev/null
+++ b/code/renderer/tr_noise.c
@@ -0,0 +1,93 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// tr_noise.c
+#include "tr_local.h"
+
+#define NOISE_SIZE 256
+#define NOISE_MASK ( NOISE_SIZE - 1 )
+
+#define VAL( a ) s_noise_perm[ ( a ) & ( NOISE_MASK )]
+#define INDEX( x, y, z, t ) VAL( x + VAL( y + VAL( z + VAL( t ) ) ) )
+
+static float s_noise_table[NOISE_SIZE];
+static int s_noise_perm[NOISE_SIZE];
+
+#define LERP( a, b, w ) ( a * ( 1.0f - w ) + b * w )
+
+static float GetNoiseValue( int x, int y, int z, int t )
+{
+ int index = INDEX( ( int ) x, ( int ) y, ( int ) z, ( int ) t );
+
+ return s_noise_table[index];
+}
+
+void R_NoiseInit( void )
+{
+ int i;
+
+ for ( i = 0; i < NOISE_SIZE; i++ )
+ {
+ s_noise_table[i] = ( float ) ( ( ( rand() / ( float ) RAND_MAX ) * 2.0 - 1.0 ) );
+ s_noise_perm[i] = ( unsigned char ) ( rand() / ( float ) RAND_MAX * 255 );
+ }
+}
+
+float R_NoiseGet4f( float x, float y, float z, float t )
+{
+ int i;
+ int ix, iy, iz, it;
+ float fx, fy, fz, ft;
+ float front[4];
+ float back[4];
+ float fvalue, bvalue, value[2], finalvalue;
+
+ ix = ( int ) floor( x );
+ fx = x - ix;
+ iy = ( int ) floor( y );
+ fy = y - iy;
+ iz = ( int ) floor( z );
+ fz = z - iz;
+ it = ( int ) floor( t );
+ ft = t - it;
+
+ for ( i = 0; i < 2; i++ )
+ {
+ front[0] = GetNoiseValue( ix, iy, iz, it + i );
+ front[1] = GetNoiseValue( ix+1, iy, iz, it + i );
+ front[2] = GetNoiseValue( ix, iy+1, iz, it + i );
+ front[3] = GetNoiseValue( ix+1, iy+1, iz, it + i );
+
+ back[0] = GetNoiseValue( ix, iy, iz + 1, it + i );
+ back[1] = GetNoiseValue( ix+1, iy, iz + 1, it + i );
+ back[2] = GetNoiseValue( ix, iy+1, iz + 1, it + i );
+ back[3] = GetNoiseValue( ix+1, iy+1, iz + 1, it + i );
+
+ fvalue = LERP( LERP( front[0], front[1], fx ), LERP( front[2], front[3], fx ), fy );
+ bvalue = LERP( LERP( back[0], back[1], fx ), LERP( back[2], back[3], fx ), fy );
+
+ value[i] = LERP( fvalue, bvalue, fz );
+ }
+
+ finalvalue = LERP( value[0], value[1], ft );
+
+ return finalvalue;
+}
diff --git a/code/renderer/tr_public.h b/code/renderer/tr_public.h
new file mode 100644
index 0000000..c6846a9
--- /dev/null
+++ b/code/renderer/tr_public.h
@@ -0,0 +1,171 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#ifndef __TR_PUBLIC_H
+#define __TR_PUBLIC_H
+
+#include "tr_types.h"
+
+#define REF_API_VERSION 8
+
+//
+// these are the functions exported by the refresh module
+//
+typedef struct {
+ // called before the library is unloaded
+ // if the system is just reconfiguring, pass destroyWindow = qfalse,
+ // which will keep the screen from flashing to the desktop.
+ void (*Shutdown)( qboolean destroyWindow );
+
+ // All data that will be used in a level should be
+ // registered before rendering any frames to prevent disk hits,
+ // but they can still be registered at a later time
+ // if necessary.
+ //
+ // BeginRegistration makes any existing media pointers invalid
+ // and returns the current gl configuration, including screen width
+ // and height, which can be used by the client to intelligently
+ // size display elements
+ void (*BeginRegistration)( glconfig_t *config );
+ qhandle_t (*RegisterModel)( const char *name );
+ qhandle_t (*RegisterSkin)( const char *name );
+ qhandle_t (*RegisterShader)( const char *name );
+ qhandle_t (*RegisterShaderNoMip)( const char *name );
+ void (*LoadWorld)( const char *name );
+
+ // the vis data is a large enough block of data that we go to the trouble
+ // of sharing it with the clipmodel subsystem
+ void (*SetWorldVisData)( const byte *vis );
+
+ // EndRegistration will draw a tiny polygon with each texture, forcing
+ // them to be loaded into card memory
+ void (*EndRegistration)( void );
+
+ // a scene is built up by calls to R_ClearScene and the various R_Add functions.
+ // Nothing is drawn until R_RenderScene is called.
+ void (*ClearScene)( void );
+ void (*AddRefEntityToScene)( const refEntity_t *re );
+ void (*AddPolyToScene)( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num );
+ int (*LightForPoint)( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir );
+ void (*AddLightToScene)( const vec3_t org, float intensity, float r, float g, float b );
+ void (*AddAdditiveLightToScene)( const vec3_t org, float intensity, float r, float g, float b );
+ void (*RenderScene)( const refdef_t *fd );
+
+ void (*SetColor)( const float *rgba ); // NULL = 1,1,1,1
+ void (*DrawStretchPic) ( float x, float y, float w, float h,
+ float s1, float t1, float s2, float t2, qhandle_t hShader ); // 0 = white
+
+ // Draw images for cinematic rendering, pass as 32 bit rgba
+ void (*DrawStretchRaw) (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty);
+ void (*UploadCinematic) (int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty);
+
+ void (*BeginFrame)( stereoFrame_t stereoFrame );
+
+ // if the pointers are not NULL, timing info will be returned
+ void (*EndFrame)( int *frontEndMsec, int *backEndMsec );
+
+
+ int (*MarkFragments)( int numPoints, const vec3_t *points, const vec3_t projection,
+ int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer );
+
+ int (*LerpTag)( orientation_t *tag, qhandle_t model, int startFrame, int endFrame,
+ float frac, const char *tagName );
+ void (*ModelBounds)( qhandle_t model, vec3_t mins, vec3_t maxs );
+
+#ifdef __USEA3D
+ void (*A3D_RenderGeometry) (void *pVoidA3D, void *pVoidGeom, void *pVoidMat, void *pVoidGeomStatus);
+#endif
+ void (*RegisterFont)(const char *fontName, int pointSize, fontInfo_t *font);
+ void (*RemapShader)(const char *oldShader, const char *newShader, const char *offsetTime);
+ qboolean (*GetEntityToken)( char *buffer, int size );
+ qboolean (*inPVS)( const vec3_t p1, const vec3_t p2 );
+
+ void (*TakeVideoFrame)( int h, int w, byte* captureBuffer, byte *encodeBuffer, qboolean motionJpeg );
+} refexport_t;
+
+//
+// these are the functions imported by the refresh module
+//
+typedef struct {
+ // print message on the local console
+ void (QDECL *Printf)( int printLevel, const char *fmt, ...);
+
+ // abort the game
+ void (QDECL *Error)( int errorLevel, const char *fmt, ...);
+
+ // milliseconds should only be used for profiling, never
+ // for anything game related. Get time from the refdef
+ int (*Milliseconds)( void );
+
+ // stack based memory allocation for per-level things that
+ // won't be freed
+#ifdef HUNK_DEBUG
+ void *(*Hunk_AllocDebug)( int size, ha_pref pref, char *label, char *file, int line );
+#else
+ void *(*Hunk_Alloc)( int size, ha_pref pref );
+#endif
+ void *(*Hunk_AllocateTempMemory)( int size );
+ void (*Hunk_FreeTempMemory)( void *block );
+
+ // dynamic memory allocator for things that need to be freed
+ void *(*Malloc)( int bytes );
+ void (*Free)( void *buf );
+
+ cvar_t *(*Cvar_Get)( const char *name, const char *value, int flags );
+ void (*Cvar_Set)( const char *name, const char *value );
+ void (*Cvar_CheckRange)( cvar_t *cv, float minVal, float maxVal, qboolean shouldBeIntegral );
+
+ void (*Cmd_AddCommand)( const char *name, void(*cmd)(void) );
+ void (*Cmd_RemoveCommand)( const char *name );
+
+ int (*Cmd_Argc) (void);
+ char *(*Cmd_Argv) (int i);
+
+ void (*Cmd_ExecuteText) (int exec_when, const char *text);
+
+ // visualization for debugging collision detection
+ void (*CM_DrawDebugSurface)( void (*drawPoly)(int color, int numPoints, float *points) );
+
+ // a -1 return means the file does not exist
+ // NULL can be passed for buf to just determine existance
+ int (*FS_FileIsInPAK)( const char *name, int *pCheckSum );
+ int (*FS_ReadFile)( const char *name, void **buf );
+ void (*FS_FreeFile)( void *buf );
+ char ** (*FS_ListFiles)( const char *name, const char *extension, int *numfilesfound );
+ void (*FS_FreeFileList)( char **filelist );
+ void (*FS_WriteFile)( const char *qpath, const void *buffer, int size );
+ qboolean (*FS_FileExists)( const char *file );
+
+ // cinematic stuff
+ void (*CIN_UploadCinematic)(int handle);
+ int (*CIN_PlayCinematic)( const char *arg0, int xpos, int ypos, int width, int height, int bits);
+ e_status (*CIN_RunCinematic) (int handle);
+
+ void (*CL_WriteAVIVideoFrame)( const byte *buffer, int size );
+} refimport_t;
+
+
+// this is the only function actually exported at the linker level
+// If the module can't init to a valid rendering state, NULL will be
+// returned.
+refexport_t*GetRefAPI( int apiVersion, refimport_t *rimp );
+
+#endif // __TR_PUBLIC_H
diff --git a/code/renderer/tr_scene.c b/code/renderer/tr_scene.c
new file mode 100644
index 0000000..970ac33
--- /dev/null
+++ b/code/renderer/tr_scene.c
@@ -0,0 +1,418 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "tr_local.h"
+
+int r_firstSceneDrawSurf;
+
+int r_numdlights;
+int r_firstSceneDlight;
+
+int r_numentities;
+int r_firstSceneEntity;
+
+int r_numpolys;
+int r_firstScenePoly;
+
+int r_numpolyverts;
+
+
+/*
+====================
+R_ToggleSmpFrame
+
+====================
+*/
+void R_ToggleSmpFrame( void ) {
+ if ( r_smp->integer ) {
+ // use the other buffers next frame, because another CPU
+ // may still be rendering into the current ones
+ tr.smpFrame ^= 1;
+ } else {
+ tr.smpFrame = 0;
+ }
+
+ backEndData[tr.smpFrame]->commands.used = 0;
+
+ r_firstSceneDrawSurf = 0;
+
+ r_numdlights = 0;
+ r_firstSceneDlight = 0;
+
+ r_numentities = 0;
+ r_firstSceneEntity = 0;
+
+ r_numpolys = 0;
+ r_firstScenePoly = 0;
+
+ r_numpolyverts = 0;
+}
+
+
+/*
+====================
+RE_ClearScene
+
+====================
+*/
+void RE_ClearScene( void ) {
+ r_firstSceneDlight = r_numdlights;
+ r_firstSceneEntity = r_numentities;
+ r_firstScenePoly = r_numpolys;
+}
+
+/*
+===========================================================================
+
+DISCRETE POLYS
+
+===========================================================================
+*/
+
+/*
+=====================
+R_AddPolygonSurfaces
+
+Adds all the scene's polys into this view's drawsurf list
+=====================
+*/
+void R_AddPolygonSurfaces( void ) {
+ int i;
+ shader_t *sh;
+ srfPoly_t *poly;
+
+ tr.currentEntityNum = ENTITYNUM_WORLD;
+ tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT;
+
+ for ( i = 0, poly = tr.refdef.polys; i < tr.refdef.numPolys ; i++, poly++ ) {
+ sh = R_GetShaderByHandle( poly->hShader );
+ R_AddDrawSurf( ( void * )poly, sh, poly->fogIndex, qfalse );
+ }
+}
+
+/*
+=====================
+RE_AddPolyToScene
+
+=====================
+*/
+void RE_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts, int numPolys ) {
+ srfPoly_t *poly;
+ int i, j;
+ int fogIndex;
+ fog_t *fog;
+ vec3_t bounds[2];
+
+ if ( !tr.registered ) {
+ return;
+ }
+
+ if ( !hShader ) {
+ ri.Printf( PRINT_WARNING, "WARNING: RE_AddPolyToScene: NULL poly shader\n");
+ return;
+ }
+
+ for ( j = 0; j < numPolys; j++ ) {
+ if ( r_numpolyverts + numVerts > max_polyverts || r_numpolys >= max_polys ) {
+ /*
+ NOTE TTimo this was initially a PRINT_WARNING
+ but it happens a lot with high fighting scenes and particles
+ since we don't plan on changing the const and making for room for those effects
+ simply cut this message to developer only
+ */
+ ri.Printf( PRINT_DEVELOPER, "WARNING: RE_AddPolyToScene: r_max_polys or r_max_polyverts reached\n");
+ return;
+ }
+
+ poly = &backEndData[tr.smpFrame]->polys[r_numpolys];
+ poly->surfaceType = SF_POLY;
+ poly->hShader = hShader;
+ poly->numVerts = numVerts;
+ poly->verts = &backEndData[tr.smpFrame]->polyVerts[r_numpolyverts];
+
+ Com_Memcpy( poly->verts, &verts[numVerts*j], numVerts * sizeof( *verts ) );
+
+ if ( glConfig.hardwareType == GLHW_RAGEPRO ) {
+ poly->verts->modulate[0] = 255;
+ poly->verts->modulate[1] = 255;
+ poly->verts->modulate[2] = 255;
+ poly->verts->modulate[3] = 255;
+ }
+ // done.
+ r_numpolys++;
+ r_numpolyverts += numVerts;
+
+ // if no world is loaded
+ if ( tr.world == NULL ) {
+ fogIndex = 0;
+ }
+ // see if it is in a fog volume
+ else if ( tr.world->numfogs == 1 ) {
+ fogIndex = 0;
+ } else {
+ // find which fog volume the poly is in
+ VectorCopy( poly->verts[0].xyz, bounds[0] );
+ VectorCopy( poly->verts[0].xyz, bounds[1] );
+ for ( i = 1 ; i < poly->numVerts ; i++ ) {
+ AddPointToBounds( poly->verts[i].xyz, bounds[0], bounds[1] );
+ }
+ for ( fogIndex = 1 ; fogIndex < tr.world->numfogs ; fogIndex++ ) {
+ fog = &tr.world->fogs[fogIndex];
+ if ( bounds[1][0] >= fog->bounds[0][0]
+ && bounds[1][1] >= fog->bounds[0][1]
+ && bounds[1][2] >= fog->bounds[0][2]
+ && bounds[0][0] <= fog->bounds[1][0]
+ && bounds[0][1] <= fog->bounds[1][1]
+ && bounds[0][2] <= fog->bounds[1][2] ) {
+ break;
+ }
+ }
+ if ( fogIndex == tr.world->numfogs ) {
+ fogIndex = 0;
+ }
+ }
+ poly->fogIndex = fogIndex;
+ }
+}
+
+
+//=================================================================================
+
+
+/*
+=====================
+RE_AddRefEntityToScene
+
+=====================
+*/
+void RE_AddRefEntityToScene( const refEntity_t *ent ) {
+ if ( !tr.registered ) {
+ return;
+ }
+ if ( r_numentities >= MAX_ENTITIES ) {
+ return;
+ }
+ if ( Q_isnan(ent->origin[0]) || Q_isnan(ent->origin[1]) || Q_isnan(ent->origin[2]) ) {
+ static qboolean firstTime = qtrue;
+ if (firstTime) {
+ firstTime = qfalse;
+ Com_DPrintf(S_COLOR_YELLOW "WARNING: RE_AddRefEntityToScene passed a refEntity which has an origin with a NaN component\n");
+ }
+ return;
+ }
+ if ( ent->reType < 0 || ent->reType >= RT_MAX_REF_ENTITY_TYPE ) {
+ ri.Error( ERR_DROP, "RE_AddRefEntityToScene: bad reType %i", ent->reType );
+ }
+
+ backEndData[tr.smpFrame]->entities[r_numentities].e = *ent;
+ backEndData[tr.smpFrame]->entities[r_numentities].lightingCalculated = qfalse;
+
+ r_numentities++;
+}
+
+
+/*
+=====================
+RE_AddDynamicLightToScene
+
+=====================
+*/
+void RE_AddDynamicLightToScene( const vec3_t org, float intensity, float r, float g, float b, int additive ) {
+ dlight_t *dl;
+
+ if ( !tr.registered ) {
+ return;
+ }
+ if ( r_numdlights >= MAX_DLIGHTS ) {
+ return;
+ }
+ if ( intensity <= 0 ) {
+ return;
+ }
+ // these cards don't have the correct blend mode
+ if ( glConfig.hardwareType == GLHW_RIVA128 || glConfig.hardwareType == GLHW_PERMEDIA2 ) {
+ return;
+ }
+ dl = &backEndData[tr.smpFrame]->dlights[r_numdlights++];
+ VectorCopy (org, dl->origin);
+ dl->radius = intensity;
+ dl->color[0] = r;
+ dl->color[1] = g;
+ dl->color[2] = b;
+ dl->additive = additive;
+}
+
+/*
+=====================
+RE_AddLightToScene
+
+=====================
+*/
+void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) {
+ RE_AddDynamicLightToScene( org, intensity, r, g, b, qfalse );
+}
+
+/*
+=====================
+RE_AddAdditiveLightToScene
+
+=====================
+*/
+void RE_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) {
+ RE_AddDynamicLightToScene( org, intensity, r, g, b, qtrue );
+}
+
+/*
+@@@@@@@@@@@@@@@@@@@@@
+RE_RenderScene
+
+Draw a 3D view into a part of the window, then return
+to 2D drawing.
+
+Rendering a scene may require multiple views to be rendered
+to handle mirrors,
+@@@@@@@@@@@@@@@@@@@@@
+*/
+void RE_RenderScene( const refdef_t *fd ) {
+ viewParms_t parms;
+ int startTime;
+
+ if ( !tr.registered ) {
+ return;
+ }
+ GLimp_LogComment( "====== RE_RenderScene =====\n" );
+
+ if ( r_norefresh->integer ) {
+ return;
+ }
+
+ startTime = ri.Milliseconds();
+
+ if (!tr.world && !( fd->rdflags & RDF_NOWORLDMODEL ) ) {
+ ri.Error (ERR_DROP, "R_RenderScene: NULL worldmodel");
+ }
+
+ Com_Memcpy( tr.refdef.text, fd->text, sizeof( tr.refdef.text ) );
+
+ tr.refdef.x = fd->x;
+ tr.refdef.y = fd->y;
+ tr.refdef.width = fd->width;
+ tr.refdef.height = fd->height;
+ tr.refdef.fov_x = fd->fov_x;
+ tr.refdef.fov_y = fd->fov_y;
+
+ VectorCopy( fd->vieworg, tr.refdef.vieworg );
+ VectorCopy( fd->viewaxis[0], tr.refdef.viewaxis[0] );
+ VectorCopy( fd->viewaxis[1], tr.refdef.viewaxis[1] );
+ VectorCopy( fd->viewaxis[2], tr.refdef.viewaxis[2] );
+
+ tr.refdef.time = fd->time;
+ tr.refdef.rdflags = fd->rdflags;
+
+ // copy the areamask data over and note if it has changed, which
+ // will force a reset of the visible leafs even if the view hasn't moved
+ tr.refdef.areamaskModified = qfalse;
+ if ( ! (tr.refdef.rdflags & RDF_NOWORLDMODEL) ) {
+ int areaDiff;
+ int i;
+
+ // compare the area bits
+ areaDiff = 0;
+ for (i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++) {
+ areaDiff |= ((int *)tr.refdef.areamask)[i] ^ ((int *)fd->areamask)[i];
+ ((int *)tr.refdef.areamask)[i] = ((int *)fd->areamask)[i];
+ }
+
+ if ( areaDiff ) {
+ // a door just opened or something
+ tr.refdef.areamaskModified = qtrue;
+ }
+ }
+
+
+ // derived info
+
+ tr.refdef.floatTime = tr.refdef.time * 0.001f;
+
+ tr.refdef.numDrawSurfs = r_firstSceneDrawSurf;
+ tr.refdef.drawSurfs = backEndData[tr.smpFrame]->drawSurfs;
+
+ tr.refdef.num_entities = r_numentities - r_firstSceneEntity;
+ tr.refdef.entities = &backEndData[tr.smpFrame]->entities[r_firstSceneEntity];
+
+ tr.refdef.num_dlights = r_numdlights - r_firstSceneDlight;
+ tr.refdef.dlights = &backEndData[tr.smpFrame]->dlights[r_firstSceneDlight];
+
+ tr.refdef.numPolys = r_numpolys - r_firstScenePoly;
+ tr.refdef.polys = &backEndData[tr.smpFrame]->polys[r_firstScenePoly];
+
+ // turn off dynamic lighting globally by clearing all the
+ // dlights if it needs to be disabled or if vertex lighting is enabled
+ if ( r_dynamiclight->integer == 0 ||
+ r_vertexLight->integer == 1 ||
+ glConfig.hardwareType == GLHW_PERMEDIA2 ) {
+ tr.refdef.num_dlights = 0;
+ }
+
+ // a single frame may have multiple scenes draw inside it --
+ // a 3D game view, 3D status bar renderings, 3D menus, etc.
+ // They need to be distinguished by the light flare code, because
+ // the visibility state for a given surface may be different in
+ // each scene / view.
+ tr.frameSceneNum++;
+ tr.sceneCount++;
+
+ // setup view parms for the initial view
+ //
+ // set up viewport
+ // The refdef takes 0-at-the-top y coordinates, so
+ // convert to GL's 0-at-the-bottom space
+ //
+ Com_Memset( &parms, 0, sizeof( parms ) );
+ parms.viewportX = tr.refdef.x;
+ parms.viewportY = glConfig.vidHeight - ( tr.refdef.y + tr.refdef.height );
+ parms.viewportWidth = tr.refdef.width;
+ parms.viewportHeight = tr.refdef.height;
+ parms.isPortal = qfalse;
+
+ parms.fovX = tr.refdef.fov_x;
+ parms.fovY = tr.refdef.fov_y;
+
+ parms.stereoFrame = tr.refdef.stereoFrame;
+
+ VectorCopy( fd->vieworg, parms.or.origin );
+ VectorCopy( fd->viewaxis[0], parms.or.axis[0] );
+ VectorCopy( fd->viewaxis[1], parms.or.axis[1] );
+ VectorCopy( fd->viewaxis[2], parms.or.axis[2] );
+
+ VectorCopy( fd->vieworg, parms.pvsOrigin );
+
+ R_RenderView( &parms );
+
+ // the next scene rendered in this frame will tack on after this one
+ r_firstSceneDrawSurf = tr.refdef.numDrawSurfs;
+ r_firstSceneEntity = r_numentities;
+ r_firstSceneDlight = r_numdlights;
+ r_firstScenePoly = r_numpolys;
+
+ tr.frontEndMsec += ri.Milliseconds() - startTime;
+}
diff --git a/code/renderer/tr_shade.c b/code/renderer/tr_shade.c
new file mode 100644
index 0000000..49c3690
--- /dev/null
+++ b/code/renderer/tr_shade.c
@@ -0,0 +1,1548 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// tr_shade.c
+
+#include "tr_local.h"
+#if idppc_altivec && !defined(MACOS_X)
+#include <altivec.h>
+#endif
+
+/*
+
+ THIS ENTIRE FILE IS BACK END
+
+ This file deals with applying shaders to surface data in the tess struct.
+*/
+#ifndef PANDORA
+/*
+================
+R_ArrayElementDiscrete
+
+This is just for OpenGL conformance testing, it should never be the fastest
+================
+*/
+static void APIENTRY R_ArrayElementDiscrete( GLint index ) {
+ qglColor4ubv( tess.svars.colors[ index ] );
+ if ( glState.currenttmu ) {
+ qglMultiTexCoord2fARB( 0, tess.svars.texcoords[ 0 ][ index ][0], tess.svars.texcoords[ 0 ][ index ][1] );
+ qglMultiTexCoord2fARB( 1, tess.svars.texcoords[ 1 ][ index ][0], tess.svars.texcoords[ 1 ][ index ][1] );
+ } else {
+ qglTexCoord2fv( tess.svars.texcoords[ 0 ][ index ] );
+ }
+ qglVertex3fv( tess.xyz[ index ] );
+}
+
+/*
+===================
+R_DrawStripElements
+
+===================
+*/
+static int c_vertexes; // for seeing how long our average strips are
+static int c_begins;
+static void R_DrawStripElements( int numIndexes, const glIndex_t *indexes, void ( APIENTRY *element )(GLint) ) {
+ int i;
+ int last[3] = { -1, -1, -1 };
+ qboolean even;
+
+ c_begins++;
+
+ if ( numIndexes <= 0 ) {
+ return;
+ }
+
+ qglBegin( GL_TRIANGLE_STRIP );
+
+ // prime the strip
+ element( indexes[0] );
+ element( indexes[1] );
+ element( indexes[2] );
+ c_vertexes += 3;
+
+ last[0] = indexes[0];
+ last[1] = indexes[1];
+ last[2] = indexes[2];
+
+ even = qfalse;
+
+ for ( i = 3; i < numIndexes; i += 3 )
+ {
+ // odd numbered triangle in potential strip
+ if ( !even )
+ {
+ // check previous triangle to see if we're continuing a strip
+ if ( ( indexes[i+0] == last[2] ) && ( indexes[i+1] == last[1] ) )
+ {
+ element( indexes[i+2] );
+ c_vertexes++;
+ assert( indexes[i+2] < tess.numVertexes );
+ even = qtrue;
+ }
+ // otherwise we're done with this strip so finish it and start
+ // a new one
+ else
+ {
+ qglEnd();
+
+ qglBegin( GL_TRIANGLE_STRIP );
+ c_begins++;
+
+ element( indexes[i+0] );
+ element( indexes[i+1] );
+ element( indexes[i+2] );
+
+ c_vertexes += 3;
+
+ even = qfalse;
+ }
+ }
+ else
+ {
+ // check previous triangle to see if we're continuing a strip
+ if ( ( last[2] == indexes[i+1] ) && ( last[0] == indexes[i+0] ) )
+ {
+ element( indexes[i+2] );
+ c_vertexes++;
+
+ even = qfalse;
+ }
+ // otherwise we're done with this strip so finish it and start
+ // a new one
+ else
+ {
+ qglEnd();
+
+ qglBegin( GL_TRIANGLE_STRIP );
+ c_begins++;
+
+ element( indexes[i+0] );
+ element( indexes[i+1] );
+ element( indexes[i+2] );
+ c_vertexes += 3;
+
+ even = qfalse;
+ }
+ }
+
+ // cache the last three vertices
+ last[0] = indexes[i+0];
+ last[1] = indexes[i+1];
+ last[2] = indexes[i+2];
+ }
+
+ qglEnd();
+}
+
+
+
+/*
+==================
+R_DrawElements
+
+Optionally performs our own glDrawElements that looks for strip conditions
+instead of using the single glDrawElements call that may be inefficient
+without compiled vertex arrays.
+==================
+*/
+static void R_DrawElements( int numIndexes, const glIndex_t *indexes ) {
+ int primitives;
+
+ primitives = r_primitives->integer;
+
+ // default is to use triangles if compiled vertex arrays are present
+ if ( primitives == 0 ) {
+ if ( qglLockArraysEXT ) {
+ primitives = 2;
+ } else {
+ primitives = 1;
+ }
+ }
+
+
+ if ( primitives == 2 ) {
+ qglDrawElements( GL_TRIANGLES,
+ numIndexes,
+ GL_INDEX_TYPE,
+ indexes );
+ return;
+ }
+
+ if ( primitives == 1 ) {
+ R_DrawStripElements( numIndexes, indexes, qglArrayElement );
+ return;
+ }
+
+ if ( primitives == 3 ) {
+ R_DrawStripElements( numIndexes, indexes, R_ArrayElementDiscrete );
+ return;
+ }
+
+ // anything else will cause no drawing
+}
+#else
+static void R_DrawElements( int numIndexes, const glIndex_t* indexes )
+{
+ qglDrawElements(GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, indexes);
+}
+#endif
+
+/*
+=============================================================
+
+SURFACE SHADERS
+
+=============================================================
+*/
+
+shaderCommands_t tess;
+static qboolean setArraysOnce;
+
+/*
+=================
+R_BindAnimatedImage
+
+=================
+*/
+static void R_BindAnimatedImage( textureBundle_t *bundle ) {
+ int index;
+
+ if ( bundle->isVideoMap ) {
+ ri.CIN_RunCinematic(bundle->videoMapHandle);
+ ri.CIN_UploadCinematic(bundle->videoMapHandle);
+ return;
+ }
+
+ if ( bundle->numImageAnimations <= 1 ) {
+ GL_Bind( bundle->image[0] );
+ return;
+ }
+
+ // it is necessary to do this messy calc to make sure animations line up
+ // exactly with waveforms of the same frequency
+ index = myftol( tess.shaderTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE );
+ index >>= FUNCTABLE_SIZE2;
+
+ if ( index < 0 ) {
+ index = 0; // may happen with shader time offsets
+ }
+ index %= bundle->numImageAnimations;
+
+ GL_Bind( bundle->image[ index ] );
+}
+
+/*
+================
+DrawTris
+
+Draws triangle outlines for debugging
+================
+*/
+static void DrawTris (shaderCommands_t *input) {
+ GL_Bind( tr.whiteImage );
+#ifdef PANDORA
+ glColor4f (1,1,1,1);
+#else
+ qglColor3f (1,1,1);
+#endif
+ GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE );
+#ifdef PANDORA
+ qglDepthRangef( 0, 0 );
+#else
+ qglDepthRange( 0, 0 );
+#endif
+
+ qglDisableClientState (GL_COLOR_ARRAY);
+ qglDisableClientState (GL_TEXTURE_COORD_ARRAY);
+
+ qglVertexPointer (3, GL_FLOAT, 16, input->xyz); // padded for SIMD
+
+ if (qglLockArraysEXT) {
+ qglLockArraysEXT(0, input->numVertexes);
+ GLimp_LogComment( "glLockArraysEXT\n" );
+ }
+
+ R_DrawElements( input->numIndexes, input->indexes );
+
+ if (qglUnlockArraysEXT) {
+ qglUnlockArraysEXT();
+ GLimp_LogComment( "glUnlockArraysEXT\n" );
+ }
+#ifdef PANDORA
+ qglDepthRangef( 0, 1 );
+#else
+ qglDepthRange( 0, 1 );
+#endif
+}
+
+
+/*
+================
+DrawNormals
+
+Draws vertex normals for debugging
+================
+*/
+static void DrawNormals (shaderCommands_t *input) {
+ int i;
+ vec3_t temp;
+#ifdef PANDORA
+ vec3_t verts[2 * SHADER_MAX_VERTEXES];
+ glIndex_t indicies[2 * SHADER_MAX_VERTEXES];
+
+ for (i = 0; i < input->numVertexes; i++) {
+ VectorCopy(input->xyz[i], verts[i * 2]);
+ VectorMA(input->xyz[i], 2, input->normal[i], temp);
+ VectorCopy(temp, verts[(i * 2) + 1]);
+ indicies[(i * 2)] = i * 2;
+ indicies[(i * 2) + 1] = (i * 2) + 1;
+ }
+#endif
+ GL_Bind( tr.whiteImage );
+#ifdef PANDORA
+ glColor4f (1,1,1,1);
+ qglDepthRangef( 0, 0 ); // never occluded
+#else
+ qglColor3f (1,1,1);
+ qglDepthRange( 0, 0 ); // never occluded
+#endif
+ GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE );
+
+#ifdef PANDORA
+ qglVertexPointer(3, GL_FLOAT, 0, verts);
+ qglDrawElements(GL_LINES, i, GL_INDEX_TYPE, indicies);
+
+ qglDepthRangef( 0, 1 );
+#else
+ qglBegin (GL_LINES);
+ for (i = 0 ; i < input->numVertexes ; i++) {
+ qglVertex3fv (input->xyz[i]);
+ VectorMA (input->xyz[i], 2, input->normal[i], temp);
+ qglVertex3fv (temp);
+ }
+ qglEnd ();
+ qglDepthRange( 0, 1 );
+#endif
+}
+
+/*
+==============
+RB_BeginSurface
+
+We must set some things up before beginning any tesselation,
+because a surface may be forced to perform a RB_End due
+to overflow.
+==============
+*/
+void RB_BeginSurface( shader_t *shader, int fogNum ) {
+
+ shader_t *state = (shader->remappedShader) ? shader->remappedShader : shader;
+
+ tess.numIndexes = 0;
+ tess.numVertexes = 0;
+ tess.shader = state;
+ tess.fogNum = fogNum;
+ tess.dlightBits = 0; // will be OR'd in by surface functions
+ tess.xstages = state->stages;
+ tess.numPasses = state->numUnfoggedPasses;
+ tess.currentStageIteratorFunc = state->optimalStageIteratorFunc;
+
+ tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset;
+ if (tess.shader->clampTime && tess.shaderTime >= tess.shader->clampTime) {
+ tess.shaderTime = tess.shader->clampTime;
+ }
+
+
+}
+
+/*
+===================
+DrawMultitextured
+
+output = t0 * t1 or t0 + t1
+
+t0 = most upstream according to spec
+t1 = most downstream according to spec
+===================
+*/
+static void DrawMultitextured( shaderCommands_t *input, int stage ) {
+ shaderStage_t *pStage;
+
+ pStage = tess.xstages[stage];
+
+ GL_State( pStage->stateBits );
+#ifndef PANDORA
+ // this is an ugly hack to work around a GeForce driver
+ // bug with multitexture and clip planes
+ if ( backEnd.viewParms.isPortal ) {
+ qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
+ }
+#endif
+ //
+ // base
+ //
+ GL_SelectTexture( 0 );
+ qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] );
+ R_BindAnimatedImage( &pStage->bundle[0] );
+
+ //
+ // lightmap/secondary pass
+ //
+ GL_SelectTexture( 1 );
+ qglEnable( GL_TEXTURE_2D );
+ qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
+
+ if ( r_lightmap->integer ) {
+ GL_TexEnv( GL_REPLACE );
+ } else {
+ GL_TexEnv( tess.shader->multitextureEnv );
+ }
+
+ qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[1] );
+
+ R_BindAnimatedImage( &pStage->bundle[1] );
+
+ R_DrawElements( input->numIndexes, input->indexes );
+
+ //
+ // disable texturing on TEXTURE1, then select TEXTURE0
+ //
+ //qglDisableClientState( GL_TEXTURE_COORD_ARRAY );
+ qglDisable( GL_TEXTURE_2D );
+
+ GL_SelectTexture( 0 );
+}
+
+
+
+/*
+===================
+ProjectDlightTexture
+
+Perform dynamic lighting with another rendering pass
+===================
+*/
+#if idppc_altivec
+static void ProjectDlightTexture_altivec( void ) {
+ int i, l;
+ vec_t origin0, origin1, origin2;
+ float texCoords0, texCoords1;
+ vector float floatColorVec0, floatColorVec1;
+ vector float modulateVec, colorVec, zero;
+ vector short colorShort;
+ vector signed int colorInt;
+ vector unsigned char floatColorVecPerm, modulatePerm, colorChar;
+ vector unsigned char vSel = VECCONST_UINT8(0x00, 0x00, 0x00, 0xff,
+ 0x00, 0x00, 0x00, 0xff,
+ 0x00, 0x00, 0x00, 0xff,
+ 0x00, 0x00, 0x00, 0xff);
+ float *texCoords;
+ byte *colors;
+ byte clipBits[SHADER_MAX_VERTEXES];
+ float texCoordsArray[SHADER_MAX_VERTEXES][2];
+ byte colorArray[SHADER_MAX_VERTEXES][4];
+#ifdef PANDORA
+ glIndex_t hitIndexes[SHADER_MAX_INDEXES];
+#else
+ unsigned hitIndexes[SHADER_MAX_INDEXES];
+#endif
+ int numIndexes;
+ float scale;
+ float radius;
+ vec3_t floatColor;
+ float modulate = 0.0f;
+
+ if ( !backEnd.refdef.num_dlights ) {
+ return;
+ }
+
+ // There has to be a better way to do this so that floatColor
+ // and/or modulate are already 16-byte aligned.
+ floatColorVecPerm = vec_lvsl(0,(float *)floatColor);
+ modulatePerm = vec_lvsl(0,(float *)&modulate);
+ modulatePerm = (vector unsigned char)vec_splat((vector unsigned int)modulatePerm,0);
+ zero = (vector float)vec_splat_s8(0);
+
+ for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) {
+ dlight_t *dl;
+
+ if ( !( tess.dlightBits & ( 1 << l ) ) ) {
+ continue; // this surface definately doesn't have any of this light
+ }
+ texCoords = texCoordsArray[0];
+ colors = colorArray[0];
+
+ dl = &backEnd.refdef.dlights[l];
+ origin0 = dl->transformed[0];
+ origin1 = dl->transformed[1];
+ origin2 = dl->transformed[2];
+ radius = dl->radius;
+ scale = 1.0f / radius;
+
+ if(r_greyscale->integer)
+ {
+ float luminance;
+
+ luminance = (dl->color[0] * 255.0f + dl->color[1] * 255.0f + dl->color[2] * 255.0f) / 3;
+ floatColor[0] = floatColor[1] = floatColor[2] = luminance;
+ }
+ else
+ {
+ floatColor[0] = dl->color[0] * 255.0f;
+ floatColor[1] = dl->color[1] * 255.0f;
+ floatColor[2] = dl->color[2] * 255.0f;
+ }
+ floatColorVec0 = vec_ld(0, floatColor);
+ floatColorVec1 = vec_ld(11, floatColor);
+ floatColorVec0 = vec_perm(floatColorVec0,floatColorVec0,floatColorVecPerm);
+ for ( i = 0 ; i < tess.numVertexes ; i++, texCoords += 2, colors += 4 ) {
+ int clip = 0;
+ vec_t dist0, dist1, dist2;
+
+ dist0 = origin0 - tess.xyz[i][0];
+ dist1 = origin1 - tess.xyz[i][1];
+ dist2 = origin2 - tess.xyz[i][2];
+
+ backEnd.pc.c_dlightVertexes++;
+
+ texCoords0 = 0.5f + dist0 * scale;
+ texCoords1 = 0.5f + dist1 * scale;
+
+ if( !r_dlightBacks->integer &&
+ // dist . tess.normal[i]
+ ( dist0 * tess.normal[i][0] +
+ dist1 * tess.normal[i][1] +
+ dist2 * tess.normal[i][2] ) < 0.0f ) {
+ clip = 63;
+ } else {
+ if ( texCoords0 < 0.0f ) {
+ clip |= 1;
+ } else if ( texCoords0 > 1.0f ) {
+ clip |= 2;
+ }
+ if ( texCoords1 < 0.0f ) {
+ clip |= 4;
+ } else if ( texCoords1 > 1.0f ) {
+ clip |= 8;
+ }
+ texCoords[0] = texCoords0;
+ texCoords[1] = texCoords1;
+
+ // modulate the strength based on the height and color
+ if ( dist2 > radius ) {
+ clip |= 16;
+ modulate = 0.0f;
+ } else if ( dist2 < -radius ) {
+ clip |= 32;
+ modulate = 0.0f;
+ } else {
+ dist2 = Q_fabs(dist2);
+ if ( dist2 < radius * 0.5f ) {
+ modulate = 1.0f;
+ } else {
+ modulate = 2.0f * (radius - dist2) * scale;
+ }
+ }
+ }
+ clipBits[i] = clip;
+
+ modulateVec = vec_ld(0,(float *)&modulate);
+ modulateVec = vec_perm(modulateVec,modulateVec,modulatePerm);
+ colorVec = vec_madd(floatColorVec0,modulateVec,zero);
+ colorInt = vec_cts(colorVec,0); // RGBx
+ colorShort = vec_pack(colorInt,colorInt); // RGBxRGBx
+ colorChar = vec_packsu(colorShort,colorShort); // RGBxRGBxRGBxRGBx
+ colorChar = vec_sel(colorChar,vSel,vSel); // RGBARGBARGBARGBA replace alpha with 255
+ vec_ste((vector unsigned int)colorChar,0,(unsigned int *)colors); // store color
+ }
+
+ // build a list of triangles that need light
+ numIndexes = 0;
+ for ( i = 0 ; i < tess.numIndexes ; i += 3 ) {
+#ifdef PANDORA
+ glIndex_t a, b, c;
+#else
+ int a, b, c;
+#endif
+ a = tess.indexes[i];
+ b = tess.indexes[i+1];
+ c = tess.indexes[i+2];
+ if ( clipBits[a] & clipBits[b] & clipBits[c] ) {
+ continue; // not lighted
+ }
+ hitIndexes[numIndexes] = a;
+ hitIndexes[numIndexes+1] = b;
+ hitIndexes[numIndexes+2] = c;
+ numIndexes += 3;
+ }
+
+ if ( !numIndexes ) {
+ continue;
+ }
+
+ qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
+ qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] );
+
+ qglEnableClientState( GL_COLOR_ARRAY );
+ qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray );
+
+ GL_Bind( tr.dlightImage );
+ // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light
+ // where they aren't rendered
+ if ( dl->additive ) {
+ GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
+ }
+ else {
+ GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
+ }
+ R_DrawElements( numIndexes, hitIndexes );
+ backEnd.pc.c_totalIndexes += numIndexes;
+ backEnd.pc.c_dlightIndexes += numIndexes;
+ }
+}
+#endif
+
+
+static void ProjectDlightTexture_scalar( void ) {
+ int i, l;
+ vec3_t origin;
+ float *texCoords;
+ byte *colors;
+ byte clipBits[SHADER_MAX_VERTEXES];
+ float texCoordsArray[SHADER_MAX_VERTEXES][2];
+ byte colorArray[SHADER_MAX_VERTEXES][4];
+#ifdef PANDORA
+ glIndex_t hitIndexes[SHADER_MAX_INDEXES];
+#else
+ unsigned hitIndexes[SHADER_MAX_INDEXES];
+#endif
+ int numIndexes;
+ float scale;
+ float radius;
+ vec3_t floatColor;
+ float modulate = 0.0f;
+
+ if ( !backEnd.refdef.num_dlights ) {
+ return;
+ }
+
+ for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) {
+ dlight_t *dl;
+
+ if ( !( tess.dlightBits & ( 1 << l ) ) ) {
+ continue; // this surface definately doesn't have any of this light
+ }
+ texCoords = texCoordsArray[0];
+ colors = colorArray[0];
+
+ dl = &backEnd.refdef.dlights[l];
+ VectorCopy( dl->transformed, origin );
+ radius = dl->radius;
+ scale = 1.0f / radius;
+
+ if(r_greyscale->integer)
+ {
+ float luminance;
+
+ luminance = (dl->color[0] * 255.0f + dl->color[1] * 255.0f + dl->color[2] * 255.0f) / 3;
+ floatColor[0] = floatColor[1] = floatColor[2] = luminance;
+ }
+ else
+ {
+ floatColor[0] = dl->color[0] * 255.0f;
+ floatColor[1] = dl->color[1] * 255.0f;
+ floatColor[2] = dl->color[2] * 255.0f;
+ }
+
+ for ( i = 0 ; i < tess.numVertexes ; i++, texCoords += 2, colors += 4 ) {
+ int clip = 0;
+ vec3_t dist;
+
+ VectorSubtract( origin, tess.xyz[i], dist );
+
+ backEnd.pc.c_dlightVertexes++;
+
+ texCoords[0] = 0.5f + dist[0] * scale;
+ texCoords[1] = 0.5f + dist[1] * scale;
+
+ if( !r_dlightBacks->integer &&
+ // dist . tess.normal[i]
+ ( dist[0] * tess.normal[i][0] +
+ dist[1] * tess.normal[i][1] +
+ dist[2] * tess.normal[i][2] ) < 0.0f ) {
+ clip = 63;
+ } else {
+ if ( texCoords[0] < 0.0f ) {
+ clip |= 1;
+ } else if ( texCoords[0] > 1.0f ) {
+ clip |= 2;
+ }
+ if ( texCoords[1] < 0.0f ) {
+ clip |= 4;
+ } else if ( texCoords[1] > 1.0f ) {
+ clip |= 8;
+ }
+ texCoords[0] = texCoords[0];
+ texCoords[1] = texCoords[1];
+
+ // modulate the strength based on the height and color
+ if ( dist[2] > radius ) {
+ clip |= 16;
+ modulate = 0.0f;
+ } else if ( dist[2] < -radius ) {
+ clip |= 32;
+ modulate = 0.0f;
+ } else {
+ dist[2] = Q_fabs(dist[2]);
+ if ( dist[2] < radius * 0.5f ) {
+ modulate = 1.0f;
+ } else {
+ modulate = 2.0f * (radius - dist[2]) * scale;
+ }
+ }
+ }
+ clipBits[i] = clip;
+ colors[0] = myftol(floatColor[0] * modulate);
+ colors[1] = myftol(floatColor[1] * modulate);
+ colors[2] = myftol(floatColor[2] * modulate);
+ colors[3] = 255;
+ }
+
+ // build a list of triangles that need light
+ numIndexes = 0;
+ for ( i = 0 ; i < tess.numIndexes ; i += 3 ) {
+#ifdef PANDORA
+ glIndex_t a, b, c;
+#else
+ int a, b, c;
+#endif
+
+ a = tess.indexes[i];
+ b = tess.indexes[i+1];
+ c = tess.indexes[i+2];
+ if ( clipBits[a] & clipBits[b] & clipBits[c] ) {
+ continue; // not lighted
+ }
+ hitIndexes[numIndexes] = a;
+ hitIndexes[numIndexes+1] = b;
+ hitIndexes[numIndexes+2] = c;
+ numIndexes += 3;
+ }
+
+ if ( !numIndexes ) {
+ continue;
+ }
+
+ qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
+ qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] );
+
+ qglEnableClientState( GL_COLOR_ARRAY );
+ qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray );
+
+ GL_Bind( tr.dlightImage );
+ // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light
+ // where they aren't rendered
+ if ( dl->additive ) {
+ GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
+ }
+ else {
+ GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
+ }
+ R_DrawElements( numIndexes, hitIndexes );
+ backEnd.pc.c_totalIndexes += numIndexes;
+ backEnd.pc.c_dlightIndexes += numIndexes;
+ }
+}
+
+static void ProjectDlightTexture( void ) {
+#if idppc_altivec
+ if (com_altivec->integer) {
+ // must be in a seperate function or G3 systems will crash.
+ ProjectDlightTexture_altivec();
+ return;
+ }
+#endif
+ ProjectDlightTexture_scalar();
+}
+
+
+/*
+===================
+RB_FogPass
+
+Blends a fog texture on top of everything else
+===================
+*/
+static void RB_FogPass( void ) {
+ fog_t *fog;
+ int i;
+
+ qglEnableClientState( GL_COLOR_ARRAY );
+ qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors );
+
+ qglEnableClientState( GL_TEXTURE_COORD_ARRAY);
+ qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords[0] );
+
+ fog = tr.world->fogs + tess.fogNum;
+
+ for ( i = 0; i < tess.numVertexes; i++ ) {
+ * ( int * )&tess.svars.colors[i] = fog->colorInt;
+ }
+
+ RB_CalcFogTexCoords( ( float * ) tess.svars.texcoords[0] );
+
+ GL_Bind( tr.fogImage );
+
+ if ( tess.shader->fogPass == FP_EQUAL ) {
+ GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL );
+ } else {
+ GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA );
+ }
+
+ R_DrawElements( tess.numIndexes, tess.indexes );
+}
+
+/*
+===============
+ComputeColors
+===============
+*/
+static void ComputeColors( shaderStage_t *pStage )
+{
+ int i;
+
+ //
+ // rgbGen
+ //
+ switch ( pStage->rgbGen )
+ {
+ case CGEN_IDENTITY:
+ Com_Memset( tess.svars.colors, 0xff, tess.numVertexes * 4 );
+ break;
+ default:
+ case CGEN_IDENTITY_LIGHTING:
+ Com_Memset( tess.svars.colors, tr.identityLightByte, tess.numVertexes * 4 );
+ break;
+ case CGEN_LIGHTING_DIFFUSE:
+ RB_CalcDiffuseColor( ( unsigned char * ) tess.svars.colors );
+ break;
+ case CGEN_EXACT_VERTEX:
+ Com_Memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) );
+ break;
+ case CGEN_CONST:
+ for ( i = 0; i < tess.numVertexes; i++ ) {
+ *(int *)tess.svars.colors[i] = *(int *)pStage->constantColor;
+ }
+ break;
+ case CGEN_VERTEX:
+ if ( tr.identityLight == 1 )
+ {
+ Com_Memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) );
+ }
+ else
+ {
+ for ( i = 0; i < tess.numVertexes; i++ )
+ {
+ tess.svars.colors[i][0] = tess.vertexColors[i][0] * tr.identityLight;
+ tess.svars.colors[i][1] = tess.vertexColors[i][1] * tr.identityLight;
+ tess.svars.colors[i][2] = tess.vertexColors[i][2] * tr.identityLight;
+ tess.svars.colors[i][3] = tess.vertexColors[i][3];
+ }
+ }
+ break;
+ case CGEN_ONE_MINUS_VERTEX:
+ if ( tr.identityLight == 1 )
+ {
+ for ( i = 0; i < tess.numVertexes; i++ )
+ {
+ tess.svars.colors[i][0] = 255 - tess.vertexColors[i][0];
+ tess.svars.colors[i][1] = 255 - tess.vertexColors[i][1];
+ tess.svars.colors[i][2] = 255 - tess.vertexColors[i][2];
+ }
+ }
+ else
+ {
+ for ( i = 0; i < tess.numVertexes; i++ )
+ {
+ tess.svars.colors[i][0] = ( 255 - tess.vertexColors[i][0] ) * tr.identityLight;
+ tess.svars.colors[i][1] = ( 255 - tess.vertexColors[i][1] ) * tr.identityLight;
+ tess.svars.colors[i][2] = ( 255 - tess.vertexColors[i][2] ) * tr.identityLight;
+ }
+ }
+ break;
+ case CGEN_FOG:
+ {
+ fog_t *fog;
+
+ fog = tr.world->fogs + tess.fogNum;
+
+ for ( i = 0; i < tess.numVertexes; i++ ) {
+ * ( int * )&tess.svars.colors[i] = fog->colorInt;
+ }
+ }
+ break;
+ case CGEN_WAVEFORM:
+ RB_CalcWaveColor( &pStage->rgbWave, ( unsigned char * ) tess.svars.colors );
+ break;
+ case CGEN_ENTITY:
+ RB_CalcColorFromEntity( ( unsigned char * ) tess.svars.colors );
+ break;
+ case CGEN_ONE_MINUS_ENTITY:
+ RB_CalcColorFromOneMinusEntity( ( unsigned char * ) tess.svars.colors );
+ break;
+ }
+
+ //
+ // alphaGen
+ //
+ switch ( pStage->alphaGen )
+ {
+ case AGEN_SKIP:
+ break;
+ case AGEN_IDENTITY:
+ if ( pStage->rgbGen != CGEN_IDENTITY ) {
+ if ( ( pStage->rgbGen == CGEN_VERTEX && tr.identityLight != 1 ) ||
+ pStage->rgbGen != CGEN_VERTEX ) {
+ for ( i = 0; i < tess.numVertexes; i++ ) {
+ tess.svars.colors[i][3] = 0xff;
+ }
+ }
+ }
+ break;
+ case AGEN_CONST:
+ if ( pStage->rgbGen != CGEN_CONST ) {
+ for ( i = 0; i < tess.numVertexes; i++ ) {
+ tess.svars.colors[i][3] = pStage->constantColor[3];
+ }
+ }
+ break;
+ case AGEN_WAVEFORM:
+ RB_CalcWaveAlpha( &pStage->alphaWave, ( unsigned char * ) tess.svars.colors );
+ break;
+ case AGEN_LIGHTING_SPECULAR:
+ RB_CalcSpecularAlpha( ( unsigned char * ) tess.svars.colors );
+ break;
+ case AGEN_ENTITY:
+ RB_CalcAlphaFromEntity( ( unsigned char * ) tess.svars.colors );
+ break;
+ case AGEN_ONE_MINUS_ENTITY:
+ RB_CalcAlphaFromOneMinusEntity( ( unsigned char * ) tess.svars.colors );
+ break;
+ case AGEN_VERTEX:
+ if ( pStage->rgbGen != CGEN_VERTEX ) {
+ for ( i = 0; i < tess.numVertexes; i++ ) {
+ tess.svars.colors[i][3] = tess.vertexColors[i][3];
+ }
+ }
+ break;
+ case AGEN_ONE_MINUS_VERTEX:
+ for ( i = 0; i < tess.numVertexes; i++ )
+ {
+ tess.svars.colors[i][3] = 255 - tess.vertexColors[i][3];
+ }
+ break;
+ case AGEN_PORTAL:
+ {
+ unsigned char alpha;
+
+ for ( i = 0; i < tess.numVertexes; i++ )
+ {
+ float len;
+ vec3_t v;
+
+ VectorSubtract( tess.xyz[i], backEnd.viewParms.or.origin, v );
+ len = VectorLength( v );
+
+ len /= tess.shader->portalRange;
+
+ if ( len < 0 )
+ {
+ alpha = 0;
+ }
+ else if ( len > 1 )
+ {
+ alpha = 0xff;
+ }
+ else
+ {
+ alpha = len * 0xff;
+ }
+
+ tess.svars.colors[i][3] = alpha;
+ }
+ }
+ break;
+ }
+
+ //
+ // fog adjustment for colors to fade out as fog increases
+ //
+ if ( tess.fogNum )
+ {
+ switch ( pStage->adjustColorsForFog )
+ {
+ case ACFF_MODULATE_RGB:
+ RB_CalcModulateColorsByFog( ( unsigned char * ) tess.svars.colors );
+ break;
+ case ACFF_MODULATE_ALPHA:
+ RB_CalcModulateAlphasByFog( ( unsigned char * ) tess.svars.colors );
+ break;
+ case ACFF_MODULATE_RGBA:
+ RB_CalcModulateRGBAsByFog( ( unsigned char * ) tess.svars.colors );
+ break;
+ case ACFF_NONE:
+ break;
+ }
+ }
+
+ // if in greyscale rendering mode turn all color values into greyscale.
+ if(r_greyscale->integer)
+ {
+ int scale;
+
+ for(i = 0; i < tess.numVertexes; i++)
+ {
+ scale = (tess.svars.colors[i][0] + tess.svars.colors[i][1] + tess.svars.colors[i][2]) / 3;
+ tess.svars.colors[i][0] = tess.svars.colors[i][1] = tess.svars.colors[i][2] = scale;
+ }
+ }
+}
+
+/*
+===============
+ComputeTexCoords
+===============
+*/
+static void ComputeTexCoords( shaderStage_t *pStage ) {
+ int i;
+ int b;
+
+ for ( b = 0; b < NUM_TEXTURE_BUNDLES; b++ ) {
+ int tm;
+
+ //
+ // generate the texture coordinates
+ //
+ switch ( pStage->bundle[b].tcGen )
+ {
+ case TCGEN_IDENTITY:
+ Com_Memset( tess.svars.texcoords[b], 0, sizeof( float ) * 2 * tess.numVertexes );
+ break;
+ case TCGEN_TEXTURE:
+ for ( i = 0 ; i < tess.numVertexes ; i++ ) {
+ tess.svars.texcoords[b][i][0] = tess.texCoords[i][0][0];
+ tess.svars.texcoords[b][i][1] = tess.texCoords[i][0][1];
+ }
+ break;
+ case TCGEN_LIGHTMAP:
+ for ( i = 0 ; i < tess.numVertexes ; i++ ) {
+ tess.svars.texcoords[b][i][0] = tess.texCoords[i][1][0];
+ tess.svars.texcoords[b][i][1] = tess.texCoords[i][1][1];
+ }
+ break;
+ case TCGEN_VECTOR:
+ for ( i = 0 ; i < tess.numVertexes ; i++ ) {
+ tess.svars.texcoords[b][i][0] = DotProduct( tess.xyz[i], pStage->bundle[b].tcGenVectors[0] );
+ tess.svars.texcoords[b][i][1] = DotProduct( tess.xyz[i], pStage->bundle[b].tcGenVectors[1] );
+ }
+ break;
+ case TCGEN_FOG:
+ RB_CalcFogTexCoords( ( float * ) tess.svars.texcoords[b] );
+ break;
+ case TCGEN_ENVIRONMENT_MAPPED:
+ RB_CalcEnvironmentTexCoords( ( float * ) tess.svars.texcoords[b] );
+ break;
+ case TCGEN_BAD:
+ return;
+ }
+
+ //
+ // alter texture coordinates
+ //
+ for ( tm = 0; tm < pStage->bundle[b].numTexMods ; tm++ ) {
+ switch ( pStage->bundle[b].texMods[tm].type )
+ {
+ case TMOD_NONE:
+ tm = TR_MAX_TEXMODS; // break out of for loop
+ break;
+
+ case TMOD_TURBULENT:
+ RB_CalcTurbulentTexCoords( &pStage->bundle[b].texMods[tm].wave,
+ ( float * ) tess.svars.texcoords[b] );
+ break;
+
+ case TMOD_ENTITY_TRANSLATE:
+ RB_CalcScrollTexCoords( backEnd.currentEntity->e.shaderTexCoord,
+ ( float * ) tess.svars.texcoords[b] );
+ break;
+
+ case TMOD_SCROLL:
+ RB_CalcScrollTexCoords( pStage->bundle[b].texMods[tm].scroll,
+ ( float * ) tess.svars.texcoords[b] );
+ break;
+
+ case TMOD_SCALE:
+ RB_CalcScaleTexCoords( pStage->bundle[b].texMods[tm].scale,
+ ( float * ) tess.svars.texcoords[b] );
+ break;
+
+ case TMOD_STRETCH:
+ RB_CalcStretchTexCoords( &pStage->bundle[b].texMods[tm].wave,
+ ( float * ) tess.svars.texcoords[b] );
+ break;
+
+ case TMOD_TRANSFORM:
+ RB_CalcTransformTexCoords( &pStage->bundle[b].texMods[tm],
+ ( float * ) tess.svars.texcoords[b] );
+ break;
+
+ case TMOD_ROTATE:
+ RB_CalcRotateTexCoords( pStage->bundle[b].texMods[tm].rotateSpeed,
+ ( float * ) tess.svars.texcoords[b] );
+ break;
+
+ default:
+ ri.Error( ERR_DROP, "ERROR: unknown texmod '%d' in shader '%s'\n", pStage->bundle[b].texMods[tm].type, tess.shader->name );
+ break;
+ }
+ }
+ }
+}
+
+/*
+** RB_IterateStagesGeneric
+*/
+static void RB_IterateStagesGeneric( shaderCommands_t *input )
+{
+ int stage;
+
+ for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ )
+ {
+ shaderStage_t *pStage = tess.xstages[stage];
+
+ if ( !pStage )
+ {
+ break;
+ }
+
+ ComputeColors( pStage );
+ ComputeTexCoords( pStage );
+
+ if ( !setArraysOnce )
+ {
+ qglEnableClientState( GL_COLOR_ARRAY );
+ qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, input->svars.colors );
+ }
+
+ //
+ // do multitexture
+ //
+ if ( pStage->bundle[1].image[0] != 0 )
+ {
+ DrawMultitextured( input, stage );
+ }
+ else
+ {
+ if ( !setArraysOnce )
+ {
+ qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] );
+ }
+
+ //
+ // set state
+ //
+ if ( pStage->bundle[0].vertexLightmap && ( (r_vertexLight->integer && !r_uiFullScreen->integer) || glConfig.hardwareType == GLHW_PERMEDIA2 ) && r_lightmap->integer )
+ {
+ GL_Bind( tr.whiteImage );
+ }
+ else
+ R_BindAnimatedImage( &pStage->bundle[0] );
+
+ GL_State( pStage->stateBits );
+
+ //
+ // draw
+ //
+ R_DrawElements( input->numIndexes, input->indexes );
+ }
+ // allow skipping out to show just lightmaps during development
+ if ( r_lightmap->integer && ( pStage->bundle[0].isLightmap || pStage->bundle[1].isLightmap || pStage->bundle[0].vertexLightmap ) )
+ {
+ break;
+ }
+ }
+}
+
+
+/*
+** RB_StageIteratorGeneric
+*/
+void RB_StageIteratorGeneric( void )
+{
+ shaderCommands_t *input;
+
+ input = &tess;
+
+ RB_DeformTessGeometry();
+
+ //
+ // log this call
+ //
+ if ( r_logFile->integer )
+ {
+ // don't just call LogComment, or we will get
+ // a call to va() every frame!
+ GLimp_LogComment( va("--- RB_StageIteratorGeneric( %s ) ---\n", tess.shader->name) );
+ }
+
+ //
+ // set face culling appropriately
+ //
+ GL_Cull( input->shader->cullType );
+
+ // set polygon offset if necessary
+ if ( input->shader->polygonOffset )
+ {
+ qglEnable( GL_POLYGON_OFFSET_FILL );
+ qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value );
+ }
+
+ //
+ // if there is only a single pass then we can enable color
+ // and texture arrays before we compile, otherwise we need
+ // to avoid compiling those arrays since they will change
+ // during multipass rendering
+ //
+ if ( tess.numPasses > 1 || input->shader->multitextureEnv )
+ {
+ setArraysOnce = qfalse;
+ qglDisableClientState (GL_COLOR_ARRAY);
+ qglDisableClientState (GL_TEXTURE_COORD_ARRAY);
+ }
+ else
+ {
+ setArraysOnce = qtrue;
+
+ qglEnableClientState( GL_COLOR_ARRAY);
+ qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors );
+
+ qglEnableClientState( GL_TEXTURE_COORD_ARRAY);
+ qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords[0] );
+ }
+
+ //
+ // lock XYZ
+ //
+ qglVertexPointer (3, GL_FLOAT, 16, input->xyz); // padded for SIMD
+ if (qglLockArraysEXT)
+ {
+ qglLockArraysEXT(0, input->numVertexes);
+ GLimp_LogComment( "glLockArraysEXT\n" );
+ }
+
+ //
+ // enable color and texcoord arrays after the lock if necessary
+ //
+ if ( !setArraysOnce )
+ {
+ qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
+ qglEnableClientState( GL_COLOR_ARRAY );
+ }
+
+ //
+ // call shader function
+ //
+ RB_IterateStagesGeneric( input );
+
+ //
+ // now do any dynamic lighting needed
+ //
+ if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE
+ && !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) ) {
+ ProjectDlightTexture();
+ }
+
+ //
+ // now do fog
+ //
+ if ( tess.fogNum && tess.shader->fogPass ) {
+ RB_FogPass();
+ }
+
+ //
+ // unlock arrays
+ //
+ if (qglUnlockArraysEXT)
+ {
+ qglUnlockArraysEXT();
+ GLimp_LogComment( "glUnlockArraysEXT\n" );
+ }
+
+ //
+ // reset polygon offset
+ //
+ if ( input->shader->polygonOffset )
+ {
+ qglDisable( GL_POLYGON_OFFSET_FILL );
+ }
+}
+
+
+/*
+** RB_StageIteratorVertexLitTexture
+*/
+void RB_StageIteratorVertexLitTexture( void )
+{
+ shaderCommands_t *input;
+ shader_t *shader;
+
+ input = &tess;
+
+ shader = input->shader;
+
+ //
+ // compute colors
+ //
+ RB_CalcDiffuseColor( ( unsigned char * ) tess.svars.colors );
+
+ //
+ // log this call
+ //
+ if ( r_logFile->integer )
+ {
+ // don't just call LogComment, or we will get
+ // a call to va() every frame!
+ GLimp_LogComment( va("--- RB_StageIteratorVertexLitTexturedUnfogged( %s ) ---\n", tess.shader->name) );
+ }
+
+ //
+ // set face culling appropriately
+ //
+ GL_Cull( input->shader->cullType );
+
+ //
+ // set arrays and lock
+ //
+ qglEnableClientState( GL_COLOR_ARRAY);
+ qglEnableClientState( GL_TEXTURE_COORD_ARRAY);
+
+ qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors );
+ qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][0] );
+ qglVertexPointer (3, GL_FLOAT, 16, input->xyz);
+
+ if ( qglLockArraysEXT )
+ {
+ qglLockArraysEXT(0, input->numVertexes);
+ GLimp_LogComment( "glLockArraysEXT\n" );
+ }
+
+ //
+ // call special shade routine
+ //
+ R_BindAnimatedImage( &tess.xstages[0]->bundle[0] );
+ GL_State( tess.xstages[0]->stateBits );
+ R_DrawElements( input->numIndexes, input->indexes );
+
+ //
+ // now do any dynamic lighting needed
+ //
+ if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE ) {
+ ProjectDlightTexture();
+ }
+
+ //
+ // now do fog
+ //
+ if ( tess.fogNum && tess.shader->fogPass ) {
+ RB_FogPass();
+ }
+
+ //
+ // unlock arrays
+ //
+ if (qglUnlockArraysEXT)
+ {
+ qglUnlockArraysEXT();
+ GLimp_LogComment( "glUnlockArraysEXT\n" );
+ }
+}
+
+//define REPLACE_MODE
+
+void RB_StageIteratorLightmappedMultitexture( void ) {
+ shaderCommands_t *input;
+
+ input = &tess;
+
+ //
+ // log this call
+ //
+ if ( r_logFile->integer ) {
+ // don't just call LogComment, or we will get
+ // a call to va() every frame!
+ GLimp_LogComment( va("--- RB_StageIteratorLightmappedMultitexture( %s ) ---\n", tess.shader->name) );
+ }
+
+ //
+ // set face culling appropriately
+ //
+ GL_Cull( input->shader->cullType );
+
+ //
+ // set color, pointers, and lock
+ //
+ GL_State( GLS_DEFAULT );
+ qglVertexPointer( 3, GL_FLOAT, 16, input->xyz );
+
+#ifdef REPLACE_MODE
+ qglDisableClientState( GL_COLOR_ARRAY );
+ qglColor3f( 1, 1, 1 );
+ qglShadeModel( GL_FLAT );
+#else
+ qglEnableClientState( GL_COLOR_ARRAY );
+ qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.constantColor255 );
+#endif
+
+ //
+ // select base stage
+ //
+ GL_SelectTexture( 0 );
+
+ qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
+ R_BindAnimatedImage( &tess.xstages[0]->bundle[0] );
+ qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][0] );
+
+ //
+ // configure second stage
+ //
+ GL_SelectTexture( 1 );
+ qglEnable( GL_TEXTURE_2D );
+ if ( r_lightmap->integer ) {
+ GL_TexEnv( GL_REPLACE );
+ } else {
+ GL_TexEnv( GL_MODULATE );
+ }
+ R_BindAnimatedImage( &tess.xstages[0]->bundle[1] );
+ qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
+ qglTexCoordPointer( 2, GL_FLOAT, 16, tess.texCoords[0][1] );
+
+ //
+ // lock arrays
+ //
+ if ( qglLockArraysEXT ) {
+ qglLockArraysEXT(0, input->numVertexes);
+ GLimp_LogComment( "glLockArraysEXT\n" );
+ }
+
+ R_DrawElements( input->numIndexes, input->indexes );
+
+ //
+ // disable texturing on TEXTURE1, then select TEXTURE0
+ //
+ qglDisable( GL_TEXTURE_2D );
+ qglDisableClientState( GL_TEXTURE_COORD_ARRAY );
+
+ GL_SelectTexture( 0 );
+#ifdef REPLACE_MODE
+ GL_TexEnv( GL_MODULATE );
+ qglShadeModel( GL_SMOOTH );
+#endif
+
+ //
+ // now do any dynamic lighting needed
+ //
+ if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE ) {
+ ProjectDlightTexture();
+ }
+
+ //
+ // now do fog
+ //
+ if ( tess.fogNum && tess.shader->fogPass ) {
+ RB_FogPass();
+ }
+
+ //
+ // unlock arrays
+ //
+ if ( qglUnlockArraysEXT ) {
+ qglUnlockArraysEXT();
+ GLimp_LogComment( "glUnlockArraysEXT\n" );
+ }
+}
+
+/*
+** RB_EndSurface
+*/
+void RB_EndSurface( void ) {
+ shaderCommands_t *input;
+
+ input = &tess;
+
+ if (input->numIndexes == 0) {
+ return;
+ }
+
+ if (input->indexes[SHADER_MAX_INDEXES-1] != 0) {
+ ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_INDEXES hit");
+ }
+ if (input->xyz[SHADER_MAX_VERTEXES-1][0] != 0) {
+ ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_VERTEXES hit");
+ }
+
+ if ( tess.shader == tr.shadowShader ) {
+ RB_ShadowTessEnd();
+ return;
+ }
+
+ // for debugging of sort order issues, stop rendering after a given sort value
+ if ( r_debugSort->integer && r_debugSort->integer < tess.shader->sort ) {
+ return;
+ }
+
+ //
+ // update performance counters
+ //
+ backEnd.pc.c_shaders++;
+ backEnd.pc.c_vertexes += tess.numVertexes;
+ backEnd.pc.c_indexes += tess.numIndexes;
+ backEnd.pc.c_totalIndexes += tess.numIndexes * tess.numPasses;
+
+ //
+ // call off to shader specific tess end function
+ //
+ tess.currentStageIteratorFunc();
+
+ //
+ // draw debugging stuff
+ //
+ if ( r_showtris->integer ) {
+ DrawTris (input);
+ }
+ if ( r_shownormals->integer ) {
+ DrawNormals (input);
+ }
+ // clear shader so we can tell we don't have any unclosed surfaces
+ tess.numIndexes = 0;
+
+ GLimp_LogComment( "----------\n" );
+}
+
diff --git a/code/renderer/tr_shade_calc.c b/code/renderer/tr_shade_calc.c
new file mode 100644
index 0000000..6f9b0be
--- /dev/null
+++ b/code/renderer/tr_shade_calc.c
@@ -0,0 +1,1231 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// tr_shade_calc.c
+
+#include "tr_local.h"
+#if idppc_altivec && !defined(MACOS_X)
+#include <altivec.h>
+#endif
+
+
+#define WAVEVALUE( table, base, amplitude, phase, freq ) ((base) + table[ myftol( ( ( (phase) + tess.shaderTime * (freq) ) * FUNCTABLE_SIZE ) ) & FUNCTABLE_MASK ] * (amplitude))
+
+static float *TableForFunc( genFunc_t func )
+{
+ switch ( func )
+ {
+ case GF_SIN:
+ return tr.sinTable;
+ case GF_TRIANGLE:
+ return tr.triangleTable;
+ case GF_SQUARE:
+ return tr.squareTable;
+ case GF_SAWTOOTH:
+ return tr.sawToothTable;
+ case GF_INVERSE_SAWTOOTH:
+ return tr.inverseSawToothTable;
+ case GF_NONE:
+ default:
+ break;
+ }
+
+ ri.Error( ERR_DROP, "TableForFunc called with invalid function '%d' in shader '%s'\n", func, tess.shader->name );
+ return NULL;
+}
+
+/*
+** EvalWaveForm
+**
+** Evaluates a given waveForm_t, referencing backEnd.refdef.time directly
+*/
+static float EvalWaveForm( const waveForm_t *wf )
+{
+ float *table;
+
+ table = TableForFunc( wf->func );
+
+ return WAVEVALUE( table, wf->base, wf->amplitude, wf->phase, wf->frequency );
+}
+
+static float EvalWaveFormClamped( const waveForm_t *wf )
+{
+ float glow = EvalWaveForm( wf );
+
+ if ( glow < 0 )
+ {
+ return 0;
+ }
+
+ if ( glow > 1 )
+ {
+ return 1;
+ }
+
+ return glow;
+}
+
+/*
+** RB_CalcStretchTexCoords
+*/
+void RB_CalcStretchTexCoords( const waveForm_t *wf, float *st )
+{
+ float p;
+ texModInfo_t tmi;
+
+ p = 1.0f / EvalWaveForm( wf );
+
+ tmi.matrix[0][0] = p;
+ tmi.matrix[1][0] = 0;
+ tmi.translate[0] = 0.5f - 0.5f * p;
+
+ tmi.matrix[0][1] = 0;
+ tmi.matrix[1][1] = p;
+ tmi.translate[1] = 0.5f - 0.5f * p;
+
+ RB_CalcTransformTexCoords( &tmi, st );
+}
+
+/*
+====================================================================
+
+DEFORMATIONS
+
+====================================================================
+*/
+
+/*
+========================
+RB_CalcDeformVertexes
+
+========================
+*/
+void RB_CalcDeformVertexes( deformStage_t *ds )
+{
+ int i;
+ vec3_t offset;
+ float scale;
+ float *xyz = ( float * ) tess.xyz;
+ float *normal = ( float * ) tess.normal;
+ float *table;
+
+ if ( ds->deformationWave.frequency == 0 )
+ {
+ scale = EvalWaveForm( &ds->deformationWave );
+
+ for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 )
+ {
+ VectorScale( normal, scale, offset );
+
+ xyz[0] += offset[0];
+ xyz[1] += offset[1];
+ xyz[2] += offset[2];
+ }
+ }
+ else
+ {
+ table = TableForFunc( ds->deformationWave.func );
+
+ for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 )
+ {
+ float off = ( xyz[0] + xyz[1] + xyz[2] ) * ds->deformationSpread;
+
+ scale = WAVEVALUE( table, ds->deformationWave.base,
+ ds->deformationWave.amplitude,
+ ds->deformationWave.phase + off,
+ ds->deformationWave.frequency );
+
+ VectorScale( normal, scale, offset );
+
+ xyz[0] += offset[0];
+ xyz[1] += offset[1];
+ xyz[2] += offset[2];
+ }
+ }
+}
+
+/*
+=========================
+RB_CalcDeformNormals
+
+Wiggle the normals for wavy environment mapping
+=========================
+*/
+void RB_CalcDeformNormals( deformStage_t *ds ) {
+ int i;
+ float scale;
+ float *xyz = ( float * ) tess.xyz;
+ float *normal = ( float * ) tess.normal;
+
+ for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) {
+ scale = 0.98f;
+ scale = R_NoiseGet4f( xyz[0] * scale, xyz[1] * scale, xyz[2] * scale,
+ tess.shaderTime * ds->deformationWave.frequency );
+ normal[ 0 ] += ds->deformationWave.amplitude * scale;
+
+ scale = 0.98f;
+ scale = R_NoiseGet4f( 100 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale,
+ tess.shaderTime * ds->deformationWave.frequency );
+ normal[ 1 ] += ds->deformationWave.amplitude * scale;
+
+ scale = 0.98f;
+ scale = R_NoiseGet4f( 200 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale,
+ tess.shaderTime * ds->deformationWave.frequency );
+ normal[ 2 ] += ds->deformationWave.amplitude * scale;
+
+ VectorNormalizeFast( normal );
+ }
+}
+
+/*
+========================
+RB_CalcBulgeVertexes
+
+========================
+*/
+void RB_CalcBulgeVertexes( deformStage_t *ds ) {
+ int i;
+ const float *st = ( const float * ) tess.texCoords[0];
+ float *xyz = ( float * ) tess.xyz;
+ float *normal = ( float * ) tess.normal;
+ float now;
+
+ now = backEnd.refdef.time * ds->bulgeSpeed * 0.001f;
+
+ for ( i = 0; i < tess.numVertexes; i++, xyz += 4, st += 4, normal += 4 ) {
+ int off;
+ float scale;
+
+ off = (float)( FUNCTABLE_SIZE / (M_PI*2) ) * ( st[0] * ds->bulgeWidth + now );
+
+ scale = tr.sinTable[ off & FUNCTABLE_MASK ] * ds->bulgeHeight;
+
+ xyz[0] += normal[0] * scale;
+ xyz[1] += normal[1] * scale;
+ xyz[2] += normal[2] * scale;
+ }
+}
+
+
+/*
+======================
+RB_CalcMoveVertexes
+
+A deformation that can move an entire surface along a wave path
+======================
+*/
+void RB_CalcMoveVertexes( deformStage_t *ds ) {
+ int i;
+ float *xyz;
+ float *table;
+ float scale;
+ vec3_t offset;
+
+ table = TableForFunc( ds->deformationWave.func );
+
+ scale = WAVEVALUE( table, ds->deformationWave.base,
+ ds->deformationWave.amplitude,
+ ds->deformationWave.phase,
+ ds->deformationWave.frequency );
+
+ VectorScale( ds->moveVector, scale, offset );
+
+ xyz = ( float * ) tess.xyz;
+ for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) {
+ VectorAdd( xyz, offset, xyz );
+ }
+}
+
+
+/*
+=============
+DeformText
+
+Change a polygon into a bunch of text polygons
+=============
+*/
+void DeformText( const char *text ) {
+ int i;
+ vec3_t origin, width, height;
+ int len;
+ int ch;
+ byte color[4];
+ float bottom, top;
+ vec3_t mid;
+
+ height[0] = 0;
+ height[1] = 0;
+ height[2] = -1;
+ CrossProduct( tess.normal[0], height, width );
+
+ // find the midpoint of the box
+ VectorClear( mid );
+ bottom = 999999;
+ top = -999999;
+ for ( i = 0 ; i < 4 ; i++ ) {
+ VectorAdd( tess.xyz[i], mid, mid );
+ if ( tess.xyz[i][2] < bottom ) {
+ bottom = tess.xyz[i][2];
+ }
+ if ( tess.xyz[i][2] > top ) {
+ top = tess.xyz[i][2];
+ }
+ }
+ VectorScale( mid, 0.25f, origin );
+
+ // determine the individual character size
+ height[0] = 0;
+ height[1] = 0;
+ height[2] = ( top - bottom ) * 0.5f;
+
+ VectorScale( width, height[2] * -0.75f, width );
+
+ // determine the starting position
+ len = strlen( text );
+ VectorMA( origin, (len-1), width, origin );
+
+ // clear the shader indexes
+ tess.numIndexes = 0;
+ tess.numVertexes = 0;
+
+ color[0] = color[1] = color[2] = color[3] = 255;
+
+ // draw each character
+ for ( i = 0 ; i < len ; i++ ) {
+ ch = text[i];
+ ch &= 255;
+
+ if ( ch != ' ' ) {
+ int row, col;
+ float frow, fcol, size;
+
+ row = ch>>4;
+ col = ch&15;
+
+ frow = row*0.0625f;
+ fcol = col*0.0625f;
+ size = 0.0625f;
+
+ RB_AddQuadStampExt( origin, width, height, color, fcol, frow, fcol + size, frow + size );
+ }
+ VectorMA( origin, -2, width, origin );
+ }
+}
+
+/*
+==================
+GlobalVectorToLocal
+==================
+*/
+static void GlobalVectorToLocal( const vec3_t in, vec3_t out ) {
+ out[0] = DotProduct( in, backEnd.or.axis[0] );
+ out[1] = DotProduct( in, backEnd.or.axis[1] );
+ out[2] = DotProduct( in, backEnd.or.axis[2] );
+}
+
+/*
+=====================
+AutospriteDeform
+
+Assuming all the triangles for this shader are independant
+quads, rebuild them as forward facing sprites
+=====================
+*/
+static void AutospriteDeform( void ) {
+ int i;
+ int oldVerts;
+ float *xyz;
+ vec3_t mid, delta;
+ float radius;
+ vec3_t left, up;
+ vec3_t leftDir, upDir;
+
+ if ( tess.numVertexes & 3 ) {
+ ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd vertex count", tess.shader->name );
+ }
+ if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) {
+ ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd index count", tess.shader->name );
+ }
+
+ oldVerts = tess.numVertexes;
+ tess.numVertexes = 0;
+ tess.numIndexes = 0;
+
+ if ( backEnd.currentEntity != &tr.worldEntity ) {
+ GlobalVectorToLocal( backEnd.viewParms.or.axis[1], leftDir );
+ GlobalVectorToLocal( backEnd.viewParms.or.axis[2], upDir );
+ } else {
+ VectorCopy( backEnd.viewParms.or.axis[1], leftDir );
+ VectorCopy( backEnd.viewParms.or.axis[2], upDir );
+ }
+
+ for ( i = 0 ; i < oldVerts ; i+=4 ) {
+ // find the midpoint
+ xyz = tess.xyz[i];
+
+ mid[0] = 0.25f * (xyz[0] + xyz[4] + xyz[8] + xyz[12]);
+ mid[1] = 0.25f * (xyz[1] + xyz[5] + xyz[9] + xyz[13]);
+ mid[2] = 0.25f * (xyz[2] + xyz[6] + xyz[10] + xyz[14]);
+
+ VectorSubtract( xyz, mid, delta );
+ radius = VectorLength( delta ) * 0.707f; // / sqrt(2)
+
+ VectorScale( leftDir, radius, left );
+ VectorScale( upDir, radius, up );
+
+ if ( backEnd.viewParms.isMirror ) {
+ VectorSubtract( vec3_origin, left, left );
+ }
+
+ // compensate for scale in the axes if necessary
+ if ( backEnd.currentEntity->e.nonNormalizedAxes ) {
+ float axisLength;
+ axisLength = VectorLength( backEnd.currentEntity->e.axis[0] );
+ if ( !axisLength ) {
+ axisLength = 0;
+ } else {
+ axisLength = 1.0f / axisLength;
+ }
+ VectorScale(left, axisLength, left);
+ VectorScale(up, axisLength, up);
+ }
+
+ RB_AddQuadStamp( mid, left, up, tess.vertexColors[i] );
+ }
+}
+
+
+/*
+=====================
+Autosprite2Deform
+
+Autosprite2 will pivot a rectangular quad along the center of its long axis
+=====================
+*/
+int edgeVerts[6][2] = {
+ { 0, 1 },
+ { 0, 2 },
+ { 0, 3 },
+ { 1, 2 },
+ { 1, 3 },
+ { 2, 3 }
+};
+
+static void Autosprite2Deform( void ) {
+ int i, j, k;
+ int indexes;
+ float *xyz;
+ vec3_t forward;
+
+ if ( tess.numVertexes & 3 ) {
+ ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd vertex count", tess.shader->name );
+ }
+ if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) {
+ ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd index count", tess.shader->name );
+ }
+
+ if ( backEnd.currentEntity != &tr.worldEntity ) {
+ GlobalVectorToLocal( backEnd.viewParms.or.axis[0], forward );
+ } else {
+ VectorCopy( backEnd.viewParms.or.axis[0], forward );
+ }
+
+ // this is a lot of work for two triangles...
+ // we could precalculate a lot of it is an issue, but it would mess up
+ // the shader abstraction
+ for ( i = 0, indexes = 0 ; i < tess.numVertexes ; i+=4, indexes+=6 ) {
+ float lengths[2];
+ int nums[2];
+ vec3_t mid[2];
+ vec3_t major, minor;
+ float *v1, *v2;
+
+ // find the midpoint
+ xyz = tess.xyz[i];
+
+ // identify the two shortest edges
+ nums[0] = nums[1] = 0;
+ lengths[0] = lengths[1] = 999999;
+
+ for ( j = 0 ; j < 6 ; j++ ) {
+ float l;
+ vec3_t temp;
+
+ v1 = xyz + 4 * edgeVerts[j][0];
+ v2 = xyz + 4 * edgeVerts[j][1];
+
+ VectorSubtract( v1, v2, temp );
+
+ l = DotProduct( temp, temp );
+ if ( l < lengths[0] ) {
+ nums[1] = nums[0];
+ lengths[1] = lengths[0];
+ nums[0] = j;
+ lengths[0] = l;
+ } else if ( l < lengths[1] ) {
+ nums[1] = j;
+ lengths[1] = l;
+ }
+ }
+
+ for ( j = 0 ; j < 2 ; j++ ) {
+ v1 = xyz + 4 * edgeVerts[nums[j]][0];
+ v2 = xyz + 4 * edgeVerts[nums[j]][1];
+
+ mid[j][0] = 0.5f * (v1[0] + v2[0]);
+ mid[j][1] = 0.5f * (v1[1] + v2[1]);
+ mid[j][2] = 0.5f * (v1[2] + v2[2]);
+ }
+
+ // find the vector of the major axis
+ VectorSubtract( mid[1], mid[0], major );
+
+ // cross this with the view direction to get minor axis
+ CrossProduct( major, forward, minor );
+ VectorNormalize( minor );
+
+ // re-project the points
+ for ( j = 0 ; j < 2 ; j++ ) {
+ float l;
+
+ v1 = xyz + 4 * edgeVerts[nums[j]][0];
+ v2 = xyz + 4 * edgeVerts[nums[j]][1];
+
+ l = 0.5 * sqrt( lengths[j] );
+
+ // we need to see which direction this edge
+ // is used to determine direction of projection
+ for ( k = 0 ; k < 5 ; k++ ) {
+ if ( tess.indexes[ indexes + k ] == i + edgeVerts[nums[j]][0]
+ && tess.indexes[ indexes + k + 1 ] == i + edgeVerts[nums[j]][1] ) {
+ break;
+ }
+ }
+
+ if ( k == 5 ) {
+ VectorMA( mid[j], l, minor, v1 );
+ VectorMA( mid[j], -l, minor, v2 );
+ } else {
+ VectorMA( mid[j], -l, minor, v1 );
+ VectorMA( mid[j], l, minor, v2 );
+ }
+ }
+ }
+}
+
+
+/*
+=====================
+RB_DeformTessGeometry
+
+=====================
+*/
+void RB_DeformTessGeometry( void ) {
+ int i;
+ deformStage_t *ds;
+
+ for ( i = 0 ; i < tess.shader->numDeforms ; i++ ) {
+ ds = &tess.shader->deforms[ i ];
+
+ switch ( ds->deformation ) {
+ case DEFORM_NONE:
+ break;
+ case DEFORM_NORMALS:
+ RB_CalcDeformNormals( ds );
+ break;
+ case DEFORM_WAVE:
+ RB_CalcDeformVertexes( ds );
+ break;
+ case DEFORM_BULGE:
+ RB_CalcBulgeVertexes( ds );
+ break;
+ case DEFORM_MOVE:
+ RB_CalcMoveVertexes( ds );
+ break;
+ case DEFORM_PROJECTION_SHADOW:
+ RB_ProjectionShadowDeform();
+ break;
+ case DEFORM_AUTOSPRITE:
+ AutospriteDeform();
+ break;
+ case DEFORM_AUTOSPRITE2:
+ Autosprite2Deform();
+ break;
+ case DEFORM_TEXT0:
+ case DEFORM_TEXT1:
+ case DEFORM_TEXT2:
+ case DEFORM_TEXT3:
+ case DEFORM_TEXT4:
+ case DEFORM_TEXT5:
+ case DEFORM_TEXT6:
+ case DEFORM_TEXT7:
+ DeformText( backEnd.refdef.text[ds->deformation - DEFORM_TEXT0] );
+ break;
+ }
+ }
+}
+
+/*
+====================================================================
+
+COLORS
+
+====================================================================
+*/
+
+
+/*
+** RB_CalcColorFromEntity
+*/
+void RB_CalcColorFromEntity( unsigned char *dstColors )
+{
+ int i;
+ int *pColors = ( int * ) dstColors;
+ int c;
+
+ if ( !backEnd.currentEntity )
+ return;
+
+ c = * ( int * ) backEnd.currentEntity->e.shaderRGBA;
+
+ for ( i = 0; i < tess.numVertexes; i++, pColors++ )
+ {
+ *pColors = c;
+ }
+}
+
+/*
+** RB_CalcColorFromOneMinusEntity
+*/
+void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors )
+{
+ int i;
+ int *pColors = ( int * ) dstColors;
+ unsigned char invModulate[4];
+ int c;
+
+ if ( !backEnd.currentEntity )
+ return;
+
+ invModulate[0] = 255 - backEnd.currentEntity->e.shaderRGBA[0];
+ invModulate[1] = 255 - backEnd.currentEntity->e.shaderRGBA[1];
+ invModulate[2] = 255 - backEnd.currentEntity->e.shaderRGBA[2];
+ invModulate[3] = 255 - backEnd.currentEntity->e.shaderRGBA[3]; // this trashes alpha, but the AGEN block fixes it
+
+ c = * ( int * ) invModulate;
+
+ for ( i = 0; i < tess.numVertexes; i++, pColors++ )
+ {
+ *pColors = * ( int * ) invModulate;
+ }
+}
+
+/*
+** RB_CalcAlphaFromEntity
+*/
+void RB_CalcAlphaFromEntity( unsigned char *dstColors )
+{
+ int i;
+
+ if ( !backEnd.currentEntity )
+ return;
+
+ dstColors += 3;
+
+ for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 )
+ {
+ *dstColors = backEnd.currentEntity->e.shaderRGBA[3];
+ }
+}
+
+/*
+** RB_CalcAlphaFromOneMinusEntity
+*/
+void RB_CalcAlphaFromOneMinusEntity( unsigned char *dstColors )
+{
+ int i;
+
+ if ( !backEnd.currentEntity )
+ return;
+
+ dstColors += 3;
+
+ for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 )
+ {
+ *dstColors = 0xff - backEnd.currentEntity->e.shaderRGBA[3];
+ }
+}
+
+/*
+** RB_CalcWaveColor
+*/
+void RB_CalcWaveColor( const waveForm_t *wf, unsigned char *dstColors )
+{
+ int i;
+ int v;
+ float glow;
+ int *colors = ( int * ) dstColors;
+ byte color[4];
+
+
+ if ( wf->func == GF_NOISE ) {
+ glow = wf->base + R_NoiseGet4f( 0, 0, 0, ( tess.shaderTime + wf->phase ) * wf->frequency ) * wf->amplitude;
+ } else {
+ glow = EvalWaveForm( wf ) * tr.identityLight;
+ }
+
+ if ( glow < 0 ) {
+ glow = 0;
+ }
+ else if ( glow > 1 ) {
+ glow = 1;
+ }
+
+ v = myftol( 255 * glow );
+ color[0] = color[1] = color[2] = v;
+ color[3] = 255;
+ v = *(int *)color;
+
+ for ( i = 0; i < tess.numVertexes; i++, colors++ ) {
+ *colors = v;
+ }
+}
+
+/*
+** RB_CalcWaveAlpha
+*/
+void RB_CalcWaveAlpha( const waveForm_t *wf, unsigned char *dstColors )
+{
+ int i;
+ int v;
+ float glow;
+
+ glow = EvalWaveFormClamped( wf );
+
+ v = 255 * glow;
+
+ for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 )
+ {
+ dstColors[3] = v;
+ }
+}
+
+/*
+** RB_CalcModulateColorsByFog
+*/
+void RB_CalcModulateColorsByFog( unsigned char *colors ) {
+ int i;
+ float texCoords[SHADER_MAX_VERTEXES][2];
+
+ // calculate texcoords so we can derive density
+ // this is not wasted, because it would only have
+ // been previously called if the surface was opaque
+ RB_CalcFogTexCoords( texCoords[0] );
+
+ for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) {
+ float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] );
+ colors[0] *= f;
+ colors[1] *= f;
+ colors[2] *= f;
+ }
+}
+
+/*
+** RB_CalcModulateAlphasByFog
+*/
+void RB_CalcModulateAlphasByFog( unsigned char *colors ) {
+ int i;
+ float texCoords[SHADER_MAX_VERTEXES][2];
+
+ // calculate texcoords so we can derive density
+ // this is not wasted, because it would only have
+ // been previously called if the surface was opaque
+ RB_CalcFogTexCoords( texCoords[0] );
+
+ for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) {
+ float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] );
+ colors[3] *= f;
+ }
+}
+
+/*
+** RB_CalcModulateRGBAsByFog
+*/
+void RB_CalcModulateRGBAsByFog( unsigned char *colors ) {
+ int i;
+ float texCoords[SHADER_MAX_VERTEXES][2];
+
+ // calculate texcoords so we can derive density
+ // this is not wasted, because it would only have
+ // been previously called if the surface was opaque
+ RB_CalcFogTexCoords( texCoords[0] );
+
+ for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) {
+ float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] );
+ colors[0] *= f;
+ colors[1] *= f;
+ colors[2] *= f;
+ colors[3] *= f;
+ }
+}
+
+
+/*
+====================================================================
+
+TEX COORDS
+
+====================================================================
+*/
+
+/*
+========================
+RB_CalcFogTexCoords
+
+To do the clipped fog plane really correctly, we should use
+projected textures, but I don't trust the drivers and it
+doesn't fit our shader data.
+========================
+*/
+void RB_CalcFogTexCoords( float *st ) {
+ int i;
+ float *v;
+ float s, t;
+ float eyeT;
+ qboolean eyeOutside;
+ fog_t *fog;
+ vec3_t local;
+ vec4_t fogDistanceVector, fogDepthVector = {0, 0, 0, 0};
+
+ fog = tr.world->fogs + tess.fogNum;
+
+ // all fogging distance is based on world Z units
+ VectorSubtract( backEnd.or.origin, backEnd.viewParms.or.origin, local );
+ fogDistanceVector[0] = -backEnd.or.modelMatrix[2];
+ fogDistanceVector[1] = -backEnd.or.modelMatrix[6];
+ fogDistanceVector[2] = -backEnd.or.modelMatrix[10];
+ fogDistanceVector[3] = DotProduct( local, backEnd.viewParms.or.axis[0] );
+
+ // scale the fog vectors based on the fog's thickness
+ fogDistanceVector[0] *= fog->tcScale;
+ fogDistanceVector[1] *= fog->tcScale;
+ fogDistanceVector[2] *= fog->tcScale;
+ fogDistanceVector[3] *= fog->tcScale;
+
+ // rotate the gradient vector for this orientation
+ if ( fog->hasSurface ) {
+ fogDepthVector[0] = fog->surface[0] * backEnd.or.axis[0][0] +
+ fog->surface[1] * backEnd.or.axis[0][1] + fog->surface[2] * backEnd.or.axis[0][2];
+ fogDepthVector[1] = fog->surface[0] * backEnd.or.axis[1][0] +
+ fog->surface[1] * backEnd.or.axis[1][1] + fog->surface[2] * backEnd.or.axis[1][2];
+ fogDepthVector[2] = fog->surface[0] * backEnd.or.axis[2][0] +
+ fog->surface[1] * backEnd.or.axis[2][1] + fog->surface[2] * backEnd.or.axis[2][2];
+ fogDepthVector[3] = -fog->surface[3] + DotProduct( backEnd.or.origin, fog->surface );
+
+ eyeT = DotProduct( backEnd.or.viewOrigin, fogDepthVector ) + fogDepthVector[3];
+ } else {
+ eyeT = 1; // non-surface fog always has eye inside
+ }
+
+ // see if the viewpoint is outside
+ // this is needed for clipping distance even for constant fog
+
+ if ( eyeT < 0 ) {
+ eyeOutside = qtrue;
+ } else {
+ eyeOutside = qfalse;
+ }
+
+ fogDistanceVector[3] += 1.0/512;
+
+ // calculate density for each point
+ for (i = 0, v = tess.xyz[0] ; i < tess.numVertexes ; i++, v += 4) {
+ // calculate the length in fog
+ s = DotProduct( v, fogDistanceVector ) + fogDistanceVector[3];
+ t = DotProduct( v, fogDepthVector ) + fogDepthVector[3];
+
+ // partially clipped fogs use the T axis
+ if ( eyeOutside ) {
+ if ( t < 1.0 ) {
+ t = 1.0/32; // point is outside, so no fogging
+ } else {
+ t = 1.0/32 + 30.0/32 * t / ( t - eyeT ); // cut the distance at the fog plane
+ }
+ } else {
+ if ( t < 0 ) {
+ t = 1.0/32; // point is outside, so no fogging
+ } else {
+ t = 31.0/32;
+ }
+ }
+
+ st[0] = s;
+ st[1] = t;
+ st += 2;
+ }
+}
+
+
+
+/*
+** RB_CalcEnvironmentTexCoords
+*/
+void RB_CalcEnvironmentTexCoords( float *st )
+{
+ int i;
+ float *v, *normal;
+ vec3_t viewer, reflected;
+ float d;
+
+ v = tess.xyz[0];
+ normal = tess.normal[0];
+
+ for (i = 0 ; i < tess.numVertexes ; i++, v += 4, normal += 4, st += 2 )
+ {
+ VectorSubtract (backEnd.or.viewOrigin, v, viewer);
+ VectorNormalizeFast (viewer);
+
+ d = DotProduct (normal, viewer);
+
+ reflected[0] = normal[0]*2*d - viewer[0];
+ reflected[1] = normal[1]*2*d - viewer[1];
+ reflected[2] = normal[2]*2*d - viewer[2];
+
+ st[0] = 0.5 + reflected[1] * 0.5;
+ st[1] = 0.5 - reflected[2] * 0.5;
+ }
+}
+
+/*
+** RB_CalcTurbulentTexCoords
+*/
+void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *st )
+{
+ int i;
+ float now;
+
+ now = ( wf->phase + tess.shaderTime * wf->frequency );
+
+ for ( i = 0; i < tess.numVertexes; i++, st += 2 )
+ {
+ float s = st[0];
+ float t = st[1];
+
+ st[0] = s + tr.sinTable[ ( ( int ) ( ( ( tess.xyz[i][0] + tess.xyz[i][2] )* 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude;
+ st[1] = t + tr.sinTable[ ( ( int ) ( ( tess.xyz[i][1] * 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude;
+ }
+}
+
+/*
+** RB_CalcScaleTexCoords
+*/
+void RB_CalcScaleTexCoords( const float scale[2], float *st )
+{
+ int i;
+
+ for ( i = 0; i < tess.numVertexes; i++, st += 2 )
+ {
+ st[0] *= scale[0];
+ st[1] *= scale[1];
+ }
+}
+
+/*
+** RB_CalcScrollTexCoords
+*/
+void RB_CalcScrollTexCoords( const float scrollSpeed[2], float *st )
+{
+ int i;
+ float timeScale = tess.shaderTime;
+ float adjustedScrollS, adjustedScrollT;
+
+ adjustedScrollS = scrollSpeed[0] * timeScale;
+ adjustedScrollT = scrollSpeed[1] * timeScale;
+
+ // clamp so coordinates don't continuously get larger, causing problems
+ // with hardware limits
+ adjustedScrollS = adjustedScrollS - floor( adjustedScrollS );
+ adjustedScrollT = adjustedScrollT - floor( adjustedScrollT );
+
+ for ( i = 0; i < tess.numVertexes; i++, st += 2 )
+ {
+ st[0] += adjustedScrollS;
+ st[1] += adjustedScrollT;
+ }
+}
+
+/*
+** RB_CalcTransformTexCoords
+*/
+void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *st )
+{
+ int i;
+
+ for ( i = 0; i < tess.numVertexes; i++, st += 2 )
+ {
+ float s = st[0];
+ float t = st[1];
+
+ st[0] = s * tmi->matrix[0][0] + t * tmi->matrix[1][0] + tmi->translate[0];
+ st[1] = s * tmi->matrix[0][1] + t * tmi->matrix[1][1] + tmi->translate[1];
+ }
+}
+
+/*
+** RB_CalcRotateTexCoords
+*/
+void RB_CalcRotateTexCoords( float degsPerSecond, float *st )
+{
+ float timeScale = tess.shaderTime;
+ float degs;
+ int index;
+ float sinValue, cosValue;
+ texModInfo_t tmi;
+
+ degs = -degsPerSecond * timeScale;
+ index = degs * ( FUNCTABLE_SIZE / 360.0f );
+
+ sinValue = tr.sinTable[ index & FUNCTABLE_MASK ];
+ cosValue = tr.sinTable[ ( index + FUNCTABLE_SIZE / 4 ) & FUNCTABLE_MASK ];
+
+ tmi.matrix[0][0] = cosValue;
+ tmi.matrix[1][0] = -sinValue;
+ tmi.translate[0] = 0.5 - 0.5 * cosValue + 0.5 * sinValue;
+
+ tmi.matrix[0][1] = sinValue;
+ tmi.matrix[1][1] = cosValue;
+ tmi.translate[1] = 0.5 - 0.5 * sinValue - 0.5 * cosValue;
+
+ RB_CalcTransformTexCoords( &tmi, st );
+}
+
+
+
+
+
+
+#if id386 && !defined(__GNUC__)
+
+long myftol( float f ) {
+ static int tmp;
+ __asm fld f
+ __asm fistp tmp
+ __asm mov eax, tmp
+}
+
+#endif
+
+/*
+** RB_CalcSpecularAlpha
+**
+** Calculates specular coefficient and places it in the alpha channel
+*/
+vec3_t lightOrigin = { -960, 1980, 96 }; // FIXME: track dynamically
+
+void RB_CalcSpecularAlpha( unsigned char *alphas ) {
+ int i;
+ float *v, *normal;
+ vec3_t viewer, reflected;
+ float l, d;
+ int b;
+ vec3_t lightDir;
+ int numVertexes;
+
+ v = tess.xyz[0];
+ normal = tess.normal[0];
+
+ alphas += 3;
+
+ numVertexes = tess.numVertexes;
+ for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4, alphas += 4) {
+ float ilength;
+
+ VectorSubtract( lightOrigin, v, lightDir );
+// ilength = Q_rsqrt( DotProduct( lightDir, lightDir ) );
+ VectorNormalizeFast( lightDir );
+
+ // calculate the specular color
+ d = DotProduct (normal, lightDir);
+// d *= ilength;
+
+ // we don't optimize for the d < 0 case since this tends to
+ // cause visual artifacts such as faceted "snapping"
+ reflected[0] = normal[0]*2*d - lightDir[0];
+ reflected[1] = normal[1]*2*d - lightDir[1];
+ reflected[2] = normal[2]*2*d - lightDir[2];
+
+ VectorSubtract (backEnd.or.viewOrigin, v, viewer);
+ ilength = Q_rsqrt( DotProduct( viewer, viewer ) );
+ l = DotProduct (reflected, viewer);
+ l *= ilength;
+
+ if (l < 0) {
+ b = 0;
+ } else {
+ l = l*l;
+ l = l*l;
+ b = l * 255;
+ if (b > 255) {
+ b = 255;
+ }
+ }
+
+ *alphas = b;
+ }
+}
+
+/*
+** RB_CalcDiffuseColor
+**
+** The basic vertex lighting calc
+*/
+#if idppc_altivec
+static void RB_CalcDiffuseColor_altivec( unsigned char *colors )
+{
+ int i;
+ float *v, *normal;
+ trRefEntity_t *ent;
+ int ambientLightInt;
+ vec3_t lightDir;
+ int numVertexes;
+ vector unsigned char vSel = VECCONST_UINT8(0x00, 0x00, 0x00, 0xff,
+ 0x00, 0x00, 0x00, 0xff,
+ 0x00, 0x00, 0x00, 0xff,
+ 0x00, 0x00, 0x00, 0xff);
+ vector float ambientLightVec;
+ vector float directedLightVec;
+ vector float lightDirVec;
+ vector float normalVec0, normalVec1;
+ vector float incomingVec0, incomingVec1, incomingVec2;
+ vector float zero, jVec;
+ vector signed int jVecInt;
+ vector signed short jVecShort;
+ vector unsigned char jVecChar, normalPerm;
+ ent = backEnd.currentEntity;
+ ambientLightInt = ent->ambientLightInt;
+ // A lot of this could be simplified if we made sure
+ // entities light info was 16-byte aligned.
+ jVecChar = vec_lvsl(0, ent->ambientLight);
+ ambientLightVec = vec_ld(0, (vector float *)ent->ambientLight);
+ jVec = vec_ld(11, (vector float *)ent->ambientLight);
+ ambientLightVec = vec_perm(ambientLightVec,jVec,jVecChar);
+
+ jVecChar = vec_lvsl(0, ent->directedLight);
+ directedLightVec = vec_ld(0,(vector float *)ent->directedLight);
+ jVec = vec_ld(11,(vector float *)ent->directedLight);
+ directedLightVec = vec_perm(directedLightVec,jVec,jVecChar);
+
+ jVecChar = vec_lvsl(0, ent->lightDir);
+ lightDirVec = vec_ld(0,(vector float *)ent->lightDir);
+ jVec = vec_ld(11,(vector float *)ent->lightDir);
+ lightDirVec = vec_perm(lightDirVec,jVec,jVecChar);
+
+ zero = (vector float)vec_splat_s8(0);
+ VectorCopy( ent->lightDir, lightDir );
+
+ v = tess.xyz[0];
+ normal = tess.normal[0];
+
+ normalPerm = vec_lvsl(0,normal);
+ numVertexes = tess.numVertexes;
+ for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4) {
+ normalVec0 = vec_ld(0,(vector float *)normal);
+ normalVec1 = vec_ld(11,(vector float *)normal);
+ normalVec0 = vec_perm(normalVec0,normalVec1,normalPerm);
+ incomingVec0 = vec_madd(normalVec0, lightDirVec, zero);
+ incomingVec1 = vec_sld(incomingVec0,incomingVec0,4);
+ incomingVec2 = vec_add(incomingVec0,incomingVec1);
+ incomingVec1 = vec_sld(incomingVec1,incomingVec1,4);
+ incomingVec2 = vec_add(incomingVec2,incomingVec1);
+ incomingVec0 = vec_splat(incomingVec2,0);
+ incomingVec0 = vec_max(incomingVec0,zero);
+ normalPerm = vec_lvsl(12,normal);
+ jVec = vec_madd(incomingVec0, directedLightVec, ambientLightVec);
+ jVecInt = vec_cts(jVec,0); // RGBx
+ jVecShort = vec_pack(jVecInt,jVecInt); // RGBxRGBx
+ jVecChar = vec_packsu(jVecShort,jVecShort); // RGBxRGBxRGBxRGBx
+ jVecChar = vec_sel(jVecChar,vSel,vSel); // RGBARGBARGBARGBA replace alpha with 255
+ vec_ste((vector unsigned int)jVecChar,0,(unsigned int *)&colors[i*4]); // store color
+ }
+}
+#endif
+
+static void RB_CalcDiffuseColor_scalar( unsigned char *colors )
+{
+ int i, j;
+ float *v, *normal;
+ float incoming;
+ trRefEntity_t *ent;
+ int ambientLightInt;
+ vec3_t ambientLight;
+ vec3_t lightDir;
+ vec3_t directedLight;
+ int numVertexes;
+ ent = backEnd.currentEntity;
+ ambientLightInt = ent->ambientLightInt;
+ VectorCopy( ent->ambientLight, ambientLight );
+ VectorCopy( ent->directedLight, directedLight );
+ VectorCopy( ent->lightDir, lightDir );
+
+ v = tess.xyz[0];
+ normal = tess.normal[0];
+
+ numVertexes = tess.numVertexes;
+ for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4) {
+ incoming = DotProduct (normal, lightDir);
+ if ( incoming <= 0 ) {
+ *(int *)&colors[i*4] = ambientLightInt;
+ continue;
+ }
+ j = myftol( ambientLight[0] + incoming * directedLight[0] );
+ if ( j > 255 ) {
+ j = 255;
+ }
+ colors[i*4+0] = j;
+
+ j = myftol( ambientLight[1] + incoming * directedLight[1] );
+ if ( j > 255 ) {
+ j = 255;
+ }
+ colors[i*4+1] = j;
+
+ j = myftol( ambientLight[2] + incoming * directedLight[2] );
+ if ( j > 255 ) {
+ j = 255;
+ }
+ colors[i*4+2] = j;
+
+ colors[i*4+3] = 255;
+ }
+}
+
+void RB_CalcDiffuseColor( unsigned char *colors )
+{
+#if idppc_altivec
+ if (com_altivec->integer) {
+ // must be in a seperate function or G3 systems will crash.
+ RB_CalcDiffuseColor_altivec( colors );
+ return;
+ }
+#endif
+ RB_CalcDiffuseColor_scalar( colors );
+}
+
diff --git a/code/renderer/tr_shader.c b/code/renderer/tr_shader.c
new file mode 100644
index 0000000..79b8aa0
--- /dev/null
+++ b/code/renderer/tr_shader.c
@@ -0,0 +1,3075 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#include "tr_local.h"
+
+// tr_shader.c -- this file deals with the parsing and definition of shaders
+
+static char *s_shaderText;
+
+// the shader is parsed into these global variables, then copied into
+// dynamically allocated memory if it is valid.
+static shaderStage_t stages[MAX_SHADER_STAGES];
+static shader_t shader;
+static texModInfo_t texMods[MAX_SHADER_STAGES][TR_MAX_TEXMODS];
+static qboolean deferLoad;
+
+#define FILE_HASH_SIZE 1024
+static shader_t* hashTable[FILE_HASH_SIZE];
+
+#define MAX_SHADERTEXT_HASH 2048
+static char **shaderTextHashTable[MAX_SHADERTEXT_HASH];
+
+/*
+================
+return a hash value for the filename
+================
+*/
+#ifdef __GNUCC__
+ #warning TODO: check if long is ok here
+#endif
+static long generateHashValue( const char *fname, const int size ) {
+ int i;
+ long hash;
+ char letter;
+
+ hash = 0;
+ i = 0;
+ while (fname[i] != '\0') {
+ letter = tolower(fname[i]);
+ if (letter =='.') break; // don't include extension
+ if (letter =='\\') letter = '/'; // damn path names
+ if (letter == PATH_SEP) letter = '/'; // damn path names
+ hash+=(long)(letter)*(i+119);
+ i++;
+ }
+ hash = (hash ^ (hash >> 10) ^ (hash >> 20));
+ hash &= (size-1);
+ return hash;
+}
+
+void R_RemapShader(const char *shaderName, const char *newShaderName, const char *timeOffset) {
+ char strippedName[MAX_QPATH];
+ int hash;
+ shader_t *sh, *sh2;
+ qhandle_t h;
+
+ sh = R_FindShaderByName( shaderName );
+ if (sh == NULL || sh == tr.defaultShader) {
+ h = RE_RegisterShaderLightMap(shaderName, 0);
+ sh = R_GetShaderByHandle(h);
+ }
+ if (sh == NULL || sh == tr.defaultShader) {
+ ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: shader %s not found\n", shaderName );
+ return;
+ }
+
+ sh2 = R_FindShaderByName( newShaderName );
+ if (sh2 == NULL || sh2 == tr.defaultShader) {
+ h = RE_RegisterShaderLightMap(newShaderName, 0);
+ sh2 = R_GetShaderByHandle(h);
+ }
+
+ if (sh2 == NULL || sh2 == tr.defaultShader) {
+ ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: new shader %s not found\n", newShaderName );
+ return;
+ }
+
+ // remap all the shaders with the given name
+ // even tho they might have different lightmaps
+ COM_StripExtension(shaderName, strippedName, sizeof(strippedName));
+ hash = generateHashValue(strippedName, FILE_HASH_SIZE);
+ for (sh = hashTable[hash]; sh; sh = sh->next) {
+ if (Q_stricmp(sh->name, strippedName) == 0) {
+ if (sh != sh2) {
+ sh->remappedShader = sh2;
+ } else {
+ sh->remappedShader = NULL;
+ }
+ }
+ }
+ if (timeOffset) {
+ sh2->timeOffset = atof(timeOffset);
+ }
+}
+
+/*
+===============
+ParseVector
+===============
+*/
+static qboolean ParseVector( char **text, int count, float *v ) {
+ char *token;
+ int i;
+
+ // FIXME: spaces are currently required after parens, should change parseext...
+ token = COM_ParseExt( text, qfalse );
+ if ( strcmp( token, "(" ) ) {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name );
+ return qfalse;
+ }
+
+ for ( i = 0 ; i < count ; i++ ) {
+ token = COM_ParseExt( text, qfalse );
+ if ( !token[0] ) {
+ ri.Printf( PRINT_WARNING, "WARNING: missing vector element in shader '%s'\n", shader.name );
+ return qfalse;
+ }
+ v[i] = atof( token );
+ }
+
+ token = COM_ParseExt( text, qfalse );
+ if ( strcmp( token, ")" ) ) {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name );
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+
+/*
+===============
+NameToAFunc
+===============
+*/
+static unsigned NameToAFunc( const char *funcname )
+{
+ if ( !Q_stricmp( funcname, "GT0" ) )
+ {
+ return GLS_ATEST_GT_0;
+ }
+ else if ( !Q_stricmp( funcname, "LT128" ) )
+ {
+ return GLS_ATEST_LT_80;
+ }
+ else if ( !Q_stricmp( funcname, "GE128" ) )
+ {
+ return GLS_ATEST_GE_80;
+ }
+
+ ri.Printf( PRINT_WARNING, "WARNING: invalid alphaFunc name '%s' in shader '%s'\n", funcname, shader.name );
+ return 0;
+}
+
+
+/*
+===============
+NameToSrcBlendMode
+===============
+*/
+static int NameToSrcBlendMode( const char *name )
+{
+ if ( !Q_stricmp( name, "GL_ONE" ) )
+ {
+ return GLS_SRCBLEND_ONE;
+ }
+ else if ( !Q_stricmp( name, "GL_ZERO" ) )
+ {
+ return GLS_SRCBLEND_ZERO;
+ }
+ else if ( !Q_stricmp( name, "GL_DST_COLOR" ) )
+ {
+ return GLS_SRCBLEND_DST_COLOR;
+ }
+ else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_COLOR" ) )
+ {
+ return GLS_SRCBLEND_ONE_MINUS_DST_COLOR;
+ }
+ else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) )
+ {
+ return GLS_SRCBLEND_SRC_ALPHA;
+ }
+ else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) )
+ {
+ return GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA;
+ }
+ else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) )
+ {
+ return GLS_SRCBLEND_DST_ALPHA;
+ }
+ else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) )
+ {
+ return GLS_SRCBLEND_ONE_MINUS_DST_ALPHA;
+ }
+ else if ( !Q_stricmp( name, "GL_SRC_ALPHA_SATURATE" ) )
+ {
+ return GLS_SRCBLEND_ALPHA_SATURATE;
+ }
+
+ ri.Printf( PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name );
+ return GLS_SRCBLEND_ONE;
+}
+
+/*
+===============
+NameToDstBlendMode
+===============
+*/
+static int NameToDstBlendMode( const char *name )
+{
+ if ( !Q_stricmp( name, "GL_ONE" ) )
+ {
+ return GLS_DSTBLEND_ONE;
+ }
+ else if ( !Q_stricmp( name, "GL_ZERO" ) )
+ {
+ return GLS_DSTBLEND_ZERO;
+ }
+ else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) )
+ {
+ return GLS_DSTBLEND_SRC_ALPHA;
+ }
+ else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) )
+ {
+ return GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
+ }
+ else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) )
+ {
+ return GLS_DSTBLEND_DST_ALPHA;
+ }
+ else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) )
+ {
+ return GLS_DSTBLEND_ONE_MINUS_DST_ALPHA;
+ }
+ else if ( !Q_stricmp( name, "GL_SRC_COLOR" ) )
+ {
+ return GLS_DSTBLEND_SRC_COLOR;
+ }
+ else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_COLOR" ) )
+ {
+ return GLS_DSTBLEND_ONE_MINUS_SRC_COLOR;
+ }
+
+ ri.Printf( PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name );
+ return GLS_DSTBLEND_ONE;
+}
+
+/*
+===============
+NameToGenFunc
+===============
+*/
+static genFunc_t NameToGenFunc( const char *funcname )
+{
+ if ( !Q_stricmp( funcname, "sin" ) )
+ {
+ return GF_SIN;
+ }
+ else if ( !Q_stricmp( funcname, "square" ) )
+ {
+ return GF_SQUARE;
+ }
+ else if ( !Q_stricmp( funcname, "triangle" ) )
+ {
+ return GF_TRIANGLE;
+ }
+ else if ( !Q_stricmp( funcname, "sawtooth" ) )
+ {
+ return GF_SAWTOOTH;
+ }
+ else if ( !Q_stricmp( funcname, "inversesawtooth" ) )
+ {
+ return GF_INVERSE_SAWTOOTH;
+ }
+ else if ( !Q_stricmp( funcname, "noise" ) )
+ {
+ return GF_NOISE;
+ }
+
+ ri.Printf( PRINT_WARNING, "WARNING: invalid genfunc name '%s' in shader '%s'\n", funcname, shader.name );
+ return GF_SIN;
+}
+
+
+/*
+===================
+ParseWaveForm
+===================
+*/
+static void ParseWaveForm( char **text, waveForm_t *wave )
+{
+ char *token;
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name );
+ return;
+ }
+ wave->func = NameToGenFunc( token );
+
+ // BASE, AMP, PHASE, FREQ
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name );
+ return;
+ }
+ wave->base = atof( token );
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name );
+ return;
+ }
+ wave->amplitude = atof( token );
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name );
+ return;
+ }
+ wave->phase = atof( token );
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name );
+ return;
+ }
+ wave->frequency = atof( token );
+}
+
+
+/*
+===================
+ParseTexMod
+===================
+*/
+static void ParseTexMod( char *_text, shaderStage_t *stage )
+{
+ const char *token;
+ char **text = &_text;
+ texModInfo_t *tmi;
+
+ if ( stage->bundle[0].numTexMods == TR_MAX_TEXMODS ) {
+ ri.Error( ERR_DROP, "ERROR: too many tcMod stages in shader '%s'\n", shader.name );
+ return;
+ }
+
+ tmi = &stage->bundle[0].texMods[stage->bundle[0].numTexMods];
+ stage->bundle[0].numTexMods++;
+
+ token = COM_ParseExt( text, qfalse );
+
+ //
+ // turb
+ //
+ if ( !Q_stricmp( token, "turb" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->wave.base = atof( token );
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->wave.amplitude = atof( token );
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->wave.phase = atof( token );
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->wave.frequency = atof( token );
+
+ tmi->type = TMOD_TURBULENT;
+ }
+ //
+ // scale
+ //
+ else if ( !Q_stricmp( token, "scale" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->scale[0] = atof( token );
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->scale[1] = atof( token );
+ tmi->type = TMOD_SCALE;
+ }
+ //
+ // scroll
+ //
+ else if ( !Q_stricmp( token, "scroll" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->scroll[0] = atof( token );
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->scroll[1] = atof( token );
+ tmi->type = TMOD_SCROLL;
+ }
+ //
+ // stretch
+ //
+ else if ( !Q_stricmp( token, "stretch" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->wave.func = NameToGenFunc( token );
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->wave.base = atof( token );
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->wave.amplitude = atof( token );
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->wave.phase = atof( token );
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->wave.frequency = atof( token );
+
+ tmi->type = TMOD_STRETCH;
+ }
+ //
+ // transform
+ //
+ else if ( !Q_stricmp( token, "transform" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->matrix[0][0] = atof( token );
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->matrix[0][1] = atof( token );
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->matrix[1][0] = atof( token );
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->matrix[1][1] = atof( token );
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->translate[0] = atof( token );
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->translate[1] = atof( token );
+
+ tmi->type = TMOD_TRANSFORM;
+ }
+ //
+ // rotate
+ //
+ else if ( !Q_stricmp( token, "rotate" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing tcMod rotate parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->rotateSpeed = atof( token );
+ tmi->type = TMOD_ROTATE;
+ }
+ //
+ // entityTranslate
+ //
+ else if ( !Q_stricmp( token, "entityTranslate" ) )
+ {
+ tmi->type = TMOD_ENTITY_TRANSLATE;
+ }
+ else
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: unknown tcMod '%s' in shader '%s'\n", token, shader.name );
+ }
+}
+
+
+/*
+===================
+ParseStage
+===================
+*/
+static qboolean ParseStage( shaderStage_t *stage, char **text )
+{
+ char *token;
+ int depthMaskBits = GLS_DEPTHMASK_TRUE, blendSrcBits = 0, blendDstBits = 0, atestBits = 0, depthFuncBits = 0;
+ qboolean depthMaskExplicit = qfalse;
+
+ stage->active = qtrue;
+
+ while ( 1 )
+ {
+ token = COM_ParseExt( text, qtrue );
+ if ( !token[0] )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: no matching '}' found\n" );
+ return qfalse;
+ }
+
+ if ( token[0] == '}' )
+ {
+ break;
+ }
+ //
+ // map <name>
+ //
+ else if ( !Q_stricmp( token, "map" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( !token[0] )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'map' keyword in shader '%s'\n", shader.name );
+ return qfalse;
+ }
+
+ if ( !Q_stricmp( token, "$whiteimage" ) )
+ {
+ stage->bundle[0].image[0] = tr.whiteImage;
+ continue;
+ }
+ else if ( !Q_stricmp( token, "$lightmap" ) )
+ {
+ stage->bundle[0].isLightmap = qtrue;
+ if ( shader.lightmapIndex < 0 ) {
+ stage->bundle[0].image[0] = tr.whiteImage;
+ } else {
+ stage->bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex];
+ }
+ continue;
+ }
+ else
+ {
+ stage->bundle[0].image[0] = R_FindImageFile( token, !shader.noMipMaps, !shader.noPicMip, GL_REPEAT );
+ if ( !stage->bundle[0].image[0] )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name );
+ return qfalse;
+ }
+ }
+ }
+ //
+ // clampmap <name>
+ //
+ else if ( !Q_stricmp( token, "clampmap" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( !token[0] )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'clampmap' keyword in shader '%s'\n", shader.name );
+ return qfalse;
+ }
+
+ stage->bundle[0].image[0] = R_FindImageFile( token, !shader.noMipMaps, !shader.noPicMip, GL_CLAMP_TO_EDGE );
+ if ( !stage->bundle[0].image[0] )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name );
+ return qfalse;
+ }
+ }
+ //
+ // animMap <frequency> <image1> .... <imageN>
+ //
+ else if ( !Q_stricmp( token, "animMap" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( !token[0] )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'animMmap' keyword in shader '%s'\n", shader.name );
+ return qfalse;
+ }
+ stage->bundle[0].imageAnimationSpeed = atof( token );
+
+ // parse up to MAX_IMAGE_ANIMATIONS animations
+ while ( 1 ) {
+ int num;
+
+ token = COM_ParseExt( text, qfalse );
+ if ( !token[0] ) {
+ break;
+ }
+ num = stage->bundle[0].numImageAnimations;
+ if ( num < MAX_IMAGE_ANIMATIONS ) {
+ stage->bundle[0].image[num] = R_FindImageFile( token, !shader.noMipMaps, !shader.noPicMip, GL_REPEAT );
+ if ( !stage->bundle[0].image[num] )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name );
+ return qfalse;
+ }
+ stage->bundle[0].numImageAnimations++;
+ }
+ }
+ }
+ else if ( !Q_stricmp( token, "videoMap" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( !token[0] )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'videoMmap' keyword in shader '%s'\n", shader.name );
+ return qfalse;
+ }
+ stage->bundle[0].videoMapHandle = ri.CIN_PlayCinematic( token, 0, 0, 256, 256, (CIN_loop | CIN_silent | CIN_shader));
+ if (stage->bundle[0].videoMapHandle != -1) {
+ stage->bundle[0].isVideoMap = qtrue;
+ stage->bundle[0].image[0] = tr.scratchImage[stage->bundle[0].videoMapHandle];
+ }
+ }
+ //
+ // alphafunc <func>
+ //
+ else if ( !Q_stricmp( token, "alphaFunc" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( !token[0] )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'alphaFunc' keyword in shader '%s'\n", shader.name );
+ return qfalse;
+ }
+
+ atestBits = NameToAFunc( token );
+ }
+ //
+ // depthFunc <func>
+ //
+ else if ( !Q_stricmp( token, "depthfunc" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+
+ if ( !token[0] )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'depthfunc' keyword in shader '%s'\n", shader.name );
+ return qfalse;
+ }
+
+ if ( !Q_stricmp( token, "lequal" ) )
+ {
+ depthFuncBits = 0;
+ }
+ else if ( !Q_stricmp( token, "equal" ) )
+ {
+ depthFuncBits = GLS_DEPTHFUNC_EQUAL;
+ }
+ else
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: unknown depthfunc '%s' in shader '%s'\n", token, shader.name );
+ continue;
+ }
+ }
+ //
+ // detail
+ //
+ else if ( !Q_stricmp( token, "detail" ) )
+ {
+ stage->isDetail = qtrue;
+ }
+ //
+ // blendfunc <srcFactor> <dstFactor>
+ // or blendfunc <add|filter|blend>
+ //
+ else if ( !Q_stricmp( token, "blendfunc" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name );
+ continue;
+ }
+ // check for "simple" blends first
+ if ( !Q_stricmp( token, "add" ) ) {
+ blendSrcBits = GLS_SRCBLEND_ONE;
+ blendDstBits = GLS_DSTBLEND_ONE;
+ } else if ( !Q_stricmp( token, "filter" ) ) {
+ blendSrcBits = GLS_SRCBLEND_DST_COLOR;
+ blendDstBits = GLS_DSTBLEND_ZERO;
+ } else if ( !Q_stricmp( token, "blend" ) ) {
+ blendSrcBits = GLS_SRCBLEND_SRC_ALPHA;
+ blendDstBits = GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
+ } else {
+ // complex double blends
+ blendSrcBits = NameToSrcBlendMode( token );
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name );
+ continue;
+ }
+ blendDstBits = NameToDstBlendMode( token );
+ }
+
+ // clear depth mask for blended surfaces
+ if ( !depthMaskExplicit )
+ {
+ depthMaskBits = 0;
+ }
+ }
+ //
+ // rgbGen
+ //
+ else if ( !Q_stricmp( token, "rgbGen" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parameters for rgbGen in shader '%s'\n", shader.name );
+ continue;
+ }
+
+ if ( !Q_stricmp( token, "wave" ) )
+ {
+ ParseWaveForm( text, &stage->rgbWave );
+ stage->rgbGen = CGEN_WAVEFORM;
+ }
+ else if ( !Q_stricmp( token, "const" ) )
+ {
+ vec3_t color;
+
+ ParseVector( text, 3, color );
+ stage->constantColor[0] = 255 * color[0];
+ stage->constantColor[1] = 255 * color[1];
+ stage->constantColor[2] = 255 * color[2];
+
+ stage->rgbGen = CGEN_CONST;
+ }
+ else if ( !Q_stricmp( token, "identity" ) )
+ {
+ stage->rgbGen = CGEN_IDENTITY;
+ }
+ else if ( !Q_stricmp( token, "identityLighting" ) )
+ {
+ stage->rgbGen = CGEN_IDENTITY_LIGHTING;
+ }
+ else if ( !Q_stricmp( token, "entity" ) )
+ {
+ stage->rgbGen = CGEN_ENTITY;
+ }
+ else if ( !Q_stricmp( token, "oneMinusEntity" ) )
+ {
+ stage->rgbGen = CGEN_ONE_MINUS_ENTITY;
+ }
+ else if ( !Q_stricmp( token, "vertex" ) )
+ {
+ stage->rgbGen = CGEN_VERTEX;
+ if ( stage->alphaGen == 0 ) {
+ stage->alphaGen = AGEN_VERTEX;
+ }
+ }
+ else if ( !Q_stricmp( token, "exactVertex" ) )
+ {
+ stage->rgbGen = CGEN_EXACT_VERTEX;
+ }
+ else if ( !Q_stricmp( token, "lightingDiffuse" ) )
+ {
+ stage->rgbGen = CGEN_LIGHTING_DIFFUSE;
+ }
+ else if ( !Q_stricmp( token, "oneMinusVertex" ) )
+ {
+ stage->rgbGen = CGEN_ONE_MINUS_VERTEX;
+ }
+ else
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: unknown rgbGen parameter '%s' in shader '%s'\n", token, shader.name );
+ continue;
+ }
+ }
+ //
+ // alphaGen
+ //
+ else if ( !Q_stricmp( token, "alphaGen" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parameters for alphaGen in shader '%s'\n", shader.name );
+ continue;
+ }
+
+ if ( !Q_stricmp( token, "wave" ) )
+ {
+ ParseWaveForm( text, &stage->alphaWave );
+ stage->alphaGen = AGEN_WAVEFORM;
+ }
+ else if ( !Q_stricmp( token, "const" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ stage->constantColor[3] = 255 * atof( token );
+ stage->alphaGen = AGEN_CONST;
+ }
+ else if ( !Q_stricmp( token, "identity" ) )
+ {
+ stage->alphaGen = AGEN_IDENTITY;
+ }
+ else if ( !Q_stricmp( token, "entity" ) )
+ {
+ stage->alphaGen = AGEN_ENTITY;
+ }
+ else if ( !Q_stricmp( token, "oneMinusEntity" ) )
+ {
+ stage->alphaGen = AGEN_ONE_MINUS_ENTITY;
+ }
+ else if ( !Q_stricmp( token, "vertex" ) )
+ {
+ stage->alphaGen = AGEN_VERTEX;
+ }
+ else if ( !Q_stricmp( token, "lightingSpecular" ) )
+ {
+ stage->alphaGen = AGEN_LIGHTING_SPECULAR;
+ }
+ else if ( !Q_stricmp( token, "oneMinusVertex" ) )
+ {
+ stage->alphaGen = AGEN_ONE_MINUS_VERTEX;
+ }
+ else if ( !Q_stricmp( token, "portal" ) )
+ {
+ stage->alphaGen = AGEN_PORTAL;
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ shader.portalRange = 256;
+ ri.Printf( PRINT_WARNING, "WARNING: missing range parameter for alphaGen portal in shader '%s', defaulting to 256\n", shader.name );
+ }
+ else
+ {
+ shader.portalRange = atof( token );
+ }
+ }
+ else
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: unknown alphaGen parameter '%s' in shader '%s'\n", token, shader.name );
+ continue;
+ }
+ }
+ //
+ // tcGen <function>
+ //
+ else if ( !Q_stricmp(token, "texgen") || !Q_stricmp( token, "tcGen" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing texgen parm in shader '%s'\n", shader.name );
+ continue;
+ }
+
+ if ( !Q_stricmp( token, "environment" ) )
+ {
+ stage->bundle[0].tcGen = TCGEN_ENVIRONMENT_MAPPED;
+ }
+ else if ( !Q_stricmp( token, "lightmap" ) )
+ {
+ stage->bundle[0].tcGen = TCGEN_LIGHTMAP;
+ }
+ else if ( !Q_stricmp( token, "texture" ) || !Q_stricmp( token, "base" ) )
+ {
+ stage->bundle[0].tcGen = TCGEN_TEXTURE;
+ }
+ else if ( !Q_stricmp( token, "vector" ) )
+ {
+ ParseVector( text, 3, stage->bundle[0].tcGenVectors[0] );
+ ParseVector( text, 3, stage->bundle[0].tcGenVectors[1] );
+
+ stage->bundle[0].tcGen = TCGEN_VECTOR;
+ }
+ else
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: unknown texgen parm in shader '%s'\n", shader.name );
+ }
+ }
+ //
+ // tcMod <type> <...>
+ //
+ else if ( !Q_stricmp( token, "tcMod" ) )
+ {
+ char buffer[1024] = "";
+
+ while ( 1 )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ break;
+ strcat( buffer, token );
+ strcat( buffer, " " );
+ }
+
+ ParseTexMod( buffer, stage );
+
+ continue;
+ }
+ //
+ // depthmask
+ //
+ else if ( !Q_stricmp( token, "depthwrite" ) )
+ {
+ depthMaskBits = GLS_DEPTHMASK_TRUE;
+ depthMaskExplicit = qtrue;
+
+ continue;
+ }
+ else
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: unknown parameter '%s' in shader '%s'\n", token, shader.name );
+ return qfalse;
+ }
+ }
+
+ //
+ // if cgen isn't explicitly specified, use either identity or identitylighting
+ //
+ if ( stage->rgbGen == CGEN_BAD ) {
+ if ( blendSrcBits == 0 ||
+ blendSrcBits == GLS_SRCBLEND_ONE ||
+ blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) {
+ stage->rgbGen = CGEN_IDENTITY_LIGHTING;
+ } else {
+ stage->rgbGen = CGEN_IDENTITY;
+ }
+ }
+
+
+ //
+ // implicitly assume that a GL_ONE GL_ZERO blend mask disables blending
+ //
+ if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) &&
+ ( blendDstBits == GLS_DSTBLEND_ZERO ) )
+ {
+ blendDstBits = blendSrcBits = 0;
+ depthMaskBits = GLS_DEPTHMASK_TRUE;
+ }
+
+ // decide which agens we can skip
+ if ( stage->alphaGen == CGEN_IDENTITY ) {
+ if ( stage->rgbGen == CGEN_IDENTITY
+ || stage->rgbGen == CGEN_LIGHTING_DIFFUSE ) {
+ stage->alphaGen = AGEN_SKIP;
+ }
+ }
+
+ //
+ // compute state bits
+ //
+ stage->stateBits = depthMaskBits |
+ blendSrcBits | blendDstBits |
+ atestBits |
+ depthFuncBits;
+
+ return qtrue;
+}
+
+/*
+===============
+ParseDeform
+
+deformVertexes wave <spread> <waveform> <base> <amplitude> <phase> <frequency>
+deformVertexes normal <frequency> <amplitude>
+deformVertexes move <vector> <waveform> <base> <amplitude> <phase> <frequency>
+deformVertexes bulge <bulgeWidth> <bulgeHeight> <bulgeSpeed>
+deformVertexes projectionShadow
+deformVertexes autoSprite
+deformVertexes autoSprite2
+deformVertexes text[0-7]
+===============
+*/
+static void ParseDeform( char **text ) {
+ char *token;
+ deformStage_t *ds;
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing deform parm in shader '%s'\n", shader.name );
+ return;
+ }
+
+ if ( shader.numDeforms == MAX_SHADER_DEFORMS ) {
+ ri.Printf( PRINT_WARNING, "WARNING: MAX_SHADER_DEFORMS in '%s'\n", shader.name );
+ return;
+ }
+
+ ds = &shader.deforms[ shader.numDeforms ];
+ shader.numDeforms++;
+
+ if ( !Q_stricmp( token, "projectionShadow" ) ) {
+ ds->deformation = DEFORM_PROJECTION_SHADOW;
+ return;
+ }
+
+ if ( !Q_stricmp( token, "autosprite" ) ) {
+ ds->deformation = DEFORM_AUTOSPRITE;
+ return;
+ }
+
+ if ( !Q_stricmp( token, "autosprite2" ) ) {
+ ds->deformation = DEFORM_AUTOSPRITE2;
+ return;
+ }
+
+ if ( !Q_stricmpn( token, "text", 4 ) ) {
+ int n;
+
+ n = token[4] - '0';
+ if ( n < 0 || n > 7 ) {
+ n = 0;
+ }
+ ds->deformation = DEFORM_TEXT0 + n;
+ return;
+ }
+
+ if ( !Q_stricmp( token, "bulge" ) ) {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name );
+ return;
+ }
+ ds->bulgeWidth = atof( token );
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name );
+ return;
+ }
+ ds->bulgeHeight = atof( token );
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name );
+ return;
+ }
+ ds->bulgeSpeed = atof( token );
+
+ ds->deformation = DEFORM_BULGE;
+ return;
+ }
+
+ if ( !Q_stricmp( token, "wave" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name );
+ return;
+ }
+
+ if ( atof( token ) != 0 )
+ {
+ ds->deformationSpread = 1.0f / atof( token );
+ }
+ else
+ {
+ ds->deformationSpread = 100.0f;
+ ri.Printf( PRINT_WARNING, "WARNING: illegal div value of 0 in deformVertexes command for shader '%s'\n", shader.name );
+ }
+
+ ParseWaveForm( text, &ds->deformationWave );
+ ds->deformation = DEFORM_WAVE;
+ return;
+ }
+
+ if ( !Q_stricmp( token, "normal" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name );
+ return;
+ }
+ ds->deformationWave.amplitude = atof( token );
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name );
+ return;
+ }
+ ds->deformationWave.frequency = atof( token );
+
+ ds->deformation = DEFORM_NORMALS;
+ return;
+ }
+
+ if ( !Q_stricmp( token, "move" ) ) {
+ int i;
+
+ for ( i = 0 ; i < 3 ; i++ ) {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 ) {
+ ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name );
+ return;
+ }
+ ds->moveVector[i] = atof( token );
+ }
+
+ ParseWaveForm( text, &ds->deformationWave );
+ ds->deformation = DEFORM_MOVE;
+ return;
+ }
+
+ ri.Printf( PRINT_WARNING, "WARNING: unknown deformVertexes subtype '%s' found in shader '%s'\n", token, shader.name );
+}
+
+
+/*
+===============
+ParseSkyParms
+
+skyParms <outerbox> <cloudheight> <innerbox>
+===============
+*/
+static void ParseSkyParms( char **text ) {
+ char *token;
+ static char *suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"};
+ char pathname[MAX_QPATH];
+ int i;
+
+ // outerbox
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 ) {
+ ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name );
+ return;
+ }
+ if ( strcmp( token, "-" ) ) {
+ for (i=0 ; i<6 ; i++) {
+ Com_sprintf( pathname, sizeof(pathname), "%s_%s.tga"
+ , token, suf[i] );
+ shader.sky.outerbox[i] = R_FindImageFile( ( char * ) pathname, qtrue, qtrue, GL_CLAMP_TO_EDGE );
+
+ if ( !shader.sky.outerbox[i] ) {
+ shader.sky.outerbox[i] = tr.defaultImage;
+ }
+ }
+ }
+
+ // cloudheight
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 ) {
+ ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name );
+ return;
+ }
+ shader.sky.cloudHeight = atof( token );
+ if ( !shader.sky.cloudHeight ) {
+ shader.sky.cloudHeight = 512;
+ }
+ R_InitSkyTexCoords( shader.sky.cloudHeight );
+
+
+ // innerbox
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 ) {
+ ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name );
+ return;
+ }
+ if ( strcmp( token, "-" ) ) {
+ for (i=0 ; i<6 ; i++) {
+ Com_sprintf( pathname, sizeof(pathname), "%s_%s.tga"
+ , token, suf[i] );
+ shader.sky.innerbox[i] = R_FindImageFile( ( char * ) pathname, qtrue, qtrue, GL_REPEAT );
+ if ( !shader.sky.innerbox[i] ) {
+ shader.sky.innerbox[i] = tr.defaultImage;
+ }
+ }
+ }
+
+ shader.isSky = qtrue;
+}
+
+
+/*
+=================
+ParseSort
+=================
+*/
+void ParseSort( char **text ) {
+ char *token;
+
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 ) {
+ ri.Printf( PRINT_WARNING, "WARNING: missing sort parameter in shader '%s'\n", shader.name );
+ return;
+ }
+
+ if ( !Q_stricmp( token, "portal" ) ) {
+ shader.sort = SS_PORTAL;
+ } else if ( !Q_stricmp( token, "sky" ) ) {
+ shader.sort = SS_ENVIRONMENT;
+ } else if ( !Q_stricmp( token, "opaque" ) ) {
+ shader.sort = SS_OPAQUE;
+ }else if ( !Q_stricmp( token, "decal" ) ) {
+ shader.sort = SS_DECAL;
+ } else if ( !Q_stricmp( token, "seeThrough" ) ) {
+ shader.sort = SS_SEE_THROUGH;
+ } else if ( !Q_stricmp( token, "banner" ) ) {
+ shader.sort = SS_BANNER;
+ } else if ( !Q_stricmp( token, "additive" ) ) {
+ shader.sort = SS_BLEND1;
+ } else if ( !Q_stricmp( token, "nearest" ) ) {
+ shader.sort = SS_NEAREST;
+ } else if ( !Q_stricmp( token, "underwater" ) ) {
+ shader.sort = SS_UNDERWATER;
+ } else {
+ shader.sort = atof( token );
+ }
+}
+
+
+
+// this table is also present in q3map
+
+typedef struct {
+ char *name;
+ int clearSolid, surfaceFlags, contents;
+} infoParm_t;
+
+infoParm_t infoParms[] = {
+ // server relevant contents
+ {"water", 1, 0, CONTENTS_WATER },
+ {"slime", 1, 0, CONTENTS_SLIME }, // mildly damaging
+ {"lava", 1, 0, CONTENTS_LAVA }, // very damaging
+ {"playerclip", 1, 0, CONTENTS_PLAYERCLIP },
+ {"monsterclip", 1, 0, CONTENTS_MONSTERCLIP },
+ {"nodrop", 1, 0, CONTENTS_NODROP }, // don't drop items or leave bodies (death fog, lava, etc)
+ {"nonsolid", 1, SURF_NONSOLID, 0}, // clears the solid flag
+
+ // utility relevant attributes
+ {"origin", 1, 0, CONTENTS_ORIGIN }, // center of rotating brushes
+ {"trans", 0, 0, CONTENTS_TRANSLUCENT }, // don't eat contained surfaces
+ {"detail", 0, 0, CONTENTS_DETAIL }, // don't include in structural bsp
+ {"structural", 0, 0, CONTENTS_STRUCTURAL }, // force into structural bsp even if trnas
+ {"areaportal", 1, 0, CONTENTS_AREAPORTAL }, // divides areas
+ {"clusterportal", 1,0, CONTENTS_CLUSTERPORTAL }, // for bots
+ {"donotenter", 1, 0, CONTENTS_DONOTENTER }, // for bots
+
+ {"fog", 1, 0, CONTENTS_FOG}, // carves surfaces entering
+ {"sky", 0, SURF_SKY, 0 }, // emit light from an environment map
+ {"lightfilter", 0, SURF_LIGHTFILTER, 0 }, // filter light going through it
+ {"alphashadow", 0, SURF_ALPHASHADOW, 0 }, // test light on a per-pixel basis
+ {"hint", 0, SURF_HINT, 0 }, // use as a primary splitter
+
+ // server attributes
+ {"slick", 0, SURF_SLICK, 0 },
+ {"noimpact", 0, SURF_NOIMPACT, 0 }, // don't make impact explosions or marks
+ {"nomarks", 0, SURF_NOMARKS, 0 }, // don't make impact marks, but still explode
+ {"ladder", 0, SURF_LADDER, 0 },
+ {"nodamage", 0, SURF_NODAMAGE, 0 },
+ {"metalsteps", 0, SURF_METALSTEPS,0 },
+ {"flesh", 0, SURF_FLESH, 0 },
+ {"nosteps", 0, SURF_NOSTEPS, 0 },
+
+ // drawsurf attributes
+ {"nodraw", 0, SURF_NODRAW, 0 }, // don't generate a drawsurface (or a lightmap)
+ {"pointlight", 0, SURF_POINTLIGHT, 0 }, // sample lighting at vertexes
+ {"nolightmap", 0, SURF_NOLIGHTMAP,0 }, // don't generate a lightmap
+ {"nodlight", 0, SURF_NODLIGHT, 0 }, // don't ever add dynamic lights
+ {"dust", 0, SURF_DUST, 0} // leave a dust trail when walking on this surface
+};
+
+
+/*
+===============
+ParseSurfaceParm
+
+surfaceparm <name>
+===============
+*/
+static void ParseSurfaceParm( char **text ) {
+ char *token;
+ int numInfoParms = sizeof(infoParms) / sizeof(infoParms[0]);
+ int i;
+
+ token = COM_ParseExt( text, qfalse );
+ for ( i = 0 ; i < numInfoParms ; i++ ) {
+ if ( !Q_stricmp( token, infoParms[i].name ) ) {
+ shader.surfaceFlags |= infoParms[i].surfaceFlags;
+ shader.contentFlags |= infoParms[i].contents;
+#if 0
+ if ( infoParms[i].clearSolid ) {
+ si->contents &= ~CONTENTS_SOLID;
+ }
+#endif
+ break;
+ }
+ }
+}
+
+/*
+=================
+ParseShader
+
+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.
+=================
+*/
+static qboolean ParseShader( char **text )
+{
+ char *token;
+ int s;
+
+ s = 0;
+
+ token = COM_ParseExt( text, qtrue );
+ if ( token[0] != '{' )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader.name );
+ return qfalse;
+ }
+
+ while ( 1 )
+ {
+ token = COM_ParseExt( text, qtrue );
+ if ( !token[0] )
+ {
+ ri.Printf( PRINT_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 ) {
+ ri.Printf( PRINT_WARNING, "WARNING: too many stages in shader %s\n", shader.name );
+ return qfalse;
+ }
+
+ if ( !ParseStage( &stages[s], 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;
+ }
+ // sun parms
+ else if ( !Q_stricmp( token, "q3map_sun" ) ) {
+ float a, b;
+
+ token = COM_ParseExt( text, qfalse );
+ tr.sunLight[0] = atof( token );
+ token = COM_ParseExt( text, qfalse );
+ tr.sunLight[1] = atof( token );
+ token = COM_ParseExt( text, qfalse );
+ tr.sunLight[2] = atof( token );
+
+ VectorNormalize( tr.sunLight );
+
+ token = COM_ParseExt( text, qfalse );
+ a = atof( token );
+ VectorScale( tr.sunLight, a, tr.sunLight);
+
+ token = COM_ParseExt( text, qfalse );
+ a = atof( token );
+ a = a / 180 * M_PI;
+
+ token = COM_ParseExt( text, qfalse );
+ 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 );
+ }
+ else if ( !Q_stricmp( token, "deformVertexes" ) ) {
+ ParseDeform( text );
+ continue;
+ }
+ else if ( !Q_stricmp( token, "tesssize" ) ) {
+ SkipRestOfLine( text );
+ continue;
+ }
+ else if ( !Q_stricmp( token, "clampTime" ) ) {
+ token = COM_ParseExt( text, qfalse );
+ if (token[0]) {
+ shader.clampTime = atof(token);
+ }
+ }
+ // skip stuff that only the q3map needs
+ else if ( !Q_stricmpn( token, "q3map", 5 ) ) {
+ SkipRestOfLine( text );
+ continue;
+ }
+ // skip stuff that only q3map or the server needs
+ else if ( !Q_stricmp( token, "surfaceParm" ) ) {
+ ParseSurfaceParm( text );
+ continue;
+ }
+ // no mip maps
+ else if ( !Q_stricmp( token, "nomipmaps" ) )
+ {
+ shader.noMipMaps = qtrue;
+ shader.noPicMip = qtrue;
+ continue;
+ }
+ // no picmip adjustment
+ else if ( !Q_stricmp( token, "nopicmip" ) )
+ {
+ shader.noPicMip = qtrue;
+ continue;
+ }
+ // polygonOffset
+ else if ( !Q_stricmp( token, "polygonOffset" ) )
+ {
+ shader.polygonOffset = 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;
+ }
+
+ token = COM_ParseExt( text, qfalse );
+ if ( !token[0] )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parm for 'fogParms' keyword in shader '%s'\n", shader.name );
+ continue;
+ }
+ shader.fogParms.depthForOpaque = atof( token );
+
+ // skip any old gradient directions
+ SkipRestOfLine( text );
+ continue;
+ }
+ // portal
+ else if ( !Q_stricmp(token, "portal") )
+ {
+ shader.sort = SS_PORTAL;
+ continue;
+ }
+ // skyparms <cloudheight> <outerbox> <innerbox>
+ else if ( !Q_stricmp( token, "skyparms" ) )
+ {
+ ParseSkyParms( text );
+ continue;
+ }
+ // light <value> determines flaring in q3map, not needed here
+ else if ( !Q_stricmp(token, "light") )
+ {
+ token = COM_ParseExt( text, qfalse );
+ continue;
+ }
+ // cull <face>
+ else if ( !Q_stricmp( token, "cull") )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_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
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: invalid cull parm '%s' in shader '%s'\n", token, shader.name );
+ }
+ continue;
+ }
+ // sort
+ else if ( !Q_stricmp( token, "sort" ) )
+ {
+ ParseSort( text );
+ continue;
+ }
+ else
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: unknown general shader parameter '%s' in '%s'\n", token, shader.name );
+ return qfalse;
+ }
+ }
+
+ //
+ // ignore shaders that don't have any stages, unless it is a sky or fog
+ //
+ if ( s == 0 && !shader.isSky && !(shader.contentFlags & CONTENTS_FOG ) ) {
+ return qfalse;
+ }
+
+ shader.explicitlyDefined = qtrue;
+
+ return qtrue;
+}
+
+/*
+========================================================================================
+
+SHADER OPTIMIZATION AND FOGGING
+
+========================================================================================
+*/
+
+/*
+===================
+ComputeStageIteratorFunc
+
+See if we can use on of the simple fastpath stage functions,
+otherwise set to the generic stage function
+===================
+*/
+static void ComputeStageIteratorFunc( void )
+{
+ shader.optimalStageIteratorFunc = RB_StageIteratorGeneric;
+
+ //
+ // see if this should go into the sky path
+ //
+ if ( shader.isSky )
+ {
+ shader.optimalStageIteratorFunc = RB_StageIteratorSky;
+ goto done;
+ }
+
+ if ( r_ignoreFastPath->integer )
+ {
+ return;
+ }
+
+ //
+ // see if this can go into the vertex lit fast path
+ //
+ if ( shader.numUnfoggedPasses == 1 )
+ {
+ if ( stages[0].rgbGen == CGEN_LIGHTING_DIFFUSE )
+ {
+ if ( stages[0].alphaGen == AGEN_IDENTITY )
+ {
+ if ( stages[0].bundle[0].tcGen == TCGEN_TEXTURE )
+ {
+ if ( !shader.polygonOffset )
+ {
+ if ( !shader.multitextureEnv )
+ {
+ if ( !shader.numDeforms )
+ {
+ shader.optimalStageIteratorFunc = RB_StageIteratorVertexLitTexture;
+ goto done;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //
+ // see if this can go into an optimized LM, multitextured path
+ //
+ if ( shader.numUnfoggedPasses == 1 )
+ {
+ if ( ( stages[0].rgbGen == CGEN_IDENTITY ) && ( stages[0].alphaGen == AGEN_IDENTITY ) )
+ {
+ if ( stages[0].bundle[0].tcGen == TCGEN_TEXTURE &&
+ stages[0].bundle[1].tcGen == TCGEN_LIGHTMAP )
+ {
+ if ( !shader.polygonOffset )
+ {
+ if ( !shader.numDeforms )
+ {
+ if ( shader.multitextureEnv )
+ {
+ shader.optimalStageIteratorFunc = RB_StageIteratorLightmappedMultitexture;
+ goto done;
+ }
+ }
+ }
+ }
+ }
+ }
+
+done:
+ return;
+}
+
+typedef struct {
+ int blendA;
+ int blendB;
+
+ int multitextureEnv;
+ int multitextureBlend;
+} collapse_t;
+
+static collapse_t collapse[] = {
+ { 0, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO,
+ GL_MODULATE, 0 },
+
+ { 0, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR,
+ GL_MODULATE, 0 },
+
+ { GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR,
+ GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR },
+
+ { GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR,
+ GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR },
+
+ { GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO,
+ GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR },
+
+ { GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO,
+ GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR },
+
+ { 0, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE,
+ GL_ADD, 0 },
+
+ { GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE,
+ GL_ADD, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE },
+#if 0
+ { 0, GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_SRCBLEND_SRC_ALPHA,
+ GL_DECAL, 0 },
+#endif
+ { -1 }
+};
+
+/*
+================
+CollapseMultitexture
+
+Attempt to combine two stages into a single multitexture stage
+FIXME: I think modulated add + modulated add collapses incorrectly
+=================
+*/
+static qboolean CollapseMultitexture( void ) {
+ int abits, bbits;
+ int i;
+ textureBundle_t tmpBundle;
+
+ if ( !qglActiveTextureARB ) {
+ return qfalse;
+ }
+
+ // make sure both stages are active
+ if ( !stages[0].active || !stages[1].active ) {
+ return qfalse;
+ }
+
+ // on voodoo2, don't combine different tmus
+ if ( glConfig.driverType == GLDRV_VOODOO ) {
+ if ( stages[0].bundle[0].image[0]->TMU ==
+ stages[1].bundle[0].image[0]->TMU ) {
+ return qfalse;
+ }
+ }
+
+ abits = stages[0].stateBits;
+ bbits = stages[1].stateBits;
+
+ // make sure that both stages have identical state other than blend modes
+ if ( ( abits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) !=
+ ( bbits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) ) {
+ return qfalse;
+ }
+
+ abits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS );
+ bbits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS );
+
+ // search for a valid multitexture blend function
+ for ( i = 0; collapse[i].blendA != -1 ; i++ ) {
+ if ( abits == collapse[i].blendA
+ && bbits == collapse[i].blendB ) {
+ break;
+ }
+ }
+
+ // nothing found
+ if ( collapse[i].blendA == -1 ) {
+ return qfalse;
+ }
+
+ // GL_ADD is a separate extension
+ if ( collapse[i].multitextureEnv == GL_ADD && !glConfig.textureEnvAddAvailable ) {
+ return qfalse;
+ }
+
+ // make sure waveforms have identical parameters
+ if ( ( stages[0].rgbGen != stages[1].rgbGen ) ||
+ ( stages[0].alphaGen != stages[1].alphaGen ) ) {
+ return qfalse;
+ }
+
+ // an add collapse can only have identity colors
+ if ( collapse[i].multitextureEnv == GL_ADD && stages[0].rgbGen != CGEN_IDENTITY ) {
+ return qfalse;
+ }
+
+ if ( stages[0].rgbGen == CGEN_WAVEFORM )
+ {
+ if ( memcmp( &stages[0].rgbWave,
+ &stages[1].rgbWave,
+ sizeof( stages[0].rgbWave ) ) )
+ {
+ return qfalse;
+ }
+ }
+ if ( stages[0].alphaGen == CGEN_WAVEFORM )
+ {
+ if ( memcmp( &stages[0].alphaWave,
+ &stages[1].alphaWave,
+ sizeof( stages[0].alphaWave ) ) )
+ {
+ return qfalse;
+ }
+ }
+
+
+ // make sure that lightmaps are in bundle 1 for 3dfx
+ if ( stages[0].bundle[0].isLightmap )
+ {
+ tmpBundle = stages[0].bundle[0];
+ stages[0].bundle[0] = stages[1].bundle[0];
+ stages[0].bundle[1] = tmpBundle;
+ }
+ else
+ {
+ stages[0].bundle[1] = stages[1].bundle[0];
+ }
+
+ // set the new blend state bits
+ shader.multitextureEnv = collapse[i].multitextureEnv;
+ stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS );
+ stages[0].stateBits |= collapse[i].multitextureBlend;
+
+ //
+ // move down subsequent shaders
+ //
+ memmove( &stages[1], &stages[2], sizeof( stages[0] ) * ( MAX_SHADER_STAGES - 2 ) );
+ Com_Memset( &stages[MAX_SHADER_STAGES-1], 0, sizeof( stages[0] ) );
+
+ return qtrue;
+}
+
+/*
+=============
+
+FixRenderCommandList
+https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=493
+Arnout: this is a nasty issue. Shaders can be registered after drawsurfaces are generated
+but before the frame is rendered. This will, for the duration of one frame, cause drawsurfaces
+to be rendered with bad shaders. To fix this, need to go through all render commands and fix
+sortedIndex.
+==============
+*/
+static void FixRenderCommandList( int newShader ) {
+ renderCommandList_t *cmdList = &backEndData[tr.smpFrame]->commands;
+
+ if( cmdList ) {
+ const void *curCmd = cmdList->cmds;
+
+ while ( 1 ) {
+ switch ( *(const int *)curCmd ) {
+ case RC_SET_COLOR:
+ {
+ const setColorCommand_t *sc_cmd = (const setColorCommand_t *)curCmd;
+ curCmd = (const void *)(sc_cmd + 1);
+ break;
+ }
+ case RC_STRETCH_PIC:
+ {
+ const stretchPicCommand_t *sp_cmd = (const stretchPicCommand_t *)curCmd;
+ curCmd = (const void *)(sp_cmd + 1);
+ break;
+ }
+ case RC_DRAW_SURFS:
+ {
+ int i;
+ drawSurf_t *drawSurf;
+ shader_t *shader;
+ int fogNum;
+ int entityNum;
+ int dlightMap;
+ int sortedIndex;
+ const drawSurfsCommand_t *ds_cmd = (const drawSurfsCommand_t *)curCmd;
+
+ for( i = 0, drawSurf = ds_cmd->drawSurfs; i < ds_cmd->numDrawSurfs; i++, drawSurf++ ) {
+ R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlightMap );
+ sortedIndex = (( drawSurf->sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1));
+ if( sortedIndex >= newShader ) {
+ sortedIndex++;
+ drawSurf->sort = (sortedIndex << QSORT_SHADERNUM_SHIFT) | entityNum | ( fogNum << QSORT_FOGNUM_SHIFT ) | (int)dlightMap;
+ }
+ }
+ curCmd = (const void *)(ds_cmd + 1);
+ break;
+ }
+ case RC_DRAW_BUFFER:
+ {
+ const drawBufferCommand_t *db_cmd = (const drawBufferCommand_t *)curCmd;
+ curCmd = (const void *)(db_cmd + 1);
+ break;
+ }
+ case RC_SWAP_BUFFERS:
+ {
+ const swapBuffersCommand_t *sb_cmd = (const swapBuffersCommand_t *)curCmd;
+ curCmd = (const void *)(sb_cmd + 1);
+ break;
+ }
+ case RC_END_OF_LIST:
+ default:
+ return;
+ }
+ }
+ }
+}
+
+/*
+==============
+SortNewShader
+
+Positions the most recently created shader in the tr.sortedShaders[]
+array so that the shader->sort key is sorted reletive to the other
+shaders.
+
+Sets shader->sortedIndex
+==============
+*/
+static void SortNewShader( void ) {
+ int i;
+ float sort;
+ shader_t *newShader;
+
+ newShader = tr.shaders[ tr.numShaders - 1 ];
+ sort = newShader->sort;
+
+ for ( i = tr.numShaders - 2 ; i >= 0 ; i-- ) {
+ if ( tr.sortedShaders[ i ]->sort <= sort ) {
+ break;
+ }
+ tr.sortedShaders[i+1] = tr.sortedShaders[i];
+ tr.sortedShaders[i+1]->sortedIndex++;
+ }
+
+ // Arnout: fix rendercommandlist
+ // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=493
+ FixRenderCommandList( i+1 );
+
+ newShader->sortedIndex = i+1;
+ tr.sortedShaders[i+1] = newShader;
+}
+
+
+/*
+====================
+GeneratePermanentShader
+====================
+*/
+static shader_t *GeneratePermanentShader( void ) {
+ shader_t *newShader;
+ int i, b;
+ int size, hash;
+
+ if ( tr.numShaders == MAX_SHADERS ) {
+ ri.Printf( PRINT_WARNING, "WARNING: GeneratePermanentShader - MAX_SHADERS hit\n");
+ return tr.defaultShader;
+ }
+
+ newShader = ri.Hunk_Alloc( sizeof( shader_t ), h_low );
+
+ *newShader = shader;
+
+ if ( shader.sort <= SS_OPAQUE ) {
+ newShader->fogPass = FP_EQUAL;
+ } else if ( shader.contentFlags & CONTENTS_FOG ) {
+ newShader->fogPass = FP_LE;
+ }
+
+ tr.shaders[ tr.numShaders ] = newShader;
+ newShader->index = tr.numShaders;
+
+ tr.sortedShaders[ tr.numShaders ] = newShader;
+ newShader->sortedIndex = tr.numShaders;
+
+ tr.numShaders++;
+
+ for ( i = 0 ; i < newShader->numUnfoggedPasses ; i++ ) {
+ if ( !stages[i].active ) {
+ break;
+ }
+ newShader->stages[i] = ri.Hunk_Alloc( sizeof( stages[i] ), h_low );
+ *newShader->stages[i] = stages[i];
+
+ for ( b = 0 ; b < NUM_TEXTURE_BUNDLES ; b++ ) {
+ size = newShader->stages[i]->bundle[b].numTexMods * sizeof( texModInfo_t );
+ newShader->stages[i]->bundle[b].texMods = ri.Hunk_Alloc( size, h_low );
+ Com_Memcpy( newShader->stages[i]->bundle[b].texMods, stages[i].bundle[b].texMods, size );
+ }
+ }
+
+ SortNewShader();
+
+ hash = generateHashValue(newShader->name, FILE_HASH_SIZE);
+ newShader->next = hashTable[hash];
+ hashTable[hash] = newShader;
+
+ return newShader;
+}
+
+/*
+=================
+VertexLightingCollapse
+
+If vertex lighting is enabled, only render a single
+pass, trying to guess which is the correct one to best aproximate
+what it is supposed to look like.
+=================
+*/
+static void VertexLightingCollapse( void ) {
+ int stage;
+ shaderStage_t *bestStage;
+ int bestImageRank;
+ int rank;
+
+ // if we aren't opaque, just use the first pass
+ if ( shader.sort == SS_OPAQUE ) {
+
+ // pick the best texture for the single pass
+ bestStage = &stages[0];
+ bestImageRank = -999999;
+
+ for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) {
+ shaderStage_t *pStage = &stages[stage];
+
+ if ( !pStage->active ) {
+ break;
+ }
+ rank = 0;
+
+ if ( pStage->bundle[0].isLightmap ) {
+ rank -= 100;
+ }
+ if ( pStage->bundle[0].tcGen != TCGEN_TEXTURE ) {
+ rank -= 5;
+ }
+ if ( pStage->bundle[0].numTexMods ) {
+ rank -= 5;
+ }
+ if ( pStage->rgbGen != CGEN_IDENTITY && pStage->rgbGen != CGEN_IDENTITY_LIGHTING ) {
+ rank -= 3;
+ }
+
+ if ( rank > bestImageRank ) {
+ bestImageRank = rank;
+ bestStage = pStage;
+ }
+ }
+
+ stages[0].bundle[0] = bestStage->bundle[0];
+ stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS );
+ stages[0].stateBits |= GLS_DEPTHMASK_TRUE;
+ if ( shader.lightmapIndex == LIGHTMAP_NONE ) {
+ stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE;
+ } else {
+ stages[0].rgbGen = CGEN_EXACT_VERTEX;
+ }
+ stages[0].alphaGen = AGEN_SKIP;
+ } else {
+ // don't use a lightmap (tesla coils)
+ if ( stages[0].bundle[0].isLightmap ) {
+ stages[0] = stages[1];
+ }
+
+ // if we were in a cross-fade cgen, hack it to normal
+ if ( stages[0].rgbGen == CGEN_ONE_MINUS_ENTITY || stages[1].rgbGen == CGEN_ONE_MINUS_ENTITY ) {
+ stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
+ }
+ if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_SAWTOOTH )
+ && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_INVERSE_SAWTOOTH ) ) {
+ stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
+ }
+ if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_INVERSE_SAWTOOTH )
+ && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_SAWTOOTH ) ) {
+ stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
+ }
+ }
+
+ for ( stage = 1; stage < MAX_SHADER_STAGES; stage++ ) {
+ shaderStage_t *pStage = &stages[stage];
+
+ if ( !pStage->active ) {
+ break;
+ }
+
+ Com_Memset( pStage, 0, sizeof( *pStage ) );
+ }
+}
+
+/*
+=========================
+FinishShader
+
+Returns a freshly allocated shader with all the needed info
+from the current global working shader
+=========================
+*/
+static shader_t *FinishShader( void ) {
+ int stage;
+ qboolean hasLightmapStage;
+ qboolean vertexLightmap;
+
+ hasLightmapStage = qfalse;
+ vertexLightmap = qfalse;
+
+ //
+ // set sky stuff appropriate
+ //
+ if ( shader.isSky ) {
+ shader.sort = SS_ENVIRONMENT;
+ }
+
+ //
+ // set polygon offset
+ //
+ if ( shader.polygonOffset && !shader.sort ) {
+ shader.sort = SS_DECAL;
+ }
+
+ //
+ // set appropriate stage information
+ //
+ for ( stage = 0; stage < MAX_SHADER_STAGES; ) {
+ shaderStage_t *pStage = &stages[stage];
+
+ if ( !pStage->active ) {
+ break;
+ }
+
+ // check for a missing texture
+ if ( !pStage->bundle[0].image[0] ) {
+ ri.Printf( PRINT_WARNING, "Shader %s has a stage with no image\n", shader.name );
+ pStage->active = qfalse;
+ stage++;
+ continue;
+ }
+
+ //
+ // ditch this stage if it's detail and detail textures are disabled
+ //
+ if ( pStage->isDetail && !r_detailTextures->integer )
+ {
+ int index;
+
+ for(index = stage + 1; index < MAX_SHADER_STAGES; index++)
+ {
+ if(!stages[index].active)
+ break;
+ }
+
+ if(index < MAX_SHADER_STAGES)
+ memmove(pStage, pStage + 1, sizeof(*pStage) * (index - stage));
+ else
+ {
+ if(stage + 1 < MAX_SHADER_STAGES)
+ memmove(pStage, pStage + 1, sizeof(*pStage) * (index - stage - 1));
+
+ Com_Memset(&stages[index - 1], 0, sizeof(*stages));
+ }
+
+ continue;
+ }
+
+ //
+ // default texture coordinate generation
+ //
+ if ( pStage->bundle[0].isLightmap ) {
+ if ( pStage->bundle[0].tcGen == TCGEN_BAD ) {
+ pStage->bundle[0].tcGen = TCGEN_LIGHTMAP;
+ }
+ hasLightmapStage = qtrue;
+ } else {
+ if ( pStage->bundle[0].tcGen == TCGEN_BAD ) {
+ pStage->bundle[0].tcGen = TCGEN_TEXTURE;
+ }
+ }
+
+
+ // not a true lightmap but we want to leave existing
+ // behaviour in place and not print out a warning
+ //if (pStage->rgbGen == CGEN_VERTEX) {
+ // vertexLightmap = qtrue;
+ //}
+
+
+
+ //
+ // determine sort order and fog color adjustment
+ //
+ if ( ( pStage->stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) &&
+ ( stages[0].stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) ) {
+ int blendSrcBits = pStage->stateBits & GLS_SRCBLEND_BITS;
+ int blendDstBits = pStage->stateBits & GLS_DSTBLEND_BITS;
+
+ // fog color adjustment only works for blend modes that have a contribution
+ // that aproaches 0 as the modulate values aproach 0 --
+ // GL_ONE, GL_ONE
+ // GL_ZERO, GL_ONE_MINUS_SRC_COLOR
+ // GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA
+
+ // modulate, additive
+ if ( ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE ) ) ||
+ ( ( blendSrcBits == GLS_SRCBLEND_ZERO ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_COLOR ) ) ) {
+ pStage->adjustColorsForFog = ACFF_MODULATE_RGB;
+ }
+ // strict blend
+ else if ( ( blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) )
+ {
+ pStage->adjustColorsForFog = ACFF_MODULATE_ALPHA;
+ }
+ // premultiplied alpha
+ else if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) )
+ {
+ pStage->adjustColorsForFog = ACFF_MODULATE_RGBA;
+ } else {
+ // we can't adjust this one correctly, so it won't be exactly correct in fog
+ }
+
+ // don't screw with sort order if this is a portal or environment
+ if ( !shader.sort ) {
+ // see through item, like a grill or grate
+ if ( pStage->stateBits & GLS_DEPTHMASK_TRUE ) {
+ shader.sort = SS_SEE_THROUGH;
+ } else {
+ shader.sort = SS_BLEND0;
+ }
+ }
+ }
+
+ stage++;
+ }
+
+ // there are times when you will need to manually apply a sort to
+ // opaque alpha tested shaders that have later blend passes
+ if ( !shader.sort ) {
+ shader.sort = SS_OPAQUE;
+ }
+
+ //
+ // if we are in r_vertexLight mode, never use a lightmap texture
+ //
+ if ( stage > 1 && ( (r_vertexLight->integer && !r_uiFullScreen->integer) || glConfig.hardwareType == GLHW_PERMEDIA2 ) ) {
+ VertexLightingCollapse();
+ stage = 1;
+ hasLightmapStage = qfalse;
+ }
+
+ //
+ // look for multitexture potential
+ //
+ if ( stage > 1 && CollapseMultitexture() ) {
+ stage--;
+ }
+
+ if ( shader.lightmapIndex >= 0 && !hasLightmapStage ) {
+ if (vertexLightmap) {
+ ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has VERTEX forced lightmap!\n", shader.name );
+ } else {
+ ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has lightmap but no lightmap stage!\n", shader.name );
+ shader.lightmapIndex = LIGHTMAP_NONE;
+ }
+ }
+
+
+ //
+ // compute number of passes
+ //
+ shader.numUnfoggedPasses = stage;
+
+ // fogonly shaders don't have any normal passes
+ if (stage == 0 && !shader.isSky)
+ shader.sort = SS_FOG;
+
+ // determine which stage iterator function is appropriate
+ ComputeStageIteratorFunc();
+
+ return GeneratePermanentShader();
+}
+
+//========================================================================================
+
+/*
+====================
+FindShaderInShaderText
+
+Scans the combined text description of all the shader files for
+the given shader name.
+
+return NULL if not found
+
+If found, it will return a valid shader
+=====================
+*/
+static char *FindShaderInShaderText( const char *shadername ) {
+
+ char *token, *p;
+
+ int i, hash;
+
+ hash = generateHashValue(shadername, MAX_SHADERTEXT_HASH);
+
+ for (i = 0; shaderTextHashTable[hash][i]; i++) {
+ p = shaderTextHashTable[hash][i];
+ token = COM_ParseExt(&p, qtrue);
+ if ( !Q_stricmp( token, shadername ) ) {
+ return p;
+ }
+ }
+
+ p = s_shaderText;
+
+ if ( !p ) {
+ return NULL;
+ }
+
+ // look for label
+ while ( 1 ) {
+ token = COM_ParseExt( &p, qtrue );
+ if ( token[0] == 0 ) {
+ break;
+ }
+
+ if ( !Q_stricmp( token, shadername ) ) {
+ return p;
+ }
+ else {
+ // skip the definition
+ SkipBracedSection( &p );
+ }
+ }
+
+ return NULL;
+}
+
+
+/*
+==================
+R_FindShaderByName
+
+Will always return a valid shader, but it might be the
+default shader if the real one can't be found.
+==================
+*/
+shader_t *R_FindShaderByName( const char *name ) {
+ char strippedName[MAX_QPATH];
+ int hash;
+ shader_t *sh;
+
+ if ( (name==NULL) || (name[0] == 0) ) {
+ return tr.defaultShader;
+ }
+
+ COM_StripExtension(name, strippedName, sizeof(strippedName));
+
+ hash = generateHashValue(strippedName, FILE_HASH_SIZE);
+
+ //
+ // see if the shader is already loaded
+ //
+ for (sh=hashTable[hash]; sh; sh=sh->next) {
+ // NOTE: if there was no shader or image available with the name strippedName
+ // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we
+ // have to check all default shaders otherwise for every call to R_FindShader
+ // with that same strippedName a new default shader is created.
+ if (Q_stricmp(sh->name, strippedName) == 0) {
+ // match found
+ return sh;
+ }
+ }
+
+ return tr.defaultShader;
+}
+
+
+/*
+===============
+R_FindShader
+
+Will always return a valid shader, but it might be the
+default shader if the real one can't be found.
+
+In the interest of not requiring an explicit shader text entry to
+be defined for every single image used in the game, three default
+shader behaviors can be auto-created for any image:
+
+If lightmapIndex == LIGHTMAP_NONE, then the image will have
+dynamic diffuse lighting applied to it, as apropriate for most
+entity skin surfaces.
+
+If lightmapIndex == LIGHTMAP_2D, then the image will be used
+for 2D rendering unless an explicit shader is found
+
+If lightmapIndex == LIGHTMAP_BY_VERTEX, then the image will use
+the vertex rgba modulate values, as apropriate for misc_model
+pre-lit surfaces.
+
+Other lightmapIndex values will have a lightmap stage created
+and src*dest blending applied with the texture, as apropriate for
+most world construction surfaces.
+
+===============
+*/
+shader_t *R_FindShader( const char *name, int lightmapIndex, qboolean mipRawImage ) {
+ char strippedName[MAX_QPATH];
+ int i, hash;
+ char *shaderText;
+ image_t *image;
+ shader_t *sh;
+
+ if ( name[0] == 0 ) {
+ return tr.defaultShader;
+ }
+
+ // use (fullbright) vertex lighting if the bsp file doesn't have
+ // lightmaps
+ if ( lightmapIndex >= 0 && lightmapIndex >= tr.numLightmaps ) {
+ lightmapIndex = LIGHTMAP_BY_VERTEX;
+ } else if ( lightmapIndex < LIGHTMAP_2D ) {
+ // negative lightmap indexes cause stray pointers (think tr.lightmaps[lightmapIndex])
+ ri.Printf( PRINT_WARNING, "WARNING: shader '%s' has invalid lightmap index of %d\n", name, lightmapIndex );
+ lightmapIndex = LIGHTMAP_BY_VERTEX;
+ }
+
+ COM_StripExtension(name, strippedName, sizeof(strippedName));
+
+ hash = generateHashValue(strippedName, FILE_HASH_SIZE);
+
+ //
+ // see if the shader is already loaded
+ //
+ for (sh = hashTable[hash]; sh; sh = sh->next) {
+ // NOTE: if there was no shader or image available with the name strippedName
+ // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we
+ // have to check all default shaders otherwise for every call to R_FindShader
+ // with that same strippedName a new default shader is created.
+ if ( (sh->lightmapIndex == lightmapIndex || sh->defaultShader) &&
+ !Q_stricmp(sh->name, strippedName)) {
+ // match found
+ return sh;
+ }
+ }
+
+ // make sure the render thread is stopped, because we are probably
+ // going to have to upload an image
+ if (r_smp->integer) {
+ R_SyncRenderThread();
+ }
+
+ // clear the global shader
+ Com_Memset( &shader, 0, sizeof( shader ) );
+ Com_Memset( &stages, 0, sizeof( stages ) );
+ Q_strncpyz(shader.name, strippedName, sizeof(shader.name));
+ shader.lightmapIndex = lightmapIndex;
+ for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) {
+ stages[i].bundle[0].texMods = texMods[i];
+ }
+
+ // FIXME: set these "need" values apropriately
+ shader.needsNormal = qtrue;
+ shader.needsST1 = qtrue;
+ shader.needsST2 = qtrue;
+ shader.needsColor = qtrue;
+
+ //
+ // attempt to define shader from an explicit parameter file
+ //
+ shaderText = FindShaderInShaderText( strippedName );
+ if ( shaderText ) {
+ // enable this when building a pak file to get a global list
+ // of all explicit shaders
+ if ( r_printShaders->integer ) {
+ ri.Printf( PRINT_ALL, "*SHADER* %s\n", name );
+ }
+
+ if ( !ParseShader( &shaderText ) ) {
+ // had errors, so use default shader
+ shader.defaultShader = qtrue;
+ }
+ sh = FinishShader();
+ return sh;
+ }
+
+
+ //
+ // if not defined in the in-memory shader descriptions,
+ // look for a single supported image file
+ //
+ image = R_FindImageFile( name, mipRawImage, mipRawImage, mipRawImage ? GL_REPEAT : GL_CLAMP_TO_EDGE );
+ if ( !image ) {
+ ri.Printf( PRINT_DEVELOPER, "Couldn't find image file for shader %s\n", name );
+ shader.defaultShader = qtrue;
+ return FinishShader();
+ }
+
+ //
+ // create the default shading commands
+ //
+ if ( shader.lightmapIndex == LIGHTMAP_NONE ) {
+ // dynamic colors at vertexes
+ stages[0].bundle[0].image[0] = image;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE;
+ stages[0].stateBits = GLS_DEFAULT;
+ } else if ( shader.lightmapIndex == LIGHTMAP_BY_VERTEX ) {
+ // explicit colors at vertexes
+ stages[0].bundle[0].image[0] = image;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_EXACT_VERTEX;
+ stages[0].alphaGen = AGEN_SKIP;
+ stages[0].stateBits = GLS_DEFAULT;
+ } else if ( shader.lightmapIndex == LIGHTMAP_2D ) {
+ // GUI elements
+ stages[0].bundle[0].image[0] = image;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_VERTEX;
+ stages[0].alphaGen = AGEN_VERTEX;
+ stages[0].stateBits = GLS_DEPTHTEST_DISABLE |
+ GLS_SRCBLEND_SRC_ALPHA |
+ GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
+ } else if ( shader.lightmapIndex == LIGHTMAP_WHITEIMAGE ) {
+ // fullbright level
+ stages[0].bundle[0].image[0] = tr.whiteImage;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
+ stages[0].stateBits = GLS_DEFAULT;
+
+ stages[1].bundle[0].image[0] = image;
+ stages[1].active = qtrue;
+ stages[1].rgbGen = CGEN_IDENTITY;
+ stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
+ } else {
+ // two pass lightmap
+ stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex];
+ stages[0].bundle[0].isLightmap = qtrue;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation
+ // for identitylight
+ stages[0].stateBits = GLS_DEFAULT;
+
+ stages[1].bundle[0].image[0] = image;
+ stages[1].active = qtrue;
+ stages[1].rgbGen = CGEN_IDENTITY;
+ stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
+ }
+
+ return FinishShader();
+}
+
+
+qhandle_t RE_RegisterShaderFromImage(const char *name, int lightmapIndex, image_t *image, qboolean mipRawImage) {
+ int i, hash;
+ shader_t *sh;
+
+ hash = generateHashValue(name, FILE_HASH_SIZE);
+
+ // probably not necessary since this function
+ // only gets called from tr_font.c with lightmapIndex == LIGHTMAP_2D
+ // but better safe than sorry.
+ if ( lightmapIndex >= tr.numLightmaps ) {
+ lightmapIndex = LIGHTMAP_WHITEIMAGE;
+ }
+
+ //
+ // see if the shader is already loaded
+ //
+ for (sh=hashTable[hash]; sh; sh=sh->next) {
+ // NOTE: if there was no shader or image available with the name strippedName
+ // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we
+ // have to check all default shaders otherwise for every call to R_FindShader
+ // with that same strippedName a new default shader is created.
+ if ( (sh->lightmapIndex == lightmapIndex || sh->defaultShader) &&
+ // index by name
+ !Q_stricmp(sh->name, name)) {
+ // match found
+ return sh->index;
+ }
+ }
+
+ // make sure the render thread is stopped, because we are probably
+ // going to have to upload an image
+ if (r_smp->integer) {
+ R_SyncRenderThread();
+ }
+
+ // clear the global shader
+ Com_Memset( &shader, 0, sizeof( shader ) );
+ Com_Memset( &stages, 0, sizeof( stages ) );
+ Q_strncpyz(shader.name, name, sizeof(shader.name));
+ shader.lightmapIndex = lightmapIndex;
+ for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) {
+ stages[i].bundle[0].texMods = texMods[i];
+ }
+
+ // FIXME: set these "need" values apropriately
+ shader.needsNormal = qtrue;
+ shader.needsST1 = qtrue;
+ shader.needsST2 = qtrue;
+ shader.needsColor = qtrue;
+
+ //
+ // create the default shading commands
+ //
+ if ( shader.lightmapIndex == LIGHTMAP_NONE ) {
+ // dynamic colors at vertexes
+ stages[0].bundle[0].image[0] = image;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE;
+ stages[0].stateBits = GLS_DEFAULT;
+ } else if ( shader.lightmapIndex == LIGHTMAP_BY_VERTEX ) {
+ // explicit colors at vertexes
+ stages[0].bundle[0].image[0] = image;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_EXACT_VERTEX;
+ stages[0].alphaGen = AGEN_SKIP;
+ stages[0].stateBits = GLS_DEFAULT;
+ } else if ( shader.lightmapIndex == LIGHTMAP_2D ) {
+ // GUI elements
+ stages[0].bundle[0].image[0] = image;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_VERTEX;
+ stages[0].alphaGen = AGEN_VERTEX;
+ stages[0].stateBits = GLS_DEPTHTEST_DISABLE |
+ GLS_SRCBLEND_SRC_ALPHA |
+ GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
+ } else if ( shader.lightmapIndex == LIGHTMAP_WHITEIMAGE ) {
+ // fullbright level
+ stages[0].bundle[0].image[0] = tr.whiteImage;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
+ stages[0].stateBits = GLS_DEFAULT;
+
+ stages[1].bundle[0].image[0] = image;
+ stages[1].active = qtrue;
+ stages[1].rgbGen = CGEN_IDENTITY;
+ stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
+ } else {
+ // two pass lightmap
+ stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex];
+ stages[0].bundle[0].isLightmap = qtrue;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation
+ // for identitylight
+ stages[0].stateBits = GLS_DEFAULT;
+
+ stages[1].bundle[0].image[0] = image;
+ stages[1].active = qtrue;
+ stages[1].rgbGen = CGEN_IDENTITY;
+ stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
+ }
+
+ sh = FinishShader();
+ return sh->index;
+}
+
+
+/*
+====================
+RE_RegisterShader
+
+This is the exported shader entry point for the rest of the system
+It will always return an index that will be valid.
+
+This should really only be used for explicit shaders, because there is no
+way to ask for different implicit lighting modes (vertex, lightmap, etc)
+====================
+*/
+qhandle_t RE_RegisterShaderLightMap( const char *name, int lightmapIndex ) {
+ shader_t *sh;
+
+ if ( strlen( name ) >= MAX_QPATH ) {
+ Com_Printf( "Shader name exceeds MAX_QPATH\n" );
+ return 0;
+ }
+
+ sh = R_FindShader( name, lightmapIndex, qtrue );
+
+ // we want to return 0 if the shader failed to
+ // load for some reason, but R_FindShader should
+ // still keep a name allocated for it, so if
+ // something calls RE_RegisterShader again with
+ // the same name, we don't try looking for it again
+ if ( sh->defaultShader ) {
+ return 0;
+ }
+
+ return sh->index;
+}
+
+
+/*
+====================
+RE_RegisterShader
+
+This is the exported shader entry point for the rest of the system
+It will always return an index that will be valid.
+
+This should really only be used for explicit shaders, because there is no
+way to ask for different implicit lighting modes (vertex, lightmap, etc)
+====================
+*/
+qhandle_t RE_RegisterShader( const char *name ) {
+ shader_t *sh;
+
+ if ( strlen( name ) >= MAX_QPATH ) {
+ Com_Printf( "Shader name exceeds MAX_QPATH\n" );
+ return 0;
+ }
+
+ sh = R_FindShader( name, LIGHTMAP_2D, qtrue );
+
+ // we want to return 0 if the shader failed to
+ // load for some reason, but R_FindShader should
+ // still keep a name allocated for it, so if
+ // something calls RE_RegisterShader again with
+ // the same name, we don't try looking for it again
+ if ( sh->defaultShader ) {
+ return 0;
+ }
+
+ return sh->index;
+}
+
+
+/*
+====================
+RE_RegisterShaderNoMip
+
+For menu graphics that should never be picmiped
+====================
+*/
+qhandle_t RE_RegisterShaderNoMip( const char *name ) {
+ shader_t *sh;
+
+ if ( strlen( name ) >= MAX_QPATH ) {
+ Com_Printf( "Shader name exceeds MAX_QPATH\n" );
+ return 0;
+ }
+
+ sh = R_FindShader( name, LIGHTMAP_2D, qfalse );
+
+ // we want to return 0 if the shader failed to
+ // load for some reason, but R_FindShader should
+ // still keep a name allocated for it, so if
+ // something calls RE_RegisterShader again with
+ // the same name, we don't try looking for it again
+ if ( sh->defaultShader ) {
+ return 0;
+ }
+
+ return sh->index;
+}
+
+/*
+====================
+R_GetShaderByHandle
+
+When a handle is passed in by another module, this range checks
+it and returns a valid (possibly default) shader_t to be used internally.
+====================
+*/
+shader_t *R_GetShaderByHandle( qhandle_t hShader ) {
+ if ( hShader < 0 ) {
+ ri.Printf( PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader );
+ return tr.defaultShader;
+ }
+ if ( hShader >= tr.numShaders ) {
+ ri.Printf( PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader );
+ return tr.defaultShader;
+ }
+ return tr.shaders[hShader];
+}
+
+/*
+===============
+R_ShaderList_f
+
+Dump information on all valid shaders to the console
+A second parameter will cause it to print in sorted order
+===============
+*/
+void R_ShaderList_f (void) {
+ int i;
+ int count;
+ shader_t *shader;
+
+ ri.Printf (PRINT_ALL, "-----------------------\n");
+
+ count = 0;
+ for ( i = 0 ; i < tr.numShaders ; i++ ) {
+ if ( ri.Cmd_Argc() > 1 ) {
+ shader = tr.sortedShaders[i];
+ } else {
+ shader = tr.shaders[i];
+ }
+
+ ri.Printf( PRINT_ALL, "%i ", shader->numUnfoggedPasses );
+
+ if (shader->lightmapIndex >= 0 ) {
+ ri.Printf (PRINT_ALL, "L ");
+ } else {
+ ri.Printf (PRINT_ALL, " ");
+ }
+ if ( shader->multitextureEnv == GL_ADD ) {
+ ri.Printf( PRINT_ALL, "MT(a) " );
+ } else if ( shader->multitextureEnv == GL_MODULATE ) {
+ ri.Printf( PRINT_ALL, "MT(m) " );
+ } else if ( shader->multitextureEnv == GL_DECAL ) {
+ ri.Printf( PRINT_ALL, "MT(d) " );
+ } else {
+ ri.Printf( PRINT_ALL, " " );
+ }
+ if ( shader->explicitlyDefined ) {
+ ri.Printf( PRINT_ALL, "E " );
+ } else {
+ ri.Printf( PRINT_ALL, " " );
+ }
+
+ if ( shader->optimalStageIteratorFunc == RB_StageIteratorGeneric ) {
+ ri.Printf( PRINT_ALL, "gen " );
+ } else if ( shader->optimalStageIteratorFunc == RB_StageIteratorSky ) {
+ ri.Printf( PRINT_ALL, "sky " );
+ } else if ( shader->optimalStageIteratorFunc == RB_StageIteratorLightmappedMultitexture ) {
+ ri.Printf( PRINT_ALL, "lmmt" );
+ } else if ( shader->optimalStageIteratorFunc == RB_StageIteratorVertexLitTexture ) {
+ ri.Printf( PRINT_ALL, "vlt " );
+ } else {
+ ri.Printf( PRINT_ALL, " " );
+ }
+
+ if ( shader->defaultShader ) {
+ ri.Printf (PRINT_ALL, ": %s (DEFAULTED)\n", shader->name);
+ } else {
+ ri.Printf (PRINT_ALL, ": %s\n", shader->name);
+ }
+ count++;
+ }
+ ri.Printf (PRINT_ALL, "%i total shaders\n", count);
+ ri.Printf (PRINT_ALL, "------------------\n");
+}
+
+
+/*
+====================
+ScanAndLoadShaderFiles
+
+Finds and loads all .shader files, combining them into
+a single large text block that can be scanned for shader names
+=====================
+*/
+#define MAX_SHADER_FILES 4096
+static void ScanAndLoadShaderFiles( void )
+{
+ char **shaderFiles;
+ char *buffers[MAX_SHADER_FILES];
+ char *p;
+ int numShaderFiles;
+ int i;
+ char *oldp, *token, *hashMem;
+ int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash, size;
+
+ long sum = 0, summand;
+ // scan for shader files
+ shaderFiles = ri.FS_ListFiles( "scripts", ".shader", &numShaderFiles );
+
+ if ( !shaderFiles || !numShaderFiles )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: no shader files found\n" );
+ return;
+ }
+
+ if ( numShaderFiles > MAX_SHADER_FILES ) {
+ numShaderFiles = MAX_SHADER_FILES;
+ }
+
+ // load and parse shader files
+ for ( i = 0; i < numShaderFiles; i++ )
+ {
+ char filename[MAX_QPATH];
+
+ Com_sprintf( filename, sizeof( filename ), "scripts/%s", shaderFiles[i] );
+ ri.Printf( PRINT_DEVELOPER, "...loading '%s'\n", filename );
+ summand = ri.FS_ReadFile( filename, (void **)&buffers[i] );
+
+ if ( !buffers[i] )
+ ri.Error( ERR_DROP, "Couldn't load %s", filename );
+
+ // Do a simple check on the shader structure in that file to make sure one bad shader file cannot fuck up all other shaders.
+ p = buffers[i];
+ while(1)
+ {
+ token = COM_ParseExt(&p, qtrue);
+
+ if(!*token)
+ break;
+
+ oldp = p;
+
+ token = COM_ParseExt(&p, qtrue);
+ if(token[0] != '{' && token[1] != '\0')
+ {
+ ri.Printf(PRINT_WARNING, "WARNING: Bad shader file %s has incorrect syntax.\n", filename);
+ ri.FS_FreeFile(buffers[i]);
+ buffers[i] = NULL;
+ break;
+ }
+
+ SkipBracedSection(&oldp);
+ p = oldp;
+ }
+
+
+ if (buffers[i])
+ sum += summand;
+ }
+
+ // build single large buffer
+ s_shaderText = ri.Hunk_Alloc( sum + numShaderFiles*2, h_low );
+ s_shaderText[ 0 ] = '\0';
+
+ // free in reverse order, so the temp files are all dumped
+ for ( i = numShaderFiles - 1; i >= 0 ; i-- )
+ {
+ if(buffers[i])
+ {
+ p = &s_shaderText[strlen(s_shaderText)];
+ strcat( s_shaderText, buffers[i] );
+ ri.FS_FreeFile( buffers[i] );
+ COM_Compress(p);
+ strcat( s_shaderText, "\n" );
+ }
+ }
+
+ // free up memory
+ ri.FS_FreeFileList( shaderFiles );
+
+ Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes));
+ size = 0;
+
+ p = s_shaderText;
+ // look for shader names
+ while ( 1 ) {
+ token = COM_ParseExt( &p, qtrue );
+ if ( token[0] == 0 ) {
+ break;
+ }
+
+ hash = generateHashValue(token, MAX_SHADERTEXT_HASH);
+ shaderTextHashTableSizes[hash]++;
+ size++;
+ SkipBracedSection(&p);
+ }
+
+ size += MAX_SHADERTEXT_HASH;
+
+ hashMem = ri.Hunk_Alloc( size * sizeof(char *), h_low );
+
+ for (i = 0; i < MAX_SHADERTEXT_HASH; i++) {
+ shaderTextHashTable[i] = (char **) hashMem;
+ hashMem = ((char *) hashMem) + ((shaderTextHashTableSizes[i] + 1) * sizeof(char *));
+ }
+
+ Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes));
+
+ p = s_shaderText;
+ // look for shader names
+ while ( 1 ) {
+ oldp = p;
+ token = COM_ParseExt( &p, qtrue );
+ if ( token[0] == 0 ) {
+ break;
+ }
+
+ hash = generateHashValue(token, MAX_SHADERTEXT_HASH);
+ shaderTextHashTable[hash][shaderTextHashTableSizes[hash]++] = oldp;
+
+ SkipBracedSection(&p);
+ }
+
+ return;
+
+}
+
+
+/*
+====================
+CreateInternalShaders
+====================
+*/
+static void CreateInternalShaders( void ) {
+ tr.numShaders = 0;
+
+ // init the default shader
+ Com_Memset( &shader, 0, sizeof( shader ) );
+ Com_Memset( &stages, 0, sizeof( stages ) );
+
+ Q_strncpyz( shader.name, "<default>", sizeof( shader.name ) );
+
+ shader.lightmapIndex = LIGHTMAP_NONE;
+ stages[0].bundle[0].image[0] = tr.defaultImage;
+ stages[0].active = qtrue;
+ stages[0].stateBits = GLS_DEFAULT;
+ tr.defaultShader = FinishShader();
+
+ // shadow shader is just a marker
+ Q_strncpyz( shader.name, "<stencil shadow>", sizeof( shader.name ) );
+ shader.sort = SS_STENCIL_SHADOW;
+ tr.shadowShader = FinishShader();
+}
+
+static void CreateExternalShaders( void ) {
+ tr.projectionShadowShader = R_FindShader( "projectionShadow", LIGHTMAP_NONE, qtrue );
+ tr.flareShader = R_FindShader( "flareShader", LIGHTMAP_NONE, qtrue );
+
+ // Hack to make fogging work correctly on flares. Fog colors are calculated
+ // in tr_flare.c already.
+ if(!tr.flareShader->defaultShader)
+ {
+ int index;
+
+ for(index = 0; index < tr.flareShader->numUnfoggedPasses; index++)
+ {
+ tr.flareShader->stages[index]->adjustColorsForFog = ACFF_NONE;
+ tr.flareShader->stages[index]->stateBits |= GLS_DEPTHTEST_DISABLE;
+ }
+ }
+
+ tr.sunShader = R_FindShader( "sun", LIGHTMAP_NONE, qtrue );
+}
+
+/*
+==================
+R_InitShaders
+==================
+*/
+void R_InitShaders( void ) {
+ ri.Printf( PRINT_ALL, "Initializing Shaders\n" );
+
+ Com_Memset(hashTable, 0, sizeof(hashTable));
+
+ deferLoad = qfalse;
+
+ CreateInternalShaders();
+
+ ScanAndLoadShaderFiles();
+
+ CreateExternalShaders();
+}
diff --git a/code/renderer/tr_shadows.c b/code/renderer/tr_shadows.c
new file mode 100644
index 0000000..0514d0e
--- /dev/null
+++ b/code/renderer/tr_shadows.c
@@ -0,0 +1,374 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#include "tr_local.h"
+
+
+/*
+
+ for a projection shadow:
+
+ point[x] += light vector * ( z - shadow plane )
+ point[y] +=
+ point[z] = shadow plane
+
+ 1 0 light[x] / light[z]
+
+*/
+
+typedef struct {
+ int i2;
+ int facing;
+} edgeDef_t;
+
+#define MAX_EDGE_DEFS 32
+
+static edgeDef_t edgeDefs[SHADER_MAX_VERTEXES][MAX_EDGE_DEFS];
+static int numEdgeDefs[SHADER_MAX_VERTEXES];
+static int facing[SHADER_MAX_INDEXES/3];
+
+void R_AddEdgeDef( int i1, int i2, int facing ) {
+ int c;
+
+ c = numEdgeDefs[ i1 ];
+ if ( c == MAX_EDGE_DEFS ) {
+ return; // overflow
+ }
+ edgeDefs[ i1 ][ c ].i2 = i2;
+ edgeDefs[ i1 ][ c ].facing = facing;
+
+ numEdgeDefs[ i1 ]++;
+}
+
+void R_RenderShadowEdges( void ) {
+ int i;
+
+#if 0
+ int numTris;
+
+ // dumb way -- render every triangle's edges
+ numTris = tess.numIndexes / 3;
+
+ for ( i = 0 ; i < numTris ; i++ ) {
+ int i1, i2, i3;
+
+ if ( !facing[i] ) {
+ continue;
+ }
+
+ i1 = tess.indexes[ i*3 + 0 ];
+ i2 = tess.indexes[ i*3 + 1 ];
+ i3 = tess.indexes[ i*3 + 2 ];
+
+ qglBegin( GL_TRIANGLE_STRIP );
+ qglVertex3fv( tess.xyz[ i1 ] );
+ qglVertex3fv( tess.xyz[ i1 + tess.numVertexes ] );
+ qglVertex3fv( tess.xyz[ i2 ] );
+ qglVertex3fv( tess.xyz[ i2 + tess.numVertexes ] );
+ qglVertex3fv( tess.xyz[ i3 ] );
+ qglVertex3fv( tess.xyz[ i3 + tess.numVertexes ] );
+ qglVertex3fv( tess.xyz[ i1 ] );
+ qglVertex3fv( tess.xyz[ i1 + tess.numVertexes ] );
+ qglEnd();
+ }
+#else
+ int c, c2;
+ int j, k;
+ int i2;
+ int c_edges, c_rejected;
+ int hit[2];
+
+ // an edge is NOT a silhouette edge if its face doesn't face the light,
+ // or if it has a reverse paired edge that also faces the light.
+ // A well behaved polyhedron would have exactly two faces for each edge,
+ // but lots of models have dangling edges or overfanned edges
+ c_edges = 0;
+ c_rejected = 0;
+
+ for ( i = 0 ; i < tess.numVertexes ; i++ ) {
+ c = numEdgeDefs[ i ];
+ for ( j = 0 ; j < c ; j++ ) {
+ if ( !edgeDefs[ i ][ j ].facing ) {
+ continue;
+ }
+
+ hit[0] = 0;
+ hit[1] = 0;
+
+ i2 = edgeDefs[ i ][ j ].i2;
+ c2 = numEdgeDefs[ i2 ];
+ for ( k = 0 ; k < c2 ; k++ ) {
+ if ( edgeDefs[ i2 ][ k ].i2 == i ) {
+ hit[ edgeDefs[ i2 ][ k ].facing ]++;
+ }
+ }
+
+ // if it doesn't share the edge with another front facing
+ // triangle, it is a sil edge
+ if ( hit[ 1 ] == 0 ) {
+#ifdef PANDORA
+ glIndex_t indicies[4];
+ indicies[0] = i;
+ indicies[1] = i + tess.numVertexes;
+ indicies[2] = i2;
+ indicies[3] = i2 + tess.numVertexes;
+
+ qglVertexPointer(3, GL_FLOAT, 16, tess.xyz);
+ qglDrawElements(GL_TRIANGLE_STRIP, 4, GL_INDEX_TYPE, indicies);
+#else
+ qglBegin( GL_TRIANGLE_STRIP );
+ qglVertex3fv( tess.xyz[ i ] );
+ qglVertex3fv( tess.xyz[ i + tess.numVertexes ] );
+ qglVertex3fv( tess.xyz[ i2 ] );
+ qglVertex3fv( tess.xyz[ i2 + tess.numVertexes ] );
+ qglEnd();
+#endif
+ c_edges++;
+ } else {
+ c_rejected++;
+ }
+ }
+ }
+#endif
+}
+
+/*
+=================
+RB_ShadowTessEnd
+
+triangleFromEdge[ v1 ][ v2 ]
+
+
+ set triangle from edge( v1, v2, tri )
+ if ( facing[ triangleFromEdge[ v1 ][ v2 ] ] && !facing[ triangleFromEdge[ v2 ][ v1 ] ) {
+ }
+=================
+*/
+void RB_ShadowTessEnd( void ) {
+ int i;
+ int numTris;
+ vec3_t lightDir;
+ GLboolean rgba[4];
+
+ // we can only do this if we have enough space in the vertex buffers
+ if ( tess.numVertexes >= SHADER_MAX_VERTEXES / 2 ) {
+ return;
+ }
+
+ if ( glConfig.stencilBits < 4 ) {
+ return;
+ }
+
+ VectorCopy( backEnd.currentEntity->lightDir, lightDir );
+
+ // project vertexes away from light direction
+ for ( i = 0 ; i < tess.numVertexes ; i++ ) {
+ VectorMA( tess.xyz[i], -512, lightDir, tess.xyz[i+tess.numVertexes] );
+ }
+
+ // decide which triangles face the light
+ Com_Memset( numEdgeDefs, 0, 4 * tess.numVertexes );
+
+ numTris = tess.numIndexes / 3;
+ for ( i = 0 ; i < numTris ; i++ ) {
+ int i1, i2, i3;
+ vec3_t d1, d2, normal;
+ float *v1, *v2, *v3;
+ float d;
+
+ i1 = tess.indexes[ i*3 + 0 ];
+ i2 = tess.indexes[ i*3 + 1 ];
+ i3 = tess.indexes[ i*3 + 2 ];
+
+ v1 = tess.xyz[ i1 ];
+ v2 = tess.xyz[ i2 ];
+ v3 = tess.xyz[ i3 ];
+
+ VectorSubtract( v2, v1, d1 );
+ VectorSubtract( v3, v1, d2 );
+ CrossProduct( d1, d2, normal );
+
+ d = DotProduct( normal, lightDir );
+ if ( d > 0 ) {
+ facing[ i ] = 1;
+ } else {
+ facing[ i ] = 0;
+ }
+
+ // create the edges
+ R_AddEdgeDef( i1, i2, facing[ i ] );
+ R_AddEdgeDef( i2, i3, facing[ i ] );
+ R_AddEdgeDef( i3, i1, facing[ i ] );
+ }
+
+ // draw the silhouette edges
+
+ GL_Bind( tr.whiteImage );
+ qglEnable( GL_CULL_FACE );
+ GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO );
+#ifdef PANDORA
+ glColor4f( 0.2f, 0.2f, 0.2f, 1.0f );
+#else
+ qglColor3f( 0.2f, 0.2f, 0.2f );
+#endif
+ // don't write to the color buffer
+ qglGetBooleanv(GL_COLOR_WRITEMASK, rgba);
+ qglColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
+
+ qglEnable( GL_STENCIL_TEST );
+ qglStencilFunc( GL_ALWAYS, 1, 255 );
+
+ // mirrors have the culling order reversed
+ if ( backEnd.viewParms.isMirror ) {
+ qglCullFace( GL_FRONT );
+ qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR );
+
+ R_RenderShadowEdges();
+
+ qglCullFace( GL_BACK );
+ qglStencilOp( GL_KEEP, GL_KEEP, GL_DECR );
+
+ R_RenderShadowEdges();
+ } else {
+ qglCullFace( GL_BACK );
+ qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR );
+
+ R_RenderShadowEdges();
+
+ qglCullFace( GL_FRONT );
+ qglStencilOp( GL_KEEP, GL_KEEP, GL_DECR );
+
+ R_RenderShadowEdges();
+ }
+
+
+ // reenable writing to the color buffer
+ qglColorMask(rgba[0], rgba[1], rgba[2], rgba[3]);
+}
+
+
+/*
+=================
+RB_ShadowFinish
+
+Darken everything that is is a shadow volume.
+We have to delay this until everything has been shadowed,
+because otherwise shadows from different body parts would
+overlap and double darken.
+=================
+*/
+void RB_ShadowFinish( void ) {
+#ifdef PANDORA
+ vec3_t quad[4] = {
+ {-100.0f, 100.0f, -10.0f},
+ { 100.0f, 100.0f, -10.0f},
+ { 100.0f, -100.0f, -10.0f},
+ {-100.0f, -100.0f, -10.0f}
+ };
+ glIndex_t indicies[6] = { 0, 1, 2, 0, 3, 2 };
+#endif
+ if ( r_shadows->integer != 2 ) {
+ return;
+ }
+ if ( glConfig.stencilBits < 4 ) {
+ return;
+ }
+ qglEnable( GL_STENCIL_TEST );
+ qglStencilFunc( GL_NOTEQUAL, 0, 255 );
+
+ qglDisable (GL_CLIP_PLANE0);
+ qglDisable (GL_CULL_FACE);
+
+ GL_Bind( tr.whiteImage );
+
+ qglLoadIdentity ();
+#ifdef PANDORA
+ glColor4f( 0.6f, 0.6f, 0.6f, 1.0f );
+#else
+ qglColor3f( 0.6f, 0.6f, 0.6f );
+#endif
+ GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO );
+
+// qglColor3f( 1, 0, 0 );
+// GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO );
+#ifdef PANDORA
+ qglVertexPointer(3, GL_FLOAT, 0, quad);
+ qglDrawElements(GL_TRIANGLE_STRIP, 6, GL_INDEX_TYPE, indicies);
+ glColor4f(1,1,1,1);
+#else
+ qglBegin( GL_QUADS );
+ qglVertex3f( -100, 100, -10 );
+ qglVertex3f( 100, 100, -10 );
+ qglVertex3f( 100, -100, -10 );
+ qglVertex3f( -100, -100, -10 );
+ qglEnd ();
+
+ qglColor4f(1,1,1,1);
+#endif
+ qglDisable( GL_STENCIL_TEST );
+}
+
+
+/*
+=================
+RB_ProjectionShadowDeform
+
+=================
+*/
+void RB_ProjectionShadowDeform( void ) {
+ float *xyz;
+ int i;
+ float h;
+ vec3_t ground;
+ vec3_t light;
+ float groundDist;
+ float d;
+ vec3_t lightDir;
+
+ xyz = ( float * ) tess.xyz;
+
+ ground[0] = backEnd.or.axis[0][2];
+ ground[1] = backEnd.or.axis[1][2];
+ ground[2] = backEnd.or.axis[2][2];
+
+ groundDist = backEnd.or.origin[2] - backEnd.currentEntity->e.shadowPlane;
+
+ VectorCopy( backEnd.currentEntity->lightDir, lightDir );
+ d = DotProduct( lightDir, ground );
+ // don't let the shadows get too long or go negative
+ if ( d < 0.5 ) {
+ VectorMA( lightDir, (0.5 - d), ground, lightDir );
+ d = DotProduct( lightDir, ground );
+ }
+ d = 1.0 / d;
+
+ light[0] = lightDir[0] * d;
+ light[1] = lightDir[1] * d;
+ light[2] = lightDir[2] * d;
+
+ for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) {
+ h = DotProduct( xyz, ground ) + groundDist;
+
+ xyz[0] -= light[0] * h;
+ xyz[1] -= light[1] * h;
+ xyz[2] -= light[2] * h;
+ }
+}
diff --git a/code/renderer/tr_sky.c b/code/renderer/tr_sky.c
new file mode 100644
index 0000000..821f684
--- /dev/null
+++ b/code/renderer/tr_sky.c
@@ -0,0 +1,873 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// tr_sky.c
+#include "tr_local.h"
+
+#define SKY_SUBDIVISIONS 8
+#define HALF_SKY_SUBDIVISIONS (SKY_SUBDIVISIONS/2)
+
+static float s_cloudTexCoords[6][SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2];
+static float s_cloudTexP[6][SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1];
+
+#ifdef PANDORA
+#define qglDepthRange qglDepthRangef
+#endif
+
+/*
+===================================================================================
+
+POLYGON TO BOX SIDE PROJECTION
+
+===================================================================================
+*/
+
+static vec3_t sky_clip[6] =
+{
+ {1,1,0},
+ {1,-1,0},
+ {0,-1,1},
+ {0,1,1},
+ {1,0,1},
+ {-1,0,1}
+};
+
+static float sky_mins[2][6], sky_maxs[2][6];
+static float sky_min, sky_max;
+
+/*
+================
+AddSkyPolygon
+================
+*/
+static void AddSkyPolygon (int nump, vec3_t vecs)
+{
+ int i,j;
+ vec3_t v, av;
+ float s, t, dv;
+ int axis;
+ float *vp;
+ // s = [0]/[2], t = [1]/[2]
+ static int vec_to_st[6][3] =
+ {
+ {-2,3,1},
+ {2,3,-1},
+
+ {1,3,2},
+ {-1,3,-2},
+
+ {-2,-1,3},
+ {-2,1,-3}
+
+ // {-1,2,3},
+ // {1,2,-3}
+ };
+
+ // decide which face it maps to
+ VectorCopy (vec3_origin, v);
+ for (i=0, vp=vecs ; i<nump ; i++, vp+=3)
+ {
+ VectorAdd (vp, v, v);
+ }
+ av[0] = fabs(v[0]);
+ av[1] = fabs(v[1]);
+ av[2] = fabs(v[2]);
+ if (av[0] > av[1] && av[0] > av[2])
+ {
+ if (v[0] < 0)
+ axis = 1;
+ else
+ axis = 0;
+ }
+ else if (av[1] > av[2] && av[1] > av[0])
+ {
+ if (v[1] < 0)
+ axis = 3;
+ else
+ axis = 2;
+ }
+ else
+ {
+ if (v[2] < 0)
+ axis = 5;
+ else
+ axis = 4;
+ }
+
+ // project new texture coords
+ for (i=0 ; i<nump ; i++, vecs+=3)
+ {
+ j = vec_to_st[axis][2];
+ if (j > 0)
+ dv = vecs[j - 1];
+ else
+ dv = -vecs[-j - 1];
+ if (dv < 0.001)
+ continue; // don't divide by zero
+ j = vec_to_st[axis][0];
+ if (j < 0)
+ s = -vecs[-j -1] / dv;
+ else
+ s = vecs[j-1] / dv;
+ j = vec_to_st[axis][1];
+ if (j < 0)
+ t = -vecs[-j -1] / dv;
+ else
+ t = vecs[j-1] / dv;
+
+ if (s < sky_mins[0][axis])
+ sky_mins[0][axis] = s;
+ if (t < sky_mins[1][axis])
+ sky_mins[1][axis] = t;
+ if (s > sky_maxs[0][axis])
+ sky_maxs[0][axis] = s;
+ if (t > sky_maxs[1][axis])
+ sky_maxs[1][axis] = t;
+ }
+}
+
+#define ON_EPSILON 0.1f // point on plane side epsilon
+#define MAX_CLIP_VERTS 64
+/*
+================
+ClipSkyPolygon
+================
+*/
+static void ClipSkyPolygon (int nump, vec3_t vecs, int stage)
+{
+ float *norm;
+ float *v;
+ qboolean front, back;
+ float d, e;
+ float dists[MAX_CLIP_VERTS];
+ int sides[MAX_CLIP_VERTS];
+ vec3_t newv[2][MAX_CLIP_VERTS];
+ int newc[2];
+ int i, j;
+
+ if (nump > MAX_CLIP_VERTS-2)
+ ri.Error (ERR_DROP, "ClipSkyPolygon: MAX_CLIP_VERTS");
+ if (stage == 6)
+ { // fully clipped, so draw it
+ AddSkyPolygon (nump, vecs);
+ return;
+ }
+
+ front = back = qfalse;
+ norm = sky_clip[stage];
+ for (i=0, v = vecs ; i<nump ; i++, v+=3)
+ {
+ d = DotProduct (v, norm);
+ if (d > ON_EPSILON)
+ {
+ front = qtrue;
+ sides[i] = SIDE_FRONT;
+ }
+ else if (d < -ON_EPSILON)
+ {
+ back = qtrue;
+ sides[i] = SIDE_BACK;
+ }
+ else
+ sides[i] = SIDE_ON;
+ dists[i] = d;
+ }
+
+ if (!front || !back)
+ { // not clipped
+ ClipSkyPolygon (nump, vecs, stage+1);
+ return;
+ }
+
+ // clip it
+ sides[i] = sides[0];
+ dists[i] = dists[0];
+ VectorCopy (vecs, (vecs+(i*3)) );
+ newc[0] = newc[1] = 0;
+
+ for (i=0, v = vecs ; i<nump ; i++, v+=3)
+ {
+ switch (sides[i])
+ {
+ case SIDE_FRONT:
+ VectorCopy (v, newv[0][newc[0]]);
+ newc[0]++;
+ break;
+ case SIDE_BACK:
+ VectorCopy (v, newv[1][newc[1]]);
+ newc[1]++;
+ break;
+ case SIDE_ON:
+ VectorCopy (v, newv[0][newc[0]]);
+ newc[0]++;
+ VectorCopy (v, newv[1][newc[1]]);
+ newc[1]++;
+ break;
+ }
+
+ if (sides[i] == SIDE_ON || sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
+ continue;
+
+ d = dists[i] / (dists[i] - dists[i+1]);
+ for (j=0 ; j<3 ; j++)
+ {
+ e = v[j] + d*(v[j+3] - v[j]);
+ newv[0][newc[0]][j] = e;
+ newv[1][newc[1]][j] = e;
+ }
+ newc[0]++;
+ newc[1]++;
+ }
+
+ // continue
+ ClipSkyPolygon (newc[0], newv[0][0], stage+1);
+ ClipSkyPolygon (newc[1], newv[1][0], stage+1);
+}
+
+/*
+==============
+ClearSkyBox
+==============
+*/
+static void ClearSkyBox (void) {
+ int i;
+
+ for (i=0 ; i<6 ; i++) {
+ sky_mins[0][i] = sky_mins[1][i] = 9999;
+ sky_maxs[0][i] = sky_maxs[1][i] = -9999;
+ }
+}
+
+/*
+================
+RB_ClipSkyPolygons
+================
+*/
+void RB_ClipSkyPolygons( shaderCommands_t *input )
+{
+ vec3_t p[5]; // need one extra point for clipping
+ int i, j;
+
+ ClearSkyBox();
+
+ for ( i = 0; i < input->numIndexes; i += 3 )
+ {
+ for (j = 0 ; j < 3 ; j++)
+ {
+ VectorSubtract( input->xyz[input->indexes[i+j]],
+ backEnd.viewParms.or.origin,
+ p[j] );
+ }
+ ClipSkyPolygon( 3, p[0], 0 );
+ }
+}
+
+/*
+===================================================================================
+
+CLOUD VERTEX GENERATION
+
+===================================================================================
+*/
+
+/*
+** MakeSkyVec
+**
+** Parms: s, t range from -1 to 1
+*/
+static void MakeSkyVec( float s, float t, int axis, float outSt[2], vec3_t outXYZ )
+{
+ // 1 = s, 2 = t, 3 = 2048
+ static int st_to_vec[6][3] =
+ {
+ {3,-1,2},
+ {-3,1,2},
+
+ {1,3,2},
+ {-1,-3,2},
+
+ {-2,-1,3}, // 0 degrees yaw, look straight up
+ {2,-1,-3} // look straight down
+ };
+
+ vec3_t b;
+ int j, k;
+ float boxSize;
+
+ boxSize = backEnd.viewParms.zFar / 1.75; // div sqrt(3)
+ b[0] = s*boxSize;
+ b[1] = t*boxSize;
+ b[2] = boxSize;
+
+ for (j=0 ; j<3 ; j++)
+ {
+ k = st_to_vec[axis][j];
+ if (k < 0)
+ {
+ outXYZ[j] = -b[-k - 1];
+ }
+ else
+ {
+ outXYZ[j] = b[k - 1];
+ }
+ }
+
+ // avoid bilerp seam
+ s = (s+1)*0.5;
+ t = (t+1)*0.5;
+ if (s < sky_min)
+ {
+ s = sky_min;
+ }
+ else if (s > sky_max)
+ {
+ s = sky_max;
+ }
+
+ if (t < sky_min)
+ {
+ t = sky_min;
+ }
+ else if (t > sky_max)
+ {
+ t = sky_max;
+ }
+
+ t = 1.0 - t;
+
+
+ if ( outSt )
+ {
+ outSt[0] = s;
+ outSt[1] = t;
+ }
+}
+
+static int sky_texorder[6] = {0,2,1,3,4,5};
+static vec3_t s_skyPoints[SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1];
+static float s_skyTexCoords[SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2];
+
+static void DrawSkySide( struct image_s *image, const int mins[2], const int maxs[2] )
+{
+ int s, t;
+#ifdef PANDORA
+ int size, i = 0;
+ glIndex_t *indicies;
+ size = (maxs[1] - mins[1]) * (maxs[0] - mins[0] + 1);
+ indicies = ri.Hunk_AllocateTempMemory(sizeof(glIndex_t) * size);
+#endif
+
+ GL_Bind( image );
+
+ for ( t = mins[1]+HALF_SKY_SUBDIVISIONS; t < maxs[1]+HALF_SKY_SUBDIVISIONS; t++ )
+ {
+#ifndef PANDORA
+ qglBegin( GL_TRIANGLE_STRIP );
+#endif
+ for ( s = mins[0]+HALF_SKY_SUBDIVISIONS; s <= maxs[0]+HALF_SKY_SUBDIVISIONS; s++ )
+ {
+#ifdef PANDORA
+ indicies[i++] = t * (SKY_SUBDIVISIONS + 1) + s;
+ indicies[i++] = (t + 1) * (SKY_SUBDIVISIONS + 1) + s;
+#else
+ qglTexCoord2fv( s_skyTexCoords[t][s] );
+ qglVertex3fv( s_skyPoints[t][s] );
+
+ qglTexCoord2fv( s_skyTexCoords[t+1][s] );
+ qglVertex3fv( s_skyPoints[t+1][s] );
+#endif
+ }
+#ifndef PANDORA
+ qglEnd();
+#endif
+ }
+#ifdef PANDORA
+ qglDisableClientState(GL_COLOR_ARRAY);
+ qglEnableClientState(GL_TEXTURE_COORD_ARRAY);
+ qglTexCoordPointer(2, GL_FLOAT, 0, s_skyTexCoords);
+ qglVertexPointer(3, GL_FLOAT, 0, s_skyPoints);
+ qglDrawElements(GL_TRIANGLE_STRIP, i, GL_INDEX_TYPE, indicies);
+ Hunk_FreeTempMemory(indicies);
+#endif
+}
+
+static void DrawSkyBox( shader_t *shader )
+{
+ int i;
+
+ sky_min = 0;
+ sky_max = 1;
+
+ Com_Memset( s_skyTexCoords, 0, sizeof( s_skyTexCoords ) );
+
+ for (i=0 ; i<6 ; i++)
+ {
+ int sky_mins_subd[2], sky_maxs_subd[2];
+ int s, t;
+
+ sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS;
+ sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS;
+ sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS;
+ sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS;
+
+ if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) ||
+ ( sky_mins[1][i] >= sky_maxs[1][i] ) )
+ {
+ continue;
+ }
+
+ sky_mins_subd[0] = sky_mins[0][i] * HALF_SKY_SUBDIVISIONS;
+ sky_mins_subd[1] = sky_mins[1][i] * HALF_SKY_SUBDIVISIONS;
+ sky_maxs_subd[0] = sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS;
+ sky_maxs_subd[1] = sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS;
+
+ if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS )
+ sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS;
+ else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS )
+ sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS;
+ if ( sky_mins_subd[1] < -HALF_SKY_SUBDIVISIONS )
+ sky_mins_subd[1] = -HALF_SKY_SUBDIVISIONS;
+ else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS )
+ sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS;
+
+ if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS )
+ sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS;
+ else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS )
+ sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS;
+ if ( sky_maxs_subd[1] < -HALF_SKY_SUBDIVISIONS )
+ sky_maxs_subd[1] = -HALF_SKY_SUBDIVISIONS;
+ else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS )
+ sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS;
+
+ //
+ // iterate through the subdivisions
+ //
+ for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ )
+ {
+ for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ )
+ {
+ MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS,
+ ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS,
+ i,
+ s_skyTexCoords[t][s],
+ s_skyPoints[t][s] );
+ }
+ }
+
+ DrawSkySide( shader->sky.outerbox[sky_texorder[i]],
+ sky_mins_subd,
+ sky_maxs_subd );
+ }
+
+}
+
+static void FillCloudySkySide( const int mins[2], const int maxs[2], qboolean addIndexes )
+{
+ int s, t;
+ int vertexStart = tess.numVertexes;
+ int tHeight, sWidth;
+
+ tHeight = maxs[1] - mins[1] + 1;
+ sWidth = maxs[0] - mins[0] + 1;
+
+ for ( t = mins[1]+HALF_SKY_SUBDIVISIONS; t <= maxs[1]+HALF_SKY_SUBDIVISIONS; t++ )
+ {
+ for ( s = mins[0]+HALF_SKY_SUBDIVISIONS; s <= maxs[0]+HALF_SKY_SUBDIVISIONS; s++ )
+ {
+ VectorAdd( s_skyPoints[t][s], backEnd.viewParms.or.origin, tess.xyz[tess.numVertexes] );
+ tess.texCoords[tess.numVertexes][0][0] = s_skyTexCoords[t][s][0];
+ tess.texCoords[tess.numVertexes][0][1] = s_skyTexCoords[t][s][1];
+
+ tess.numVertexes++;
+
+ if ( tess.numVertexes >= SHADER_MAX_VERTEXES )
+ {
+ ri.Error( ERR_DROP, "SHADER_MAX_VERTEXES hit in FillCloudySkySide()\n" );
+ }
+ }
+ }
+
+ // only add indexes for one pass, otherwise it would draw multiple times for each pass
+ if ( addIndexes ) {
+ for ( t = 0; t < tHeight-1; t++ )
+ {
+ for ( s = 0; s < sWidth-1; s++ )
+ {
+ tess.indexes[tess.numIndexes] = vertexStart + s + t * ( sWidth );
+ tess.numIndexes++;
+ tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth );
+ tess.numIndexes++;
+ tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth );
+ tess.numIndexes++;
+
+ tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth );
+ tess.numIndexes++;
+ tess.indexes[tess.numIndexes] = vertexStart + s + 1 + ( t + 1 ) * ( sWidth );
+ tess.numIndexes++;
+ tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth );
+ tess.numIndexes++;
+ }
+ }
+ }
+}
+
+static void FillCloudBox( const shader_t *shader, int stage )
+{
+ int i;
+
+ for ( i =0; i < 6; i++ )
+ {
+ int sky_mins_subd[2], sky_maxs_subd[2];
+ int s, t;
+ float MIN_T;
+
+ if ( 1 ) // FIXME? shader->sky.fullClouds )
+ {
+ MIN_T = -HALF_SKY_SUBDIVISIONS;
+
+ // still don't want to draw the bottom, even if fullClouds
+ if ( i == 5 )
+ continue;
+ }
+ else
+ {
+ switch( i )
+ {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ MIN_T = -1;
+ break;
+ case 5:
+ // don't draw clouds beneath you
+ continue;
+ case 4: // top
+ default:
+ MIN_T = -HALF_SKY_SUBDIVISIONS;
+ break;
+ }
+ }
+
+ sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS;
+ sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS;
+ sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS;
+ sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS;
+
+ if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) ||
+ ( sky_mins[1][i] >= sky_maxs[1][i] ) )
+ {
+ continue;
+ }
+
+ sky_mins_subd[0] = myftol( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS );
+ sky_mins_subd[1] = myftol( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS );
+ sky_maxs_subd[0] = myftol( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS );
+ sky_maxs_subd[1] = myftol( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS );
+
+ if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS )
+ sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS;
+ else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS )
+ sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS;
+ if ( sky_mins_subd[1] < MIN_T )
+ sky_mins_subd[1] = MIN_T;
+ else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS )
+ sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS;
+
+ if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS )
+ sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS;
+ else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS )
+ sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS;
+ if ( sky_maxs_subd[1] < MIN_T )
+ sky_maxs_subd[1] = MIN_T;
+ else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS )
+ sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS;
+
+ //
+ // iterate through the subdivisions
+ //
+ for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ )
+ {
+ for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ )
+ {
+ MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS,
+ ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS,
+ i,
+ NULL,
+ s_skyPoints[t][s] );
+
+ s_skyTexCoords[t][s][0] = s_cloudTexCoords[i][t][s][0];
+ s_skyTexCoords[t][s][1] = s_cloudTexCoords[i][t][s][1];
+ }
+ }
+
+ // only add indexes for first stage
+ FillCloudySkySide( sky_mins_subd, sky_maxs_subd, ( stage == 0 ) );
+ }
+}
+
+/*
+** R_BuildCloudData
+*/
+void R_BuildCloudData( shaderCommands_t *input )
+{
+ int i;
+ shader_t *shader;
+
+ shader = input->shader;
+
+ assert( shader->isSky );
+
+ sky_min = 1.0 / 256.0f; // FIXME: not correct?
+ sky_max = 255.0 / 256.0f;
+
+ // set up for drawing
+ tess.numIndexes = 0;
+ tess.numVertexes = 0;
+
+ if ( input->shader->sky.cloudHeight )
+ {
+ for ( i = 0; i < MAX_SHADER_STAGES; i++ )
+ {
+ if ( !tess.xstages[i] ) {
+ break;
+ }
+ FillCloudBox( input->shader, i );
+ }
+ }
+}
+
+/*
+** R_InitSkyTexCoords
+** Called when a sky shader is parsed
+*/
+#define SQR( a ) ((a)*(a))
+void R_InitSkyTexCoords( float heightCloud )
+{
+ int i, s, t;
+ float radiusWorld = 4096;
+ float p;
+ float sRad, tRad;
+ vec3_t skyVec;
+ vec3_t v;
+
+ // init zfar so MakeSkyVec works even though
+ // a world hasn't been bounded
+ backEnd.viewParms.zFar = 1024;
+
+ for ( i = 0; i < 6; i++ )
+ {
+ for ( t = 0; t <= SKY_SUBDIVISIONS; t++ )
+ {
+ for ( s = 0; s <= SKY_SUBDIVISIONS; s++ )
+ {
+ // compute vector from view origin to sky side integral point
+ MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS,
+ ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS,
+ i,
+ NULL,
+ skyVec );
+
+ // compute parametric value 'p' that intersects with cloud layer
+ p = ( 1.0f / ( 2 * DotProduct( skyVec, skyVec ) ) ) *
+ ( -2 * skyVec[2] * radiusWorld +
+ 2 * sqrt( SQR( skyVec[2] ) * SQR( radiusWorld ) +
+ 2 * SQR( skyVec[0] ) * radiusWorld * heightCloud +
+ SQR( skyVec[0] ) * SQR( heightCloud ) +
+ 2 * SQR( skyVec[1] ) * radiusWorld * heightCloud +
+ SQR( skyVec[1] ) * SQR( heightCloud ) +
+ 2 * SQR( skyVec[2] ) * radiusWorld * heightCloud +
+ SQR( skyVec[2] ) * SQR( heightCloud ) ) );
+
+ s_cloudTexP[i][t][s] = p;
+
+ // compute intersection point based on p
+ VectorScale( skyVec, p, v );
+ v[2] += radiusWorld;
+
+ // compute vector from world origin to intersection point 'v'
+ VectorNormalize( v );
+
+ sRad = Q_acos( v[0] );
+ tRad = Q_acos( v[1] );
+
+ s_cloudTexCoords[i][t][s][0] = sRad;
+ s_cloudTexCoords[i][t][s][1] = tRad;
+ }
+ }
+ }
+}
+
+//======================================================================================
+
+/*
+** RB_DrawSun
+*/
+void RB_DrawSun( void ) {
+ float size;
+ float dist;
+ vec3_t origin, vec1, vec2;
+ vec3_t temp;
+
+ if ( !backEnd.skyRenderedThisView ) {
+ return;
+ }
+ if ( !r_drawSun->integer ) {
+ return;
+ }
+ qglLoadMatrixf( backEnd.viewParms.world.modelMatrix );
+ qglTranslatef (backEnd.viewParms.or.origin[0], backEnd.viewParms.or.origin[1], backEnd.viewParms.or.origin[2]);
+
+ dist = backEnd.viewParms.zFar / 1.75; // div sqrt(3)
+ size = dist * 0.4;
+
+ VectorScale( tr.sunDirection, dist, origin );
+ PerpendicularVector( vec1, tr.sunDirection );
+ CrossProduct( tr.sunDirection, vec1, vec2 );
+
+ VectorScale( vec1, size, vec1 );
+ VectorScale( vec2, size, vec2 );
+
+ // farthest depth range
+ qglDepthRange( 1.0, 1.0 );
+
+ // FIXME: use quad stamp
+ RB_BeginSurface( tr.sunShader, tess.fogNum );
+ VectorCopy( origin, temp );
+ VectorSubtract( temp, vec1, temp );
+ VectorSubtract( temp, vec2, temp );
+ VectorCopy( temp, tess.xyz[tess.numVertexes] );
+ tess.texCoords[tess.numVertexes][0][0] = 0;
+ tess.texCoords[tess.numVertexes][0][1] = 0;
+ tess.vertexColors[tess.numVertexes][0] = 255;
+ tess.vertexColors[tess.numVertexes][1] = 255;
+ tess.vertexColors[tess.numVertexes][2] = 255;
+ tess.numVertexes++;
+
+ VectorCopy( origin, temp );
+ VectorAdd( temp, vec1, temp );
+ VectorSubtract( temp, vec2, temp );
+ VectorCopy( temp, tess.xyz[tess.numVertexes] );
+ tess.texCoords[tess.numVertexes][0][0] = 0;
+ tess.texCoords[tess.numVertexes][0][1] = 1;
+ tess.vertexColors[tess.numVertexes][0] = 255;
+ tess.vertexColors[tess.numVertexes][1] = 255;
+ tess.vertexColors[tess.numVertexes][2] = 255;
+ tess.numVertexes++;
+
+ VectorCopy( origin, temp );
+ VectorAdd( temp, vec1, temp );
+ VectorAdd( temp, vec2, temp );
+ VectorCopy( temp, tess.xyz[tess.numVertexes] );
+ tess.texCoords[tess.numVertexes][0][0] = 1;
+ tess.texCoords[tess.numVertexes][0][1] = 1;
+ tess.vertexColors[tess.numVertexes][0] = 255;
+ tess.vertexColors[tess.numVertexes][1] = 255;
+ tess.vertexColors[tess.numVertexes][2] = 255;
+ tess.numVertexes++;
+
+ VectorCopy( origin, temp );
+ VectorSubtract( temp, vec1, temp );
+ VectorAdd( temp, vec2, temp );
+ VectorCopy( temp, tess.xyz[tess.numVertexes] );
+ tess.texCoords[tess.numVertexes][0][0] = 1;
+ tess.texCoords[tess.numVertexes][0][1] = 0;
+ tess.vertexColors[tess.numVertexes][0] = 255;
+ tess.vertexColors[tess.numVertexes][1] = 255;
+ tess.vertexColors[tess.numVertexes][2] = 255;
+ tess.numVertexes++;
+
+ tess.indexes[tess.numIndexes++] = 0;
+ tess.indexes[tess.numIndexes++] = 1;
+ tess.indexes[tess.numIndexes++] = 2;
+ tess.indexes[tess.numIndexes++] = 0;
+ tess.indexes[tess.numIndexes++] = 2;
+ tess.indexes[tess.numIndexes++] = 3;
+
+ RB_EndSurface();
+
+ // back to normal depth range
+ qglDepthRange( 0.0, 1.0 );
+}
+
+
+
+
+/*
+================
+RB_StageIteratorSky
+
+All of the visible sky triangles are in tess
+
+Other things could be stuck in here, like birds in the sky, etc
+================
+*/
+void RB_StageIteratorSky( void ) {
+ if ( r_fastsky->integer ) {
+ return;
+ }
+
+ // go through all the polygons and project them onto
+ // the sky box to see which blocks on each side need
+ // to be drawn
+ RB_ClipSkyPolygons( &tess );
+
+ // r_showsky will let all the sky blocks be drawn in
+ // front of everything to allow developers to see how
+ // much sky is getting sucked in
+ if ( r_showsky->integer ) {
+ qglDepthRange( 0.0, 0.0 );
+ } else {
+ qglDepthRange( 1.0, 1.0 );
+ }
+
+ // draw the outer skybox
+ if ( tess.shader->sky.outerbox[0] && tess.shader->sky.outerbox[0] != tr.defaultImage ) {
+#ifdef PANDORA
+ glColor4f( tr.identityLight, tr.identityLight, tr.identityLight, 1.0f );
+#else
+ qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight );
+#endif
+ qglPushMatrix ();
+ GL_State( 0 );
+ qglTranslatef (backEnd.viewParms.or.origin[0], backEnd.viewParms.or.origin[1], backEnd.viewParms.or.origin[2]);
+
+ DrawSkyBox( tess.shader );
+
+ qglPopMatrix();
+ }
+
+ // generate the vertexes for all the clouds, which will be drawn
+ // by the generic shader routine
+ R_BuildCloudData( &tess );
+
+ RB_StageIteratorGeneric();
+
+ // draw the inner skybox
+
+
+ // back to normal depth range
+ qglDepthRange( 0.0, 1.0 );
+
+ // note that sky was drawn so we will draw a sun later
+ backEnd.skyRenderedThisView = qtrue;
+}
+
diff --git a/code/renderer/tr_surface.c b/code/renderer/tr_surface.c
new file mode 100644
index 0000000..96f7cb1
--- /dev/null
+++ b/code/renderer/tr_surface.c
@@ -0,0 +1,1290 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+// tr_surf.c
+#include "tr_local.h"
+#if idppc_altivec && !defined(MACOS_X)
+#include <altivec.h>
+#endif
+
+/*
+
+ THIS ENTIRE FILE IS BACK END
+
+backEnd.currentEntity will be valid.
+
+Tess_Begin has already been called for the surface's shader.
+
+The modelview matrix will be set.
+
+It is safe to actually issue drawing commands here if you don't want to
+use the shader system.
+*/
+
+
+//============================================================================
+
+
+/*
+==============
+RB_CheckOverflow
+==============
+*/
+void RB_CheckOverflow( int verts, int indexes ) {
+ if (tess.numVertexes + verts < SHADER_MAX_VERTEXES
+ && tess.numIndexes + indexes < SHADER_MAX_INDEXES) {
+ return;
+ }
+
+ RB_EndSurface();
+
+ if ( verts >= SHADER_MAX_VERTEXES ) {
+ ri.Error(ERR_DROP, "RB_CheckOverflow: verts > MAX (%d > %d)", verts, SHADER_MAX_VERTEXES );
+ }
+ if ( indexes >= SHADER_MAX_INDEXES ) {
+ ri.Error(ERR_DROP, "RB_CheckOverflow: indices > MAX (%d > %d)", indexes, SHADER_MAX_INDEXES );
+ }
+
+ RB_BeginSurface(tess.shader, tess.fogNum );
+}
+
+
+/*
+==============
+RB_AddQuadStampExt
+==============
+*/
+void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, byte *color, float s1, float t1, float s2, float t2 ) {
+ vec3_t normal;
+ int ndx;
+
+ RB_CHECKOVERFLOW( 4, 6 );
+
+ ndx = tess.numVertexes;
+
+ // triangle indexes for a simple quad
+ tess.indexes[ tess.numIndexes ] = ndx;
+ tess.indexes[ tess.numIndexes + 1 ] = ndx + 1;
+ tess.indexes[ tess.numIndexes + 2 ] = ndx + 3;
+
+ tess.indexes[ tess.numIndexes + 3 ] = ndx + 3;
+ tess.indexes[ tess.numIndexes + 4 ] = ndx + 1;
+ tess.indexes[ tess.numIndexes + 5 ] = ndx + 2;
+
+ tess.xyz[ndx][0] = origin[0] + left[0] + up[0];
+ tess.xyz[ndx][1] = origin[1] + left[1] + up[1];
+ tess.xyz[ndx][2] = origin[2] + left[2] + up[2];
+
+ tess.xyz[ndx+1][0] = origin[0] - left[0] + up[0];
+ tess.xyz[ndx+1][1] = origin[1] - left[1] + up[1];
+ tess.xyz[ndx+1][2] = origin[2] - left[2] + up[2];
+
+ tess.xyz[ndx+2][0] = origin[0] - left[0] - up[0];
+ tess.xyz[ndx+2][1] = origin[1] - left[1] - up[1];
+ tess.xyz[ndx+2][2] = origin[2] - left[2] - up[2];
+
+ tess.xyz[ndx+3][0] = origin[0] + left[0] - up[0];
+ tess.xyz[ndx+3][1] = origin[1] + left[1] - up[1];
+ tess.xyz[ndx+3][2] = origin[2] + left[2] - up[2];
+
+
+ // constant normal all the way around
+ VectorSubtract( vec3_origin, backEnd.viewParms.or.axis[0], normal );
+
+ tess.normal[ndx][0] = tess.normal[ndx+1][0] = tess.normal[ndx+2][0] = tess.normal[ndx+3][0] = normal[0];
+ tess.normal[ndx][1] = tess.normal[ndx+1][1] = tess.normal[ndx+2][1] = tess.normal[ndx+3][1] = normal[1];
+ tess.normal[ndx][2] = tess.normal[ndx+1][2] = tess.normal[ndx+2][2] = tess.normal[ndx+3][2] = normal[2];
+
+ // standard square texture coordinates
+ tess.texCoords[ndx][0][0] = tess.texCoords[ndx][1][0] = s1;
+ tess.texCoords[ndx][0][1] = tess.texCoords[ndx][1][1] = t1;
+
+ tess.texCoords[ndx+1][0][0] = tess.texCoords[ndx+1][1][0] = s2;
+ tess.texCoords[ndx+1][0][1] = tess.texCoords[ndx+1][1][1] = t1;
+
+ tess.texCoords[ndx+2][0][0] = tess.texCoords[ndx+2][1][0] = s2;
+ tess.texCoords[ndx+2][0][1] = tess.texCoords[ndx+2][1][1] = t2;
+
+ tess.texCoords[ndx+3][0][0] = tess.texCoords[ndx+3][1][0] = s1;
+ tess.texCoords[ndx+3][0][1] = tess.texCoords[ndx+3][1][1] = t2;
+
+ // constant color all the way around
+ // should this be identity and let the shader specify from entity?
+ * ( unsigned int * ) &tess.vertexColors[ndx] =
+ * ( unsigned int * ) &tess.vertexColors[ndx+1] =
+ * ( unsigned int * ) &tess.vertexColors[ndx+2] =
+ * ( unsigned int * ) &tess.vertexColors[ndx+3] =
+ * ( unsigned int * )color;
+
+
+ tess.numVertexes += 4;
+ tess.numIndexes += 6;
+}
+
+/*
+==============
+RB_AddQuadStamp
+==============
+*/
+void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, byte *color ) {
+ RB_AddQuadStampExt( origin, left, up, color, 0, 0, 1, 1 );
+}
+
+/*
+==============
+RB_SurfaceSprite
+==============
+*/
+static void RB_SurfaceSprite( void ) {
+ vec3_t left, up;
+ float radius;
+
+ // calculate the xyz locations for the four corners
+ radius = backEnd.currentEntity->e.radius;
+ if ( backEnd.currentEntity->e.rotation == 0 ) {
+ VectorScale( backEnd.viewParms.or.axis[1], radius, left );
+ VectorScale( backEnd.viewParms.or.axis[2], radius, up );
+ } else {
+ float s, c;
+ float ang;
+
+ ang = M_PI * backEnd.currentEntity->e.rotation / 180;
+ s = sin( ang );
+ c = cos( ang );
+
+ VectorScale( backEnd.viewParms.or.axis[1], c * radius, left );
+ VectorMA( left, -s * radius, backEnd.viewParms.or.axis[2], left );
+
+ VectorScale( backEnd.viewParms.or.axis[2], c * radius, up );
+ VectorMA( up, s * radius, backEnd.viewParms.or.axis[1], up );
+ }
+ if ( backEnd.viewParms.isMirror ) {
+ VectorSubtract( vec3_origin, left, left );
+ }
+
+ RB_AddQuadStamp( backEnd.currentEntity->e.origin, left, up, backEnd.currentEntity->e.shaderRGBA );
+}
+
+
+/*
+=============
+RB_SurfacePolychain
+=============
+*/
+static void RB_SurfacePolychain( srfPoly_t *p ) {
+ int i;
+ int numv;
+
+ RB_CHECKOVERFLOW( p->numVerts, 3*(p->numVerts - 2) );
+
+ // fan triangles into the tess array
+ numv = tess.numVertexes;
+ for ( i = 0; i < p->numVerts; i++ ) {
+ VectorCopy( p->verts[i].xyz, tess.xyz[numv] );
+ tess.texCoords[numv][0][0] = p->verts[i].st[0];
+ tess.texCoords[numv][0][1] = p->verts[i].st[1];
+ *(int *)&tess.vertexColors[numv] = *(int *)p->verts[ i ].modulate;
+
+ numv++;
+ }
+
+ // generate fan indexes into the tess array
+ for ( i = 0; i < p->numVerts-2; i++ ) {
+ tess.indexes[tess.numIndexes + 0] = tess.numVertexes;
+ tess.indexes[tess.numIndexes + 1] = tess.numVertexes + i + 1;
+ tess.indexes[tess.numIndexes + 2] = tess.numVertexes + i + 2;
+ tess.numIndexes += 3;
+ }
+
+ tess.numVertexes = numv;
+}
+
+
+/*
+=============
+RB_SurfaceTriangles
+=============
+*/
+static void RB_SurfaceTriangles( srfTriangles_t *srf ) {
+ int i;
+ drawVert_t *dv;
+ float *xyz, *normal, *texCoords;
+ byte *color;
+ int dlightBits;
+ qboolean needsNormal;
+
+ dlightBits = srf->dlightBits[backEnd.smpFrame];
+ tess.dlightBits |= dlightBits;
+
+ RB_CHECKOVERFLOW( srf->numVerts, srf->numIndexes );
+
+ for ( i = 0 ; i < srf->numIndexes ; i += 3 ) {
+ tess.indexes[ tess.numIndexes + i + 0 ] = tess.numVertexes + srf->indexes[ i + 0 ];
+ tess.indexes[ tess.numIndexes + i + 1 ] = tess.numVertexes + srf->indexes[ i + 1 ];
+ tess.indexes[ tess.numIndexes + i + 2 ] = tess.numVertexes + srf->indexes[ i + 2 ];
+ }
+ tess.numIndexes += srf->numIndexes;
+
+ dv = srf->verts;
+ xyz = tess.xyz[ tess.numVertexes ];
+ normal = tess.normal[ tess.numVertexes ];
+ texCoords = tess.texCoords[ tess.numVertexes ][0];
+ color = tess.vertexColors[ tess.numVertexes ];
+ needsNormal = tess.shader->needsNormal;
+
+ for ( i = 0 ; i < srf->numVerts ; i++, dv++, xyz += 4, normal += 4, texCoords += 4, color += 4 ) {
+ xyz[0] = dv->xyz[0];
+ xyz[1] = dv->xyz[1];
+ xyz[2] = dv->xyz[2];
+
+ if ( needsNormal ) {
+ normal[0] = dv->normal[0];
+ normal[1] = dv->normal[1];
+ normal[2] = dv->normal[2];
+ }
+
+ texCoords[0] = dv->st[0];
+ texCoords[1] = dv->st[1];
+
+ texCoords[2] = dv->lightmap[0];
+ texCoords[3] = dv->lightmap[1];
+
+ *(int *)color = *(int *)dv->color;
+ }
+
+ for ( i = 0 ; i < srf->numVerts ; i++ ) {
+ tess.vertexDlightBits[ tess.numVertexes + i] = dlightBits;
+ }
+
+ tess.numVertexes += srf->numVerts;
+}
+
+
+
+/*
+==============
+RB_SurfaceBeam
+==============
+*/
+static void RB_SurfaceBeam( void )
+{
+#define NUM_BEAM_SEGS 6
+ refEntity_t *e;
+ int i;
+ vec3_t perpvec;
+ vec3_t direction, normalized_direction;
+#ifdef PANDORA
+ vec3_t points[NUM_BEAM_SEGS * 2];
+#else
+ vec3_t start_points[NUM_BEAM_SEGS], end_points[NUM_BEAM_SEGS];
+#endif
+ vec3_t oldorigin, origin;
+
+ e = &backEnd.currentEntity->e;
+
+ oldorigin[0] = e->oldorigin[0];
+ oldorigin[1] = e->oldorigin[1];
+ oldorigin[2] = e->oldorigin[2];
+
+ origin[0] = e->origin[0];
+ origin[1] = e->origin[1];
+ origin[2] = e->origin[2];
+
+ normalized_direction[0] = direction[0] = oldorigin[0] - origin[0];
+ normalized_direction[1] = direction[1] = oldorigin[1] - origin[1];
+ normalized_direction[2] = direction[2] = oldorigin[2] - origin[2];
+
+ if ( VectorNormalize( normalized_direction ) == 0 )
+ return;
+
+ PerpendicularVector( perpvec, normalized_direction );
+
+ VectorScale( perpvec, 4, perpvec );
+
+ for ( i = 0; i < NUM_BEAM_SEGS ; i++ )
+ {
+#ifdef PANDORA
+ RotatePointAroundVector(points[i * 2], normalized_direction, perpvec, (360.0 / NUM_BEAM_SEGS) * i);
+ VectorAdd(points[i * 2], direction, points[i * 2 + 1]);
+#else
+ RotatePointAroundVector( start_points[i], normalized_direction, perpvec, (360.0/NUM_BEAM_SEGS)*i );
+// VectorAdd( start_points[i], origin, start_points[i] );
+ VectorAdd( start_points[i], direction, end_points[i] );
+#endif
+ }
+
+ GL_Bind( tr.whiteImage );
+
+ GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE );
+#ifdef PANDORA
+ glColor4f( 1, 0, 0, 1 );
+ qglVertexPointer(3, GL_FLOAT, 0, points);
+ qglDrawArrays(GL_TRIANGLE_STRIP, 0, NUM_BEAM_SEGS * 2);
+#else
+ qglColor3f( 1, 0, 0 );
+
+ qglBegin( GL_TRIANGLE_STRIP );
+ for ( i = 0; i <= NUM_BEAM_SEGS; i++ ) {
+ qglVertex3fv( start_points[ i % NUM_BEAM_SEGS] );
+ qglVertex3fv( end_points[ i % NUM_BEAM_SEGS] );
+ }
+ qglEnd();
+#endif
+}
+
+//================================================================================
+
+static void DoRailCore( const vec3_t start, const vec3_t end, const vec3_t up, float len, float spanWidth )
+{
+ float spanWidth2;
+ int vbase;
+ float t = len / 256.0f;
+
+ vbase = tess.numVertexes;
+
+ spanWidth2 = -spanWidth;
+
+ // FIXME: use quad stamp?
+ VectorMA( start, spanWidth, up, tess.xyz[tess.numVertexes] );
+ tess.texCoords[tess.numVertexes][0][0] = 0;
+ tess.texCoords[tess.numVertexes][0][1] = 0;
+ tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] * 0.25;
+ tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] * 0.25;
+ tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] * 0.25;
+ tess.numVertexes++;
+
+ VectorMA( start, spanWidth2, up, tess.xyz[tess.numVertexes] );
+ tess.texCoords[tess.numVertexes][0][0] = 0;
+ tess.texCoords[tess.numVertexes][0][1] = 1;
+ tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0];
+ tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1];
+ tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2];
+ tess.numVertexes++;
+
+ VectorMA( end, spanWidth, up, tess.xyz[tess.numVertexes] );
+
+ tess.texCoords[tess.numVertexes][0][0] = t;
+ tess.texCoords[tess.numVertexes][0][1] = 0;
+ tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0];
+ tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1];
+ tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2];
+ tess.numVertexes++;
+
+ VectorMA( end, spanWidth2, up, tess.xyz[tess.numVertexes] );
+ tess.texCoords[tess.numVertexes][0][0] = t;
+ tess.texCoords[tess.numVertexes][0][1] = 1;
+ tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0];
+ tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1];
+ tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2];
+ tess.numVertexes++;
+
+ tess.indexes[tess.numIndexes++] = vbase;
+ tess.indexes[tess.numIndexes++] = vbase + 1;
+ tess.indexes[tess.numIndexes++] = vbase + 2;
+
+ tess.indexes[tess.numIndexes++] = vbase + 2;
+ tess.indexes[tess.numIndexes++] = vbase + 1;
+ tess.indexes[tess.numIndexes++] = vbase + 3;
+}
+
+static void DoRailDiscs( int numSegs, const vec3_t start, const vec3_t dir, const vec3_t right, const vec3_t up )
+{
+ int i;
+ vec3_t pos[4];
+ vec3_t v;
+ int spanWidth = r_railWidth->integer;
+ float c, s;
+ float scale;
+
+ if ( numSegs > 1 )
+ numSegs--;
+ if ( !numSegs )
+ return;
+
+ scale = 0.25;
+
+ for ( i = 0; i < 4; i++ )
+ {
+ c = cos( DEG2RAD( 45 + i * 90 ) );
+ s = sin( DEG2RAD( 45 + i * 90 ) );
+ v[0] = ( right[0] * c + up[0] * s ) * scale * spanWidth;
+ v[1] = ( right[1] * c + up[1] * s ) * scale * spanWidth;
+ v[2] = ( right[2] * c + up[2] * s ) * scale * spanWidth;
+ VectorAdd( start, v, pos[i] );
+
+ if ( numSegs > 1 )
+ {
+ // offset by 1 segment if we're doing a long distance shot
+ VectorAdd( pos[i], dir, pos[i] );
+ }
+ }
+
+ for ( i = 0; i < numSegs; i++ )
+ {
+ int j;
+
+ RB_CHECKOVERFLOW( 4, 6 );
+
+ for ( j = 0; j < 4; j++ )
+ {
+ VectorCopy( pos[j], tess.xyz[tess.numVertexes] );
+ tess.texCoords[tess.numVertexes][0][0] = ( j < 2 );
+ tess.texCoords[tess.numVertexes][0][1] = ( j && j != 3 );
+ tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0];
+ tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1];
+ tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2];
+ tess.numVertexes++;
+
+ VectorAdd( pos[j], dir, pos[j] );
+ }
+
+ tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 0;
+ tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 1;
+ tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 3;
+ tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 3;
+ tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 1;
+ tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 2;
+ }
+}
+
+/*
+** RB_SurfaceRailRinges
+*/
+static void RB_SurfaceRailRings( void ) {
+ refEntity_t *e;
+ int numSegs;
+ int len;
+ vec3_t vec;
+ vec3_t right, up;
+ vec3_t start, end;
+
+ e = &backEnd.currentEntity->e;
+
+ VectorCopy( e->oldorigin, start );
+ VectorCopy( e->origin, end );
+
+ // compute variables
+ VectorSubtract( end, start, vec );
+ len = VectorNormalize( vec );
+ MakeNormalVectors( vec, right, up );
+ numSegs = ( len ) / r_railSegmentLength->value;
+ if ( numSegs <= 0 ) {
+ numSegs = 1;
+ }
+
+ VectorScale( vec, r_railSegmentLength->value, vec );
+
+ DoRailDiscs( numSegs, start, vec, right, up );
+}
+
+/*
+** RB_SurfaceRailCore
+*/
+static void RB_SurfaceRailCore( void ) {
+ refEntity_t *e;
+ int len;
+ vec3_t right;
+ vec3_t vec;
+ vec3_t start, end;
+ vec3_t v1, v2;
+
+ e = &backEnd.currentEntity->e;
+
+ VectorCopy( e->oldorigin, start );
+ VectorCopy( e->origin, end );
+
+ VectorSubtract( end, start, vec );
+ len = VectorNormalize( vec );
+
+ // compute side vector
+ VectorSubtract( start, backEnd.viewParms.or.origin, v1 );
+ VectorNormalize( v1 );
+ VectorSubtract( end, backEnd.viewParms.or.origin, v2 );
+ VectorNormalize( v2 );
+ CrossProduct( v1, v2, right );
+ VectorNormalize( right );
+
+ DoRailCore( start, end, right, len, r_railCoreWidth->integer );
+}
+
+/*
+** RB_SurfaceLightningBolt
+*/
+static void RB_SurfaceLightningBolt( void ) {
+ refEntity_t *e;
+ int len;
+ vec3_t right;
+ vec3_t vec;
+ vec3_t start, end;
+ vec3_t v1, v2;
+ int i;
+
+ e = &backEnd.currentEntity->e;
+
+ VectorCopy( e->oldorigin, end );
+ VectorCopy( e->origin, start );
+
+ // compute variables
+ VectorSubtract( end, start, vec );
+ len = VectorNormalize( vec );
+
+ // compute side vector
+ VectorSubtract( start, backEnd.viewParms.or.origin, v1 );
+ VectorNormalize( v1 );
+ VectorSubtract( end, backEnd.viewParms.or.origin, v2 );
+ VectorNormalize( v2 );
+ CrossProduct( v1, v2, right );
+ VectorNormalize( right );
+
+ for ( i = 0 ; i < 4 ; i++ ) {
+ vec3_t temp;
+
+ DoRailCore( start, end, right, len, 8 );
+ RotatePointAroundVector( temp, vec, right, 45 );
+ VectorCopy( temp, right );
+ }
+}
+
+/*
+** VectorArrayNormalize
+*
+* The inputs to this routing seem to always be close to length = 1.0 (about 0.6 to 2.0)
+* This means that we don't have to worry about zero length or enormously long vectors.
+*/
+static void VectorArrayNormalize(vec4_t *normals, unsigned int count)
+{
+// assert(count);
+
+#if idppc
+ {
+ register float half = 0.5;
+ register float one = 1.0;
+ float *components = (float *)normals;
+
+ // Vanilla PPC code, but since PPC has a reciprocal square root estimate instruction,
+ // runs *much* faster than calling sqrt(). We'll use a single Newton-Raphson
+ // refinement step to get a little more precision. This seems to yeild results
+ // that are correct to 3 decimal places and usually correct to at least 4 (sometimes 5).
+ // (That is, for the given input range of about 0.6 to 2.0).
+ do {
+ float x, y, z;
+ float B, y0, y1;
+
+ x = components[0];
+ y = components[1];
+ z = components[2];
+ components += 4;
+ B = x*x + y*y + z*z;
+
+#ifdef __GNUC__
+ asm("frsqrte %0,%1" : "=f" (y0) : "f" (B));
+#else
+ y0 = __frsqrte(B);
+#endif
+ y1 = y0 + half*y0*(one - B*y0*y0);
+
+ x = x * y1;
+ y = y * y1;
+ components[-4] = x;
+ z = z * y1;
+ components[-3] = y;
+ components[-2] = z;
+ } while(count--);
+ }
+#else // No assembly version for this architecture, or C_ONLY defined
+ // given the input, it's safe to call VectorNormalizeFast
+ while (count--) {
+ VectorNormalizeFast(normals[0]);
+ normals++;
+ }
+#endif
+
+}
+
+
+
+/*
+** LerpMeshVertexes
+*/
+#if idppc_altivec
+static void LerpMeshVertexes_altivec(md3Surface_t *surf, float backlerp)
+{
+ short *oldXyz, *newXyz, *oldNormals, *newNormals;
+ float *outXyz, *outNormal;
+ float oldXyzScale ALIGN(16);
+ float newXyzScale ALIGN(16);
+ float oldNormalScale ALIGN(16);
+ float newNormalScale ALIGN(16);
+ int vertNum;
+ unsigned lat, lng;
+ int numVerts;
+
+ outXyz = tess.xyz[tess.numVertexes];
+ outNormal = tess.normal[tess.numVertexes];
+
+ newXyz = (short *)((byte *)surf + surf->ofsXyzNormals)
+ + (backEnd.currentEntity->e.frame * surf->numVerts * 4);
+ newNormals = newXyz + 3;
+
+ newXyzScale = MD3_XYZ_SCALE * (1.0 - backlerp);
+ newNormalScale = 1.0 - backlerp;
+
+ numVerts = surf->numVerts;
+
+ if ( backlerp == 0 ) {
+ vector signed short newNormalsVec0;
+ vector signed short newNormalsVec1;
+ vector signed int newNormalsIntVec;
+ vector float newNormalsFloatVec;
+ vector float newXyzScaleVec;
+ vector unsigned char newNormalsLoadPermute;
+ vector unsigned char newNormalsStorePermute;
+ vector float zero;
+
+ newNormalsStorePermute = vec_lvsl(0,(float *)&newXyzScaleVec);
+ newXyzScaleVec = *(vector float *)&newXyzScale;
+ newXyzScaleVec = vec_perm(newXyzScaleVec,newXyzScaleVec,newNormalsStorePermute);
+ newXyzScaleVec = vec_splat(newXyzScaleVec,0);
+ newNormalsLoadPermute = vec_lvsl(0,newXyz);
+ newNormalsStorePermute = vec_lvsr(0,outXyz);
+ zero = (vector float)vec_splat_s8(0);
+ //
+ // just copy the vertexes
+ //
+ for (vertNum=0 ; vertNum < numVerts ; vertNum++,
+ newXyz += 4, newNormals += 4,
+ outXyz += 4, outNormal += 4)
+ {
+ newNormalsLoadPermute = vec_lvsl(0,newXyz);
+ newNormalsStorePermute = vec_lvsr(0,outXyz);
+ newNormalsVec0 = vec_ld(0,newXyz);
+ newNormalsVec1 = vec_ld(16,newXyz);
+ newNormalsVec0 = vec_perm(newNormalsVec0,newNormalsVec1,newNormalsLoadPermute);
+ newNormalsIntVec = vec_unpackh(newNormalsVec0);
+ newNormalsFloatVec = vec_ctf(newNormalsIntVec,0);
+ newNormalsFloatVec = vec_madd(newNormalsFloatVec,newXyzScaleVec,zero);
+ newNormalsFloatVec = vec_perm(newNormalsFloatVec,newNormalsFloatVec,newNormalsStorePermute);
+ //outXyz[0] = newXyz[0] * newXyzScale;
+ //outXyz[1] = newXyz[1] * newXyzScale;
+ //outXyz[2] = newXyz[2] * newXyzScale;
+
+ lat = ( newNormals[0] >> 8 ) & 0xff;
+ lng = ( newNormals[0] & 0xff );
+ lat *= (FUNCTABLE_SIZE/256);
+ lng *= (FUNCTABLE_SIZE/256);
+
+ // decode X as cos( lat ) * sin( long )
+ // decode Y as sin( lat ) * sin( long )
+ // decode Z as cos( long )
+
+ outNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+ outNormal[1] = tr.sinTable[lat] * tr.sinTable[lng];
+ outNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+
+ vec_ste(newNormalsFloatVec,0,outXyz);
+ vec_ste(newNormalsFloatVec,4,outXyz);
+ vec_ste(newNormalsFloatVec,8,outXyz);
+ }
+ } else {
+ //
+ // interpolate and copy the vertex and normal
+ //
+ oldXyz = (short *)((byte *)surf + surf->ofsXyzNormals)
+ + (backEnd.currentEntity->e.oldframe * surf->numVerts * 4);
+ oldNormals = oldXyz + 3;
+
+ oldXyzScale = MD3_XYZ_SCALE * backlerp;
+ oldNormalScale = backlerp;
+
+ for (vertNum=0 ; vertNum < numVerts ; vertNum++,
+ oldXyz += 4, newXyz += 4, oldNormals += 4, newNormals += 4,
+ outXyz += 4, outNormal += 4)
+ {
+ vec3_t uncompressedOldNormal, uncompressedNewNormal;
+
+ // interpolate the xyz
+ outXyz[0] = oldXyz[0] * oldXyzScale + newXyz[0] * newXyzScale;
+ outXyz[1] = oldXyz[1] * oldXyzScale + newXyz[1] * newXyzScale;
+ outXyz[2] = oldXyz[2] * oldXyzScale + newXyz[2] * newXyzScale;
+
+ // FIXME: interpolate lat/long instead?
+ lat = ( newNormals[0] >> 8 ) & 0xff;
+ lng = ( newNormals[0] & 0xff );
+ lat *= 4;
+ lng *= 4;
+ uncompressedNewNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+ uncompressedNewNormal[1] = tr.sinTable[lat] * tr.sinTable[lng];
+ uncompressedNewNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+
+ lat = ( oldNormals[0] >> 8 ) & 0xff;
+ lng = ( oldNormals[0] & 0xff );
+ lat *= 4;
+ lng *= 4;
+
+ uncompressedOldNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+ uncompressedOldNormal[1] = tr.sinTable[lat] * tr.sinTable[lng];
+ uncompressedOldNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+
+ outNormal[0] = uncompressedOldNormal[0] * oldNormalScale + uncompressedNewNormal[0] * newNormalScale;
+ outNormal[1] = uncompressedOldNormal[1] * oldNormalScale + uncompressedNewNormal[1] * newNormalScale;
+ outNormal[2] = uncompressedOldNormal[2] * oldNormalScale + uncompressedNewNormal[2] * newNormalScale;
+
+// VectorNormalize (outNormal);
+ }
+ VectorArrayNormalize((vec4_t *)tess.normal[tess.numVertexes], numVerts);
+ }
+}
+#endif
+
+static void LerpMeshVertexes_scalar(md3Surface_t *surf, float backlerp)
+{
+ short *oldXyz, *newXyz, *oldNormals, *newNormals;
+ float *outXyz, *outNormal;
+ float oldXyzScale, newXyzScale;
+ float oldNormalScale, newNormalScale;
+ int vertNum;
+ unsigned lat, lng;
+ int numVerts;
+
+ outXyz = tess.xyz[tess.numVertexes];
+ outNormal = tess.normal[tess.numVertexes];
+
+ newXyz = (short *)((byte *)surf + surf->ofsXyzNormals)
+ + (backEnd.currentEntity->e.frame * surf->numVerts * 4);
+ newNormals = newXyz + 3;
+
+ newXyzScale = MD3_XYZ_SCALE * (1.0 - backlerp);
+ newNormalScale = 1.0 - backlerp;
+
+ numVerts = surf->numVerts;
+
+ if ( backlerp == 0 ) {
+ //
+ // just copy the vertexes
+ //
+ for (vertNum=0 ; vertNum < numVerts ; vertNum++,
+ newXyz += 4, newNormals += 4,
+ outXyz += 4, outNormal += 4)
+ {
+
+ outXyz[0] = newXyz[0] * newXyzScale;
+ outXyz[1] = newXyz[1] * newXyzScale;
+ outXyz[2] = newXyz[2] * newXyzScale;
+
+ lat = ( newNormals[0] >> 8 ) & 0xff;
+ lng = ( newNormals[0] & 0xff );
+ lat *= (FUNCTABLE_SIZE/256);
+ lng *= (FUNCTABLE_SIZE/256);
+
+ // decode X as cos( lat ) * sin( long )
+ // decode Y as sin( lat ) * sin( long )
+ // decode Z as cos( long )
+
+ outNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+ outNormal[1] = tr.sinTable[lat] * tr.sinTable[lng];
+ outNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+ }
+ } else {
+ //
+ // interpolate and copy the vertex and normal
+ //
+ oldXyz = (short *)((byte *)surf + surf->ofsXyzNormals)
+ + (backEnd.currentEntity->e.oldframe * surf->numVerts * 4);
+ oldNormals = oldXyz + 3;
+
+ oldXyzScale = MD3_XYZ_SCALE * backlerp;
+ oldNormalScale = backlerp;
+
+ for (vertNum=0 ; vertNum < numVerts ; vertNum++,
+ oldXyz += 4, newXyz += 4, oldNormals += 4, newNormals += 4,
+ outXyz += 4, outNormal += 4)
+ {
+ vec3_t uncompressedOldNormal, uncompressedNewNormal;
+
+ // interpolate the xyz
+ outXyz[0] = oldXyz[0] * oldXyzScale + newXyz[0] * newXyzScale;
+ outXyz[1] = oldXyz[1] * oldXyzScale + newXyz[1] * newXyzScale;
+ outXyz[2] = oldXyz[2] * oldXyzScale + newXyz[2] * newXyzScale;
+
+ // FIXME: interpolate lat/long instead?
+ lat = ( newNormals[0] >> 8 ) & 0xff;
+ lng = ( newNormals[0] & 0xff );
+ lat *= 4;
+ lng *= 4;
+ uncompressedNewNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+ uncompressedNewNormal[1] = tr.sinTable[lat] * tr.sinTable[lng];
+ uncompressedNewNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+
+ lat = ( oldNormals[0] >> 8 ) & 0xff;
+ lng = ( oldNormals[0] & 0xff );
+ lat *= 4;
+ lng *= 4;
+
+ uncompressedOldNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+ uncompressedOldNormal[1] = tr.sinTable[lat] * tr.sinTable[lng];
+ uncompressedOldNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+
+ outNormal[0] = uncompressedOldNormal[0] * oldNormalScale + uncompressedNewNormal[0] * newNormalScale;
+ outNormal[1] = uncompressedOldNormal[1] * oldNormalScale + uncompressedNewNormal[1] * newNormalScale;
+ outNormal[2] = uncompressedOldNormal[2] * oldNormalScale + uncompressedNewNormal[2] * newNormalScale;
+
+// VectorNormalize (outNormal);
+ }
+ VectorArrayNormalize((vec4_t *)tess.normal[tess.numVertexes], numVerts);
+ }
+}
+
+static void LerpMeshVertexes(md3Surface_t *surf, float backlerp)
+{
+#if idppc_altivec
+ if (com_altivec->integer) {
+ // must be in a seperate function or G3 systems will crash.
+ LerpMeshVertexes_altivec( surf, backlerp );
+ return;
+ }
+#endif // idppc_altivec
+ LerpMeshVertexes_scalar( surf, backlerp );
+}
+
+
+/*
+=============
+RB_SurfaceMesh
+=============
+*/
+static void RB_SurfaceMesh(md3Surface_t *surface) {
+ int j;
+ float backlerp;
+ int *triangles;
+ float *texCoords;
+ int indexes;
+ int Bob, Doug;
+ int numVerts;
+
+ if ( backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame ) {
+ backlerp = 0;
+ } else {
+ backlerp = backEnd.currentEntity->e.backlerp;
+ }
+
+ RB_CHECKOVERFLOW( surface->numVerts, surface->numTriangles*3 );
+
+ LerpMeshVertexes (surface, backlerp);
+
+ triangles = (int *) ((byte *)surface + surface->ofsTriangles);
+ indexes = surface->numTriangles * 3;
+ Bob = tess.numIndexes;
+ Doug = tess.numVertexes;
+ for (j = 0 ; j < indexes ; j++) {
+ tess.indexes[Bob + j] = Doug + triangles[j];
+ }
+ tess.numIndexes += indexes;
+
+ texCoords = (float *) ((byte *)surface + surface->ofsSt);
+
+ numVerts = surface->numVerts;
+ for ( j = 0; j < numVerts; j++ ) {
+ tess.texCoords[Doug + j][0][0] = texCoords[j*2+0];
+ tess.texCoords[Doug + j][0][1] = texCoords[j*2+1];
+ // FIXME: fill in lightmapST for completeness?
+ }
+
+ tess.numVertexes += surface->numVerts;
+
+}
+
+
+/*
+==============
+RB_SurfaceFace
+==============
+*/
+static void RB_SurfaceFace( srfSurfaceFace_t *surf ) {
+ int i;
+#ifdef PANDORA
+ unsigned int *indices;
+ glIndex_t *tessIndexes;
+#else
+ unsigned *indices, *tessIndexes;
+#endif
+ float *v;
+ float *normal;
+ int ndx;
+ int Bob;
+ int numPoints;
+ int dlightBits;
+
+ RB_CHECKOVERFLOW( surf->numPoints, surf->numIndices );
+
+ dlightBits = surf->dlightBits[backEnd.smpFrame];
+ tess.dlightBits |= dlightBits;
+
+ indices = ( unsigned * ) ( ( ( char * ) surf ) + surf->ofsIndices );
+
+ Bob = tess.numVertexes;
+ tessIndexes = tess.indexes + tess.numIndexes;
+ for ( i = surf->numIndices-1 ; i >= 0 ; i-- ) {
+ tessIndexes[i] = indices[i] + Bob;
+ }
+
+ tess.numIndexes += surf->numIndices;
+
+ v = surf->points[0];
+
+ ndx = tess.numVertexes;
+
+ numPoints = surf->numPoints;
+
+ if ( tess.shader->needsNormal ) {
+ normal = surf->plane.normal;
+ for ( i = 0, ndx = tess.numVertexes; i < numPoints; i++, ndx++ ) {
+ VectorCopy( normal, tess.normal[ndx] );
+ }
+ }
+
+ for ( i = 0, v = surf->points[0], ndx = tess.numVertexes; i < numPoints; i++, v += VERTEXSIZE, ndx++ ) {
+ VectorCopy( v, tess.xyz[ndx]);
+ tess.texCoords[ndx][0][0] = v[3];
+ tess.texCoords[ndx][0][1] = v[4];
+ tess.texCoords[ndx][1][0] = v[5];
+ tess.texCoords[ndx][1][1] = v[6];
+ * ( unsigned int * ) &tess.vertexColors[ndx] = * ( unsigned int * ) &v[7];
+ tess.vertexDlightBits[ndx] = dlightBits;
+ }
+
+
+ tess.numVertexes += surf->numPoints;
+}
+
+
+static float LodErrorForVolume( vec3_t local, float radius ) {
+ vec3_t world;
+ float d;
+
+ // never let it go negative
+ if ( r_lodCurveError->value < 0 ) {
+ return 0;
+ }
+
+ world[0] = local[0] * backEnd.or.axis[0][0] + local[1] * backEnd.or.axis[1][0] +
+ local[2] * backEnd.or.axis[2][0] + backEnd.or.origin[0];
+ world[1] = local[0] * backEnd.or.axis[0][1] + local[1] * backEnd.or.axis[1][1] +
+ local[2] * backEnd.or.axis[2][1] + backEnd.or.origin[1];
+ world[2] = local[0] * backEnd.or.axis[0][2] + local[1] * backEnd.or.axis[1][2] +
+ local[2] * backEnd.or.axis[2][2] + backEnd.or.origin[2];
+
+ VectorSubtract( world, backEnd.viewParms.or.origin, world );
+ d = DotProduct( world, backEnd.viewParms.or.axis[0] );
+
+ if ( d < 0 ) {
+ d = -d;
+ }
+ d -= radius;
+ if ( d < 1 ) {
+ d = 1;
+ }
+
+ return r_lodCurveError->value / d;
+}
+
+/*
+=============
+RB_SurfaceGrid
+
+Just copy the grid of points and triangulate
+=============
+*/
+static void RB_SurfaceGrid( srfGridMesh_t *cv ) {
+ int i, j;
+ float *xyz;
+ float *texCoords;
+ float *normal;
+ unsigned char *color;
+ drawVert_t *dv;
+ int rows, irows, vrows;
+ int used;
+ int widthTable[MAX_GRID_SIZE];
+ int heightTable[MAX_GRID_SIZE];
+ float lodError;
+ int lodWidth, lodHeight;
+ int numVertexes;
+ int dlightBits;
+ int *vDlightBits;
+ qboolean needsNormal;
+
+ dlightBits = cv->dlightBits[backEnd.smpFrame];
+ tess.dlightBits |= dlightBits;
+
+ // determine the allowable discrepance
+ lodError = LodErrorForVolume( cv->lodOrigin, cv->lodRadius );
+
+ // determine which rows and columns of the subdivision
+ // we are actually going to use
+ widthTable[0] = 0;
+ lodWidth = 1;
+ for ( i = 1 ; i < cv->width-1 ; i++ ) {
+ if ( cv->widthLodError[i] <= lodError ) {
+ widthTable[lodWidth] = i;
+ lodWidth++;
+ }
+ }
+ widthTable[lodWidth] = cv->width-1;
+ lodWidth++;
+
+ heightTable[0] = 0;
+ lodHeight = 1;
+ for ( i = 1 ; i < cv->height-1 ; i++ ) {
+ if ( cv->heightLodError[i] <= lodError ) {
+ heightTable[lodHeight] = i;
+ lodHeight++;
+ }
+ }
+ heightTable[lodHeight] = cv->height-1;
+ lodHeight++;
+
+
+ // very large grids may have more points or indexes than can be fit
+ // in the tess structure, so we may have to issue it in multiple passes
+
+ used = 0;
+ rows = 0;
+ while ( used < lodHeight - 1 ) {
+ // see how many rows of both verts and indexes we can add without overflowing
+ do {
+ vrows = ( SHADER_MAX_VERTEXES - tess.numVertexes ) / lodWidth;
+ irows = ( SHADER_MAX_INDEXES - tess.numIndexes ) / ( lodWidth * 6 );
+
+ // if we don't have enough space for at least one strip, flush the buffer
+ if ( vrows < 2 || irows < 1 ) {
+ RB_EndSurface();
+ RB_BeginSurface(tess.shader, tess.fogNum );
+ } else {
+ break;
+ }
+ } while ( 1 );
+
+ rows = irows;
+ if ( vrows < irows + 1 ) {
+ rows = vrows - 1;
+ }
+ if ( used + rows > lodHeight ) {
+ rows = lodHeight - used;
+ }
+
+ numVertexes = tess.numVertexes;
+
+ xyz = tess.xyz[numVertexes];
+ normal = tess.normal[numVertexes];
+ texCoords = tess.texCoords[numVertexes][0];
+ color = ( unsigned char * ) &tess.vertexColors[numVertexes];
+ vDlightBits = &tess.vertexDlightBits[numVertexes];
+ needsNormal = tess.shader->needsNormal;
+
+ for ( i = 0 ; i < rows ; i++ ) {
+ for ( j = 0 ; j < lodWidth ; j++ ) {
+ dv = cv->verts + heightTable[ used + i ] * cv->width
+ + widthTable[ j ];
+
+ xyz[0] = dv->xyz[0];
+ xyz[1] = dv->xyz[1];
+ xyz[2] = dv->xyz[2];
+ texCoords[0] = dv->st[0];
+ texCoords[1] = dv->st[1];
+ texCoords[2] = dv->lightmap[0];
+ texCoords[3] = dv->lightmap[1];
+ if ( needsNormal ) {
+ normal[0] = dv->normal[0];
+ normal[1] = dv->normal[1];
+ normal[2] = dv->normal[2];
+ }
+ * ( unsigned int * ) color = * ( unsigned int * ) dv->color;
+ *vDlightBits++ = dlightBits;
+ xyz += 4;
+ normal += 4;
+ texCoords += 4;
+ color += 4;
+ }
+ }
+
+
+ // add the indexes
+ {
+ int numIndexes;
+ int w, h;
+
+ h = rows - 1;
+ w = lodWidth - 1;
+ numIndexes = tess.numIndexes;
+ for (i = 0 ; i < h ; i++) {
+ for (j = 0 ; j < w ; j++) {
+ int v1, v2, v3, v4;
+
+ // vertex order to be reckognized as tristrips
+ v1 = numVertexes + i*lodWidth + j + 1;
+ v2 = v1 - 1;
+ v3 = v2 + lodWidth;
+ v4 = v3 + 1;
+
+ tess.indexes[numIndexes] = v2;
+ tess.indexes[numIndexes+1] = v3;
+ tess.indexes[numIndexes+2] = v1;
+
+ tess.indexes[numIndexes+3] = v1;
+ tess.indexes[numIndexes+4] = v3;
+ tess.indexes[numIndexes+5] = v4;
+ numIndexes += 6;
+ }
+ }
+
+ tess.numIndexes = numIndexes;
+ }
+
+ tess.numVertexes += rows * lodWidth;
+
+ used += rows - 1;
+ }
+}
+
+
+/*
+===========================================================================
+
+NULL MODEL
+
+===========================================================================
+*/
+
+/*
+===================
+RB_SurfaceAxis
+
+Draws x/y/z lines from the origin for orientation debugging
+===================
+*/
+static void RB_SurfaceAxis( void ) {
+#ifdef PANDORA
+ byte colors[3][4] = {
+ {255, 0, 0, 255},
+ {0, 255, 0, 255},
+ {0, 0, 255, 255}
+ };
+ vec3_t verts[6] = {
+ {0.0f, 0.0f, 0.0f},
+ {16.0f, 0.0f, 0.0f},
+ {0.0f, 0.0f, 0.0f},
+ {0.0f, 16.0f, 0.0f},
+ {0.0f, 0.0f, 0.0f},
+ {0.0f, 0.0f, 16.0f}
+ };
+ glIndex_t indicies[6] = { 0, 1, 0, 2, 0, 3 };
+#endif
+ GL_Bind( tr.whiteImage );
+ qglLineWidth( 3 );
+#ifdef PANDORA
+ qglEnableClientState(GL_COLOR_ARRAY);
+ qglColorPointer(4, GL_UNSIGNED_BYTE, 0, colors);
+ qglVertexPointer(3, GL_FLOAT, 0, verts);
+ qglDrawElements(GL_LINES, 6, GL_INDEX_TYPE, indicies);
+#else
+ qglBegin( GL_LINES );
+ qglColor3f( 1,0,0 );
+ qglVertex3f( 0,0,0 );
+ qglVertex3f( 16,0,0 );
+ qglColor3f( 0,1,0 );
+ qglVertex3f( 0,0,0 );
+ qglVertex3f( 0,16,0 );
+ qglColor3f( 0,0,1 );
+ qglVertex3f( 0,0,0 );
+ qglVertex3f( 0,0,16 );
+ qglEnd();
+#endif
+ qglLineWidth( 1 );
+}
+
+//===========================================================================
+
+/*
+====================
+RB_SurfaceEntity
+
+Entities that have a single procedurally generated surface
+====================
+*/
+static void RB_SurfaceEntity( surfaceType_t *surfType ) {
+ switch( backEnd.currentEntity->e.reType ) {
+ case RT_SPRITE:
+ RB_SurfaceSprite();
+ break;
+ case RT_BEAM:
+ RB_SurfaceBeam();
+ break;
+ case RT_RAIL_CORE:
+ RB_SurfaceRailCore();
+ break;
+ case RT_RAIL_RINGS:
+ RB_SurfaceRailRings();
+ break;
+ case RT_LIGHTNING:
+ RB_SurfaceLightningBolt();
+ break;
+ default:
+ RB_SurfaceAxis();
+ break;
+ }
+ return;
+}
+
+static void RB_SurfaceBad( surfaceType_t *surfType ) {
+ ri.Printf( PRINT_ALL, "Bad surface tesselated.\n" );
+}
+
+static void RB_SurfaceFlare(srfFlare_t *surf)
+{
+ if (r_flares->integer)
+ RB_AddFlare(surf, tess.fogNum, surf->origin, surf->color, surf->normal);
+}
+
+static void RB_SurfaceDisplayList( srfDisplayList_t *surf ) {
+ // all apropriate state must be set in RB_BeginSurface
+ // this isn't implemented yet...
+ qglCallList( surf->listNum );
+}
+
+static void RB_SurfaceSkip( void *surf ) {
+}
+
+
+void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])( void *) = {
+ (void(*)(void*))RB_SurfaceBad, // SF_BAD,
+ (void(*)(void*))RB_SurfaceSkip, // SF_SKIP,
+ (void(*)(void*))RB_SurfaceFace, // SF_FACE,
+ (void(*)(void*))RB_SurfaceGrid, // SF_GRID,
+ (void(*)(void*))RB_SurfaceTriangles, // SF_TRIANGLES,
+ (void(*)(void*))RB_SurfacePolychain, // SF_POLY,
+ (void(*)(void*))RB_SurfaceMesh, // SF_MD3,
+ (void(*)(void*))RB_SurfaceAnim, // SF_MD4,
+#ifdef RAVENMD4
+ (void(*)(void*))RB_MDRSurfaceAnim, // SF_MDR,
+#endif
+ (void(*)(void*))RB_SurfaceFlare, // SF_FLARE,
+ (void(*)(void*))RB_SurfaceEntity, // SF_ENTITY
+ (void(*)(void*))RB_SurfaceDisplayList // SF_DISPLAY_LIST
+};
diff --git a/code/renderer/tr_types.h b/code/renderer/tr_types.h
new file mode 100644
index 0000000..50f9759
--- /dev/null
+++ b/code/renderer/tr_types.h
@@ -0,0 +1,212 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+#ifndef __TR_TYPES_H
+#define __TR_TYPES_H
+
+
+#define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces
+#define MAX_ENTITIES 1023 // can't be increased without changing drawsurf bit packing
+
+// renderfx flags
+#define RF_MINLIGHT 0x0001 // allways have some light (viewmodel, some items)
+#define RF_THIRD_PERSON 0x0002 // don't draw through eyes, only mirrors (player bodies, chat sprites)
+#define RF_FIRST_PERSON 0x0004 // only draw through eyes (view weapon, damage blood blob)
+#define RF_DEPTHHACK 0x0008 // for view weapon Z crunching
+
+#define RF_CROSSHAIR 0x0010 // This item is a cross hair and will draw over everything similar to
+ // DEPTHHACK in stereo rendering mode, with the difference that the
+ // projection matrix won't be hacked to reduce the stereo separation as
+ // is done for the gun.
+
+#define RF_NOSHADOW 0x0040 // don't add stencil shadows
+
+#define RF_LIGHTING_ORIGIN 0x0080 // use refEntity->lightingOrigin instead of refEntity->origin
+ // for lighting. This allows entities to sink into the floor
+ // with their origin going solid, and allows all parts of a
+ // player to get the same lighting
+
+#define RF_SHADOW_PLANE 0x0100 // use refEntity->shadowPlane
+#define RF_WRAP_FRAMES 0x0200 // mod the model frames by the maxframes to allow continuous
+
+// refdef flags
+#define RDF_NOWORLDMODEL 0x0001 // used for player configuration screen
+#define RDF_HYPERSPACE 0x0004 // teleportation effect
+
+typedef struct {
+ vec3_t xyz;
+ float st[2];
+ byte modulate[4];
+} polyVert_t;
+
+typedef struct poly_s {
+ qhandle_t hShader;
+ int numVerts;
+ polyVert_t *verts;
+} poly_t;
+
+typedef enum {
+ RT_MODEL,
+ RT_POLY,
+ RT_SPRITE,
+ RT_BEAM,
+ RT_RAIL_CORE,
+ RT_RAIL_RINGS,
+ RT_LIGHTNING,
+ RT_PORTALSURFACE, // doesn't draw anything, just info for portals
+
+ RT_MAX_REF_ENTITY_TYPE
+} refEntityType_t;
+
+typedef struct {
+ refEntityType_t reType;
+ int renderfx;
+
+ qhandle_t hModel; // opaque type outside refresh
+
+ // most recent data
+ vec3_t lightingOrigin; // so multi-part models can be lit identically (RF_LIGHTING_ORIGIN)
+ float shadowPlane; // projection shadows go here, stencils go slightly lower
+
+ vec3_t axis[3]; // rotation vectors
+ qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale
+ float origin[3]; // also used as MODEL_BEAM's "from"
+ int frame; // also used as MODEL_BEAM's diameter
+
+ // previous data for frame interpolation
+ float oldorigin[3]; // also used as MODEL_BEAM's "to"
+ int oldframe;
+ float backlerp; // 0.0 = current, 1.0 = old
+
+ // texturing
+ int skinNum; // inline skin index
+ qhandle_t customSkin; // NULL for default skin
+ qhandle_t customShader; // use one image for the entire thing
+
+ // misc
+ byte shaderRGBA[4]; // colors used by rgbgen entity shaders
+ float shaderTexCoord[2]; // texture coordinates used by tcMod entity modifiers
+ float shaderTime; // subtracted from refdef time to control effect start times
+
+ // extra sprite information
+ float radius;
+ float rotation;
+} refEntity_t;
+
+
+#define MAX_RENDER_STRINGS 8
+#define MAX_RENDER_STRING_LENGTH 32
+
+typedef struct {
+ int x, y, width, height;
+ float fov_x, fov_y;
+ vec3_t vieworg;
+ vec3_t viewaxis[3]; // transformation matrix
+
+ // time in milliseconds for shader effects and other time dependent rendering issues
+ int time;
+
+ int rdflags; // RDF_NOWORLDMODEL, etc
+
+ // 1 bits will prevent the associated area from rendering at all
+ byte areamask[MAX_MAP_AREA_BYTES];
+
+ // text messages for deform text shaders
+ char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH];
+} refdef_t;
+
+
+typedef enum {
+ STEREO_CENTER,
+ STEREO_LEFT,
+ STEREO_RIGHT
+} stereoFrame_t;
+
+
+/*
+** glconfig_t
+**
+** Contains variables specific to the OpenGL configuration
+** being run right now. These are constant once the OpenGL
+** subsystem is initialized.
+*/
+typedef enum {
+ TC_NONE,
+ TC_S3TC, // this is for the GL_S3_s3tc extension.
+ TC_S3TC_ARB // this is for the GL_EXT_texture_compression_s3tc extension.
+} textureCompression_t;
+
+typedef enum {
+ GLDRV_ICD, // driver is integrated with window system
+ // WARNING: there are tests that check for
+ // > GLDRV_ICD for minidriverness, so this
+ // should always be the lowest value in this
+ // enum set
+ GLDRV_STANDALONE, // driver is a non-3Dfx standalone driver
+ GLDRV_VOODOO // driver is a 3Dfx standalone driver
+} glDriverType_t;
+
+typedef enum {
+ GLHW_GENERIC, // where everthing works the way it should
+ GLHW_3DFX_2D3D, // Voodoo Banshee or Voodoo3, relevant since if this is
+ // the hardware type then there can NOT exist a secondary
+ // display adapter
+ GLHW_RIVA128, // where you can't interpolate alpha
+ GLHW_RAGEPRO, // where you can't modulate alpha on alpha textures
+ GLHW_PERMEDIA2 // where you don't have src*dst
+} glHardwareType_t;
+
+typedef struct {
+ char renderer_string[MAX_STRING_CHARS];
+ char vendor_string[MAX_STRING_CHARS];
+ char version_string[MAX_STRING_CHARS];
+ char extensions_string[BIG_INFO_STRING];
+
+ int maxTextureSize; // queried from GL
+ int numTextureUnits; // multitexture ability
+
+ int colorBits, depthBits, stencilBits;
+
+ glDriverType_t driverType;
+ glHardwareType_t hardwareType;
+
+ qboolean deviceSupportsGamma;
+ textureCompression_t textureCompression;
+ qboolean textureEnvAddAvailable;
+
+ int vidWidth, vidHeight;
+ // aspect is the screen's physical width / height, which may be different
+ // than scrWidth / scrHeight if the pixels are non-square
+ // normal screens should be 4/3, but wide aspect monitors may be 16/9
+ float windowAspect;
+
+ int displayFrequency;
+
+ // synonymous with "does rendering consume the entire screen?", therefore
+ // a Voodoo or Voodoo2 will have this set to TRUE, as will a Win32 ICD that
+ // used CDS.
+ qboolean isFullscreen;
+ qboolean stereoEnabled;
+ qboolean smpActive; // dual processor
+} glconfig_t;
+
+#endif // __TR_TYPES_H
diff --git a/code/renderer/tr_world.c b/code/renderer/tr_world.c
new file mode 100644
index 0000000..d05373a
--- /dev/null
+++ b/code/renderer/tr_world.c
@@ -0,0 +1,668 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#include "tr_local.h"
+
+
+
+/*
+=================
+R_CullTriSurf
+
+Returns true if the grid is completely culled away.
+Also sets the clipped hint bit in tess
+=================
+*/
+static qboolean R_CullTriSurf( srfTriangles_t *cv ) {
+ int boxCull;
+
+ boxCull = R_CullLocalBox( cv->bounds );
+
+ if ( boxCull == CULL_OUT ) {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+=================
+R_CullGrid
+
+Returns true if the grid is completely culled away.
+Also sets the clipped hint bit in tess
+=================
+*/
+static qboolean R_CullGrid( srfGridMesh_t *cv ) {
+ int boxCull;
+ int sphereCull;
+
+ if ( r_nocurves->integer ) {
+ return qtrue;
+ }
+
+ if ( tr.currentEntityNum != ENTITYNUM_WORLD ) {
+ sphereCull = R_CullLocalPointAndRadius( cv->localOrigin, cv->meshRadius );
+ } else {
+ sphereCull = R_CullPointAndRadius( cv->localOrigin, cv->meshRadius );
+ }
+ boxCull = CULL_OUT;
+
+ // check for trivial reject
+ if ( sphereCull == CULL_OUT )
+ {
+ tr.pc.c_sphere_cull_patch_out++;
+ return qtrue;
+ }
+ // check bounding box if necessary
+ else if ( sphereCull == CULL_CLIP )
+ {
+ tr.pc.c_sphere_cull_patch_clip++;
+
+ boxCull = R_CullLocalBox( cv->meshBounds );
+
+ if ( boxCull == CULL_OUT )
+ {
+ tr.pc.c_box_cull_patch_out++;
+ return qtrue;
+ }
+ else if ( boxCull == CULL_IN )
+ {
+ tr.pc.c_box_cull_patch_in++;
+ }
+ else
+ {
+ tr.pc.c_box_cull_patch_clip++;
+ }
+ }
+ else
+ {
+ tr.pc.c_sphere_cull_patch_in++;
+ }
+
+ return qfalse;
+}
+
+
+/*
+================
+R_CullSurface
+
+Tries to back face cull surfaces before they are lighted or
+added to the sorting list.
+
+This will also allow mirrors on both sides of a model without recursion.
+================
+*/
+static qboolean R_CullSurface( surfaceType_t *surface, shader_t *shader ) {
+ srfSurfaceFace_t *sface;
+ float d;
+
+ if ( r_nocull->integer ) {
+ return qfalse;
+ }
+
+ if ( *surface == SF_GRID ) {
+ return R_CullGrid( (srfGridMesh_t *)surface );
+ }
+
+ if ( *surface == SF_TRIANGLES ) {
+ return R_CullTriSurf( (srfTriangles_t *)surface );
+ }
+
+ if ( *surface != SF_FACE ) {
+ return qfalse;
+ }
+
+ if ( shader->cullType == CT_TWO_SIDED ) {
+ return qfalse;
+ }
+
+ // face culling
+ if ( !r_facePlaneCull->integer ) {
+ return qfalse;
+ }
+
+ sface = ( srfSurfaceFace_t * ) surface;
+ d = DotProduct (tr.or.viewOrigin, sface->plane.normal);
+
+ // don't cull exactly on the plane, because there are levels of rounding
+ // through the BSP, ICD, and hardware that may cause pixel gaps if an
+ // epsilon isn't allowed here
+ if ( shader->cullType == CT_FRONT_SIDED ) {
+ if ( d < sface->plane.dist - 8 ) {
+ return qtrue;
+ }
+ } else {
+ if ( d > sface->plane.dist + 8 ) {
+ return qtrue;
+ }
+ }
+
+ return qfalse;
+}
+
+
+static int R_DlightFace( srfSurfaceFace_t *face, int dlightBits ) {
+ float d;
+ int i;
+ dlight_t *dl;
+
+ for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) {
+ if ( ! ( dlightBits & ( 1 << i ) ) ) {
+ continue;
+ }
+ dl = &tr.refdef.dlights[i];
+ d = DotProduct( dl->origin, face->plane.normal ) - face->plane.dist;
+ if ( d < -dl->radius || d > dl->radius ) {
+ // dlight doesn't reach the plane
+ dlightBits &= ~( 1 << i );
+ }
+ }
+
+ if ( !dlightBits ) {
+ tr.pc.c_dlightSurfacesCulled++;
+ }
+
+ face->dlightBits[ tr.smpFrame ] = dlightBits;
+ return dlightBits;
+}
+
+static int R_DlightGrid( srfGridMesh_t *grid, int dlightBits ) {
+ int i;
+ dlight_t *dl;
+
+ for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) {
+ if ( ! ( dlightBits & ( 1 << i ) ) ) {
+ continue;
+ }
+ dl = &tr.refdef.dlights[i];
+ if ( dl->origin[0] - dl->radius > grid->meshBounds[1][0]
+ || dl->origin[0] + dl->radius < grid->meshBounds[0][0]
+ || dl->origin[1] - dl->radius > grid->meshBounds[1][1]
+ || dl->origin[1] + dl->radius < grid->meshBounds[0][1]
+ || dl->origin[2] - dl->radius > grid->meshBounds[1][2]
+ || dl->origin[2] + dl->radius < grid->meshBounds[0][2] ) {
+ // dlight doesn't reach the bounds
+ dlightBits &= ~( 1 << i );
+ }
+ }
+
+ if ( !dlightBits ) {
+ tr.pc.c_dlightSurfacesCulled++;
+ }
+
+ grid->dlightBits[ tr.smpFrame ] = dlightBits;
+ return dlightBits;
+}
+
+
+static int R_DlightTrisurf( srfTriangles_t *surf, int dlightBits ) {
+ // FIXME: more dlight culling to trisurfs...
+ surf->dlightBits[ tr.smpFrame ] = dlightBits;
+ return dlightBits;
+#if 0
+ int i;
+ dlight_t *dl;
+
+ for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) {
+ if ( ! ( dlightBits & ( 1 << i ) ) ) {
+ continue;
+ }
+ dl = &tr.refdef.dlights[i];
+ if ( dl->origin[0] - dl->radius > grid->meshBounds[1][0]
+ || dl->origin[0] + dl->radius < grid->meshBounds[0][0]
+ || dl->origin[1] - dl->radius > grid->meshBounds[1][1]
+ || dl->origin[1] + dl->radius < grid->meshBounds[0][1]
+ || dl->origin[2] - dl->radius > grid->meshBounds[1][2]
+ || dl->origin[2] + dl->radius < grid->meshBounds[0][2] ) {
+ // dlight doesn't reach the bounds
+ dlightBits &= ~( 1 << i );
+ }
+ }
+
+ if ( !dlightBits ) {
+ tr.pc.c_dlightSurfacesCulled++;
+ }
+
+ grid->dlightBits[ tr.smpFrame ] = dlightBits;
+ return dlightBits;
+#endif
+}
+
+/*
+====================
+R_DlightSurface
+
+The given surface is going to be drawn, and it touches a leaf
+that is touched by one or more dlights, so try to throw out
+more dlights if possible.
+====================
+*/
+static int R_DlightSurface( msurface_t *surf, int dlightBits ) {
+ if ( *surf->data == SF_FACE ) {
+ dlightBits = R_DlightFace( (srfSurfaceFace_t *)surf->data, dlightBits );
+ } else if ( *surf->data == SF_GRID ) {
+ dlightBits = R_DlightGrid( (srfGridMesh_t *)surf->data, dlightBits );
+ } else if ( *surf->data == SF_TRIANGLES ) {
+ dlightBits = R_DlightTrisurf( (srfTriangles_t *)surf->data, dlightBits );
+ } else {
+ dlightBits = 0;
+ }
+
+ if ( dlightBits ) {
+ tr.pc.c_dlightSurfaces++;
+ }
+
+ return dlightBits;
+}
+
+
+
+/*
+======================
+R_AddWorldSurface
+======================
+*/
+static void R_AddWorldSurface( msurface_t *surf, int dlightBits ) {
+ if ( surf->viewCount == tr.viewCount ) {
+ return; // already in this view
+ }
+
+ surf->viewCount = tr.viewCount;
+ // FIXME: bmodel fog?
+
+ // try to cull before dlighting or adding
+ if ( R_CullSurface( surf->data, surf->shader ) ) {
+ return;
+ }
+
+ // check for dlighting
+ if ( dlightBits ) {
+ dlightBits = R_DlightSurface( surf, dlightBits );
+ dlightBits = ( dlightBits != 0 );
+ }
+
+ R_AddDrawSurf( surf->data, surf->shader, surf->fogIndex, dlightBits );
+}
+
+/*
+=============================================================
+
+ BRUSH MODELS
+
+=============================================================
+*/
+
+/*
+=================
+R_AddBrushModelSurfaces
+=================
+*/
+void R_AddBrushModelSurfaces ( trRefEntity_t *ent ) {
+ bmodel_t *bmodel;
+ int clip;
+ model_t *pModel;
+ int i;
+
+ pModel = R_GetModelByHandle( ent->e.hModel );
+
+ bmodel = pModel->bmodel;
+
+ clip = R_CullLocalBox( bmodel->bounds );
+ if ( clip == CULL_OUT ) {
+ return;
+ }
+
+ R_DlightBmodel( bmodel );
+
+ for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) {
+ R_AddWorldSurface( bmodel->firstSurface + i, tr.currentEntity->needDlights );
+ }
+}
+
+
+/*
+=============================================================
+
+ WORLD MODEL
+
+=============================================================
+*/
+
+
+/*
+================
+R_RecursiveWorldNode
+================
+*/
+static void R_RecursiveWorldNode( mnode_t *node, int planeBits, int dlightBits ) {
+
+ do {
+ int newDlights[2];
+
+ // if the node wasn't marked as potentially visible, exit
+ if (node->visframe != tr.visCount) {
+ return;
+ }
+
+ // if the bounding volume is outside the frustum, nothing
+ // inside can be visible OPTIMIZE: don't do this all the way to leafs?
+
+ if ( !r_nocull->integer ) {
+ int r;
+
+ if ( planeBits & 1 ) {
+ r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[0]);
+ if (r == 2) {
+ return; // culled
+ }
+ if ( r == 1 ) {
+ planeBits &= ~1; // all descendants will also be in front
+ }
+ }
+
+ if ( planeBits & 2 ) {
+ r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[1]);
+ if (r == 2) {
+ return; // culled
+ }
+ if ( r == 1 ) {
+ planeBits &= ~2; // all descendants will also be in front
+ }
+ }
+
+ if ( planeBits & 4 ) {
+ r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[2]);
+ if (r == 2) {
+ return; // culled
+ }
+ if ( r == 1 ) {
+ planeBits &= ~4; // all descendants will also be in front
+ }
+ }
+
+ if ( planeBits & 8 ) {
+ r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[3]);
+ if (r == 2) {
+ return; // culled
+ }
+ if ( r == 1 ) {
+ planeBits &= ~8; // all descendants will also be in front
+ }
+ }
+
+ }
+
+ if ( node->contents != -1 ) {
+ break;
+ }
+
+ // node is just a decision point, so go down both sides
+ // since we don't care about sort orders, just go positive to negative
+
+ // determine which dlights are needed
+ newDlights[0] = 0;
+ newDlights[1] = 0;
+ if ( dlightBits ) {
+ int i;
+
+ for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) {
+ dlight_t *dl;
+ float dist;
+
+ if ( dlightBits & ( 1 << i ) ) {
+ dl = &tr.refdef.dlights[i];
+ dist = DotProduct( dl->origin, node->plane->normal ) - node->plane->dist;
+
+ if ( dist > -dl->radius ) {
+ newDlights[0] |= ( 1 << i );
+ }
+ if ( dist < dl->radius ) {
+ newDlights[1] |= ( 1 << i );
+ }
+ }
+ }
+ }
+
+ // recurse down the children, front side first
+ R_RecursiveWorldNode (node->children[0], planeBits, newDlights[0] );
+
+ // tail recurse
+ node = node->children[1];
+ dlightBits = newDlights[1];
+ } while ( 1 );
+
+ {
+ // leaf node, so add mark surfaces
+ int c;
+ msurface_t *surf, **mark;
+
+ tr.pc.c_leafs++;
+
+ // add to z buffer bounds
+ if ( node->mins[0] < tr.viewParms.visBounds[0][0] ) {
+ tr.viewParms.visBounds[0][0] = node->mins[0];
+ }
+ if ( node->mins[1] < tr.viewParms.visBounds[0][1] ) {
+ tr.viewParms.visBounds[0][1] = node->mins[1];
+ }
+ if ( node->mins[2] < tr.viewParms.visBounds[0][2] ) {
+ tr.viewParms.visBounds[0][2] = node->mins[2];
+ }
+
+ if ( node->maxs[0] > tr.viewParms.visBounds[1][0] ) {
+ tr.viewParms.visBounds[1][0] = node->maxs[0];
+ }
+ if ( node->maxs[1] > tr.viewParms.visBounds[1][1] ) {
+ tr.viewParms.visBounds[1][1] = node->maxs[1];
+ }
+ if ( node->maxs[2] > tr.viewParms.visBounds[1][2] ) {
+ tr.viewParms.visBounds[1][2] = node->maxs[2];
+ }
+
+ // add the individual surfaces
+ mark = node->firstmarksurface;
+ c = node->nummarksurfaces;
+ while (c--) {
+ // the surface may have already been added if it
+ // spans multiple leafs
+ surf = *mark;
+ R_AddWorldSurface( surf, dlightBits );
+ mark++;
+ }
+ }
+
+}
+
+
+/*
+===============
+R_PointInLeaf
+===============
+*/
+static mnode_t *R_PointInLeaf( const vec3_t p ) {
+ mnode_t *node;
+ float d;
+ cplane_t *plane;
+
+ if ( !tr.world ) {
+ ri.Error (ERR_DROP, "R_PointInLeaf: bad model");
+ }
+
+ node = tr.world->nodes;
+ while( 1 ) {
+ if (node->contents != -1) {
+ break;
+ }
+ plane = node->plane;
+ d = DotProduct (p,plane->normal) - plane->dist;
+ if (d > 0) {
+ node = node->children[0];
+ } else {
+ node = node->children[1];
+ }
+ }
+
+ return node;
+}
+
+/*
+==============
+R_ClusterPVS
+==============
+*/
+static const byte *R_ClusterPVS (int cluster) {
+ if (!tr.world || !tr.world->vis || cluster < 0 || cluster >= tr.world->numClusters ) {
+ return tr.world->novis;
+ }
+
+ return tr.world->vis + cluster * tr.world->clusterBytes;
+}
+
+/*
+=================
+R_inPVS
+=================
+*/
+qboolean R_inPVS( const vec3_t p1, const vec3_t p2 ) {
+ mnode_t *leaf;
+ byte *vis;
+
+ leaf = R_PointInLeaf( p1 );
+ vis = CM_ClusterPVS( leaf->cluster );
+ leaf = R_PointInLeaf( p2 );
+
+ if ( !(vis[leaf->cluster>>3] & (1<<(leaf->cluster&7))) ) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+/*
+===============
+R_MarkLeaves
+
+Mark the leaves and nodes that are in the PVS for the current
+cluster
+===============
+*/
+static void R_MarkLeaves (void) {
+ const byte *vis;
+ mnode_t *leaf, *parent;
+ int i;
+ int cluster;
+
+ // lockpvs lets designers walk around to determine the
+ // extent of the current pvs
+ if ( r_lockpvs->integer ) {
+ return;
+ }
+
+ // current viewcluster
+ leaf = R_PointInLeaf( tr.viewParms.pvsOrigin );
+ cluster = leaf->cluster;
+
+ // if the cluster is the same and the area visibility matrix
+ // hasn't changed, we don't need to mark everything again
+
+ // if r_showcluster was just turned on, remark everything
+ if ( tr.viewCluster == cluster && !tr.refdef.areamaskModified
+ && !r_showcluster->modified ) {
+ return;
+ }
+
+ if ( r_showcluster->modified || r_showcluster->integer ) {
+ r_showcluster->modified = qfalse;
+ if ( r_showcluster->integer ) {
+ ri.Printf( PRINT_ALL, "cluster:%i area:%i\n", cluster, leaf->area );
+ }
+ }
+
+ tr.visCount++;
+ tr.viewCluster = cluster;
+
+ if ( r_novis->integer || tr.viewCluster == -1 ) {
+ for (i=0 ; i<tr.world->numnodes ; i++) {
+ if (tr.world->nodes[i].contents != CONTENTS_SOLID) {
+ tr.world->nodes[i].visframe = tr.visCount;
+ }
+ }
+ return;
+ }
+
+ vis = R_ClusterPVS (tr.viewCluster);
+
+ for (i=0,leaf=tr.world->nodes ; i<tr.world->numnodes ; i++, leaf++) {
+ cluster = leaf->cluster;
+ if ( cluster < 0 || cluster >= tr.world->numClusters ) {
+ continue;
+ }
+
+ // check general pvs
+ if ( !(vis[cluster>>3] & (1<<(cluster&7))) ) {
+ continue;
+ }
+
+ // check for door connection
+ if ( (tr.refdef.areamask[leaf->area>>3] & (1<<(leaf->area&7)) ) ) {
+ continue; // not visible
+ }
+
+ parent = leaf;
+ do {
+ if (parent->visframe == tr.visCount)
+ break;
+ parent->visframe = tr.visCount;
+ parent = parent->parent;
+ } while (parent);
+ }
+}
+
+
+/*
+=============
+R_AddWorldSurfaces
+=============
+*/
+void R_AddWorldSurfaces (void) {
+ if ( !r_drawworld->integer ) {
+ return;
+ }
+
+ if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) {
+ return;
+ }
+
+ tr.currentEntityNum = ENTITYNUM_WORLD;
+ tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT;
+
+ // determine which leaves are in the PVS / areamask
+ R_MarkLeaves ();
+
+ // clear out the visible min/max
+ ClearBounds( tr.viewParms.visBounds[0], tr.viewParms.visBounds[1] );
+
+ // perform frustum culling and add all the potentially visible surfaces
+ if ( tr.refdef.num_dlights > 32 ) {
+ tr.refdef.num_dlights = 32 ;
+ }
+ R_RecursiveWorldNode( tr.world->nodes, 15, ( 1 << tr.refdef.num_dlights ) - 1 );
+}