diff options
Diffstat (limited to 'src/renderer')
28 files changed, 25941 insertions, 0 deletions
diff --git a/src/renderer/qgl.h b/src/renderer/qgl.h new file mode 100644 index 0000000..5bf52e1 --- /dev/null +++ b/src/renderer/qgl.h @@ -0,0 +1,607 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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__ + +#if defined( __LINT__ ) + +#include <GL/gl.h> + +#elif defined( _WIN32 ) + +#if _MSC_VER +#pragma warning (disable: 4201) +#pragma warning (disable: 4214) +#pragma warning (disable: 4514) +#pragma warning (disable: 4032) +#pragma warning (disable: 4201) +#pragma warning (disable: 4214) +#endif + +#include <windows.h> +#include <GL/gl.h> + +#elif defined(MACOS_X) + +#include <OpenGL/OpenGL.h> +#include <OpenGL/gl.h> +#include <OpenGL/glu.h> +#ifndef GL_EXT_abgr +#include <OpenGL/glext.h> +#endif + +// This can be defined to use the CGLMacro.h support which avoids looking up +// the current context. +//#define USE_CGLMACROS + +#ifdef USE_CGLMACROS +#include "macosx_local.h" +#define cgl_ctx glw_state._cgl_ctx +#include <OpenGL/CGLMacro.h> +#endif + +#elif defined( __linux__ ) || defined(__FreeBSD__) + +#include <GL/gl.h> +#include <GL/glx.h> +// bk001129 - from cvs1.17 (mkv) +#if defined(__FX__) +#include <GL/fxmesa.h> +#endif + +#elif defined( __sun ) +#include <GL/gl.h> +#include <GL/glx.h> + +#else + +#include <gl.h> + +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef WINAPI +#define WINAPI +#endif + + +//=========================================================================== +// <Timbo> I hate this section so much + +/* +** multitexture extension definitions +*/ +#if !defined(__sun) + +#define GL_ACTIVE_TEXTURE_ARB               0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB        0x84E1 +#define GL_MAX_ACTIVE_TEXTURES_ARB          0x84E2 + +#define GL_TEXTURE0_ARB                     0x84C0 +#define GL_TEXTURE1_ARB                     0x84C1 +#define GL_TEXTURE2_ARB                     0x84C2 +#define GL_TEXTURE3_ARB                     0x84C3 + +#else + +#define GL_MAX_ACTIVE_TEXTURES_ARB          0x84E2 + +#endif /* defined(__sun) */ + +// anisotropic filtering constants +#define GL_TEXTURE_MAX_ANISOTROPY_EXT       0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT   0x84FF + +// define for skyboxes without black seams on non SDL-versions. +#if !defined(GL_VERSION_1_2) && !defined(GL_CLAMP_TO_EDGE) +   #define GL_CLAMP_TO_EDGE                  0x812F +#endif + +//=========================================================================== + +// NOTE: some Linux platforms would need those prototypes +#if defined(MACOS_X) || ( defined(__sun) && defined(__sparc) ) +typedef void (APIENTRY * PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLACTIVETEXTUREARBPROC) (GLenum target); +typedef void (APIENTRY * PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum target); +#endif + +// TTimo - VC7 / XP ? +#ifdef WIN32 +typedef void (APIENTRY * PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRY * PFNGLACTIVETEXTUREARBPROC) (GLenum target); +typedef void (APIENTRY * PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum target); +#endif + +/* +** extension constants +*/ + + +// S3TC compression constants +#define GL_RGB_S3TC							0x83A0 +#define GL_RGB4_S3TC						0x83A1 + + +// extensions will be function pointers on all platforms + +extern	void ( APIENTRY * qglMultiTexCoord2fARB )( GLenum texture, GLfloat s, GLfloat t ); +extern	void ( APIENTRY * qglActiveTextureARB )( GLenum texture ); +extern	void ( APIENTRY * qglClientActiveTextureARB )( GLenum texture ); + +extern	void ( APIENTRY * qglLockArraysEXT) (GLint, GLint); +extern	void ( APIENTRY * qglUnlockArraysEXT) (void); + +//=========================================================================== + +// non-dlopening systems will just redefine qgl* to gl* +#if !defined( _WIN32 ) && !defined(MACOS_X) && !defined( __linux__ ) && !defined( __FreeBSD__ ) && !defined(__sun) // rb010123 + +#include "qgl_linked.h" + +#elif (defined(MACOS_X) && !defined(USE_SDL_VIDEO)) +// This includes #ifdefs for optional logging and GL error checking after every GL call as well as #defines to prevent incorrect usage of the non-'qgl' versions of the GL API. +#include "macosx_qgl.h" + +#else + +// windows systems use a function pointer for each call so we can load minidrivers + +extern  void ( APIENTRY * qglAccum )(GLenum op, GLfloat value); +extern  void ( APIENTRY * qglAlphaFunc )(GLenum func, GLclampf ref); +extern  GLboolean ( APIENTRY * qglAreTexturesResident )(GLsizei n, const GLuint *textures, GLboolean *residences); +extern  void ( APIENTRY * qglArrayElement )(GLint i); +extern  void ( APIENTRY * qglBegin )(GLenum mode); +extern  void ( APIENTRY * qglBindTexture )(GLenum target, GLuint texture); +extern  void ( APIENTRY * qglBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +extern  void ( APIENTRY * qglBlendFunc )(GLenum sfactor, GLenum dfactor); +extern  void ( APIENTRY * qglCallList )(GLuint list); +extern  void ( APIENTRY * qglCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +extern  void ( APIENTRY * qglClear )(GLbitfield mask); +extern  void ( APIENTRY * qglClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern  void ( APIENTRY * qglClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +extern  void ( APIENTRY * qglClearDepth )(GLclampd depth); +extern  void ( APIENTRY * qglClearIndex )(GLfloat c); +extern  void ( APIENTRY * qglClearStencil )(GLint s); +extern  void ( APIENTRY * qglClipPlane )(GLenum plane, const GLdouble *equation); +extern  void ( APIENTRY * qglColor3b )(GLbyte red, GLbyte green, GLbyte blue); +extern  void ( APIENTRY * qglColor3bv )(const GLbyte *v); +extern  void ( APIENTRY * qglColor3d )(GLdouble red, GLdouble green, GLdouble blue); +extern  void ( APIENTRY * qglColor3dv )(const GLdouble *v); +extern  void ( APIENTRY * qglColor3f )(GLfloat red, GLfloat green, GLfloat blue); +extern  void ( APIENTRY * qglColor3fv )(const GLfloat *v); +extern  void ( APIENTRY * qglColor3i )(GLint red, GLint green, GLint blue); +extern  void ( APIENTRY * qglColor3iv )(const GLint *v); +extern  void ( APIENTRY * qglColor3s )(GLshort red, GLshort green, GLshort blue); +extern  void ( APIENTRY * qglColor3sv )(const GLshort *v); +extern  void ( APIENTRY * qglColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +extern  void ( APIENTRY * qglColor3ubv )(const GLubyte *v); +extern  void ( APIENTRY * qglColor3ui )(GLuint red, GLuint green, GLuint blue); +extern  void ( APIENTRY * qglColor3uiv )(const GLuint *v); +extern  void ( APIENTRY * qglColor3us )(GLushort red, GLushort green, GLushort blue); +extern  void ( APIENTRY * qglColor3usv )(const GLushort *v); +extern  void ( APIENTRY * qglColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +extern  void ( APIENTRY * qglColor4bv )(const GLbyte *v); +extern  void ( APIENTRY * qglColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +extern  void ( APIENTRY * qglColor4dv )(const GLdouble *v); +extern  void ( APIENTRY * qglColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern  void ( APIENTRY * qglColor4fv )(const GLfloat *v); +extern  void ( APIENTRY * qglColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +extern  void ( APIENTRY * qglColor4iv )(const GLint *v); +extern  void ( APIENTRY * qglColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +extern  void ( APIENTRY * qglColor4sv )(const GLshort *v); +extern  void ( APIENTRY * qglColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +extern  void ( APIENTRY * qglColor4ubv )(const GLubyte *v); +extern  void ( APIENTRY * qglColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +extern  void ( APIENTRY * qglColor4uiv )(const GLuint *v); +extern  void ( APIENTRY * qglColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +extern  void ( APIENTRY * qglColor4usv )(const GLushort *v); +extern  void ( APIENTRY * qglColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +extern  void ( APIENTRY * qglColorMaterial )(GLenum face, GLenum mode); +extern  void ( APIENTRY * qglColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern  void ( APIENTRY * qglCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +extern  void ( APIENTRY * qglCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +extern  void ( APIENTRY * qglCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +extern  void ( APIENTRY * qglCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +extern  void ( APIENTRY * qglCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +extern  void ( APIENTRY * qglCullFace )(GLenum mode); +extern  void ( APIENTRY * qglDeleteLists )(GLuint list, GLsizei range); +extern  void ( APIENTRY * qglDeleteTextures )(GLsizei n, const GLuint *textures); +extern  void ( APIENTRY * qglDepthFunc )(GLenum func); +extern  void ( APIENTRY * qglDepthMask )(GLboolean flag); +extern  void ( APIENTRY * qglDepthRange )(GLclampd zNear, GLclampd zFar); +extern  void ( APIENTRY * qglDisable )(GLenum cap); +extern  void ( APIENTRY * qglDisableClientState )(GLenum array); +extern  void ( APIENTRY * qglDrawArrays )(GLenum mode, GLint first, GLsizei count); +extern  void ( APIENTRY * qglDrawBuffer )(GLenum mode); +extern  void ( APIENTRY * qglDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +extern  void ( APIENTRY * qglDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +extern  void ( APIENTRY * qglEdgeFlag )(GLboolean flag); +extern  void ( APIENTRY * qglEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +extern  void ( APIENTRY * qglEdgeFlagv )(const GLboolean *flag); +extern  void ( APIENTRY * qglEnable )(GLenum cap); +extern  void ( APIENTRY * qglEnableClientState )(GLenum array); +extern  void ( APIENTRY * qglEnd )(void); +extern  void ( APIENTRY * qglEndList )(void); +extern  void ( APIENTRY * qglEvalCoord1d )(GLdouble u); +extern  void ( APIENTRY * qglEvalCoord1dv )(const GLdouble *u); +extern  void ( APIENTRY * qglEvalCoord1f )(GLfloat u); +extern  void ( APIENTRY * qglEvalCoord1fv )(const GLfloat *u); +extern  void ( APIENTRY * qglEvalCoord2d )(GLdouble u, GLdouble v); +extern  void ( APIENTRY * qglEvalCoord2dv )(const GLdouble *u); +extern  void ( APIENTRY * qglEvalCoord2f )(GLfloat u, GLfloat v); +extern  void ( APIENTRY * qglEvalCoord2fv )(const GLfloat *u); +extern  void ( APIENTRY * qglEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +extern  void ( APIENTRY * qglEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +extern  void ( APIENTRY * qglEvalPoint1 )(GLint i); +extern  void ( APIENTRY * qglEvalPoint2 )(GLint i, GLint j); +extern  void ( APIENTRY * qglFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +extern  void ( APIENTRY * qglFinish )(void); +extern  void ( APIENTRY * qglFlush )(void); +extern  void ( APIENTRY * qglFogf )(GLenum pname, GLfloat param); +extern  void ( APIENTRY * qglFogfv )(GLenum pname, const GLfloat *params); +extern  void ( APIENTRY * qglFogi )(GLenum pname, GLint param); +extern  void ( APIENTRY * qglFogiv )(GLenum pname, const GLint *params); +extern  void ( APIENTRY * qglFrontFace )(GLenum mode); +extern  void ( APIENTRY * qglFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern  GLuint ( APIENTRY * qglGenLists )(GLsizei range); +extern  void ( APIENTRY * qglGenTextures )(GLsizei n, GLuint *textures); +extern  void ( APIENTRY * qglGetBooleanv )(GLenum pname, GLboolean *params); +extern  void ( APIENTRY * qglGetClipPlane )(GLenum plane, GLdouble *equation); +extern  void ( APIENTRY * qglGetDoublev )(GLenum pname, GLdouble *params); +extern  GLenum ( APIENTRY * qglGetError )(void); +extern  void ( APIENTRY * qglGetFloatv )(GLenum pname, GLfloat *params); +extern  void ( APIENTRY * qglGetIntegerv )(GLenum pname, GLint *params); +extern  void ( APIENTRY * qglGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +extern  void ( APIENTRY * qglGetLightiv )(GLenum light, GLenum pname, GLint *params); +extern  void ( APIENTRY * qglGetMapdv )(GLenum target, GLenum query, GLdouble *v); +extern  void ( APIENTRY * qglGetMapfv )(GLenum target, GLenum query, GLfloat *v); +extern  void ( APIENTRY * qglGetMapiv )(GLenum target, GLenum query, GLint *v); +extern  void ( APIENTRY * qglGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +extern  void ( APIENTRY * qglGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +extern  void ( APIENTRY * qglGetPixelMapfv )(GLenum map, GLfloat *values); +extern  void ( APIENTRY * qglGetPixelMapuiv )(GLenum map, GLuint *values); +extern  void ( APIENTRY * qglGetPixelMapusv )(GLenum map, GLushort *values); +extern  void ( APIENTRY * qglGetPointerv )(GLenum pname, GLvoid* *params); +extern  void ( APIENTRY * qglGetPolygonStipple )(GLubyte *mask); +extern  const GLubyte * ( APIENTRY * qglGetString )(GLenum name); +extern  void ( APIENTRY * qglGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +extern  void ( APIENTRY * qglGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +extern  void ( APIENTRY * qglGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +extern  void ( APIENTRY * qglGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +extern  void ( APIENTRY * qglGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +extern  void ( APIENTRY * qglGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +extern  void ( APIENTRY * qglGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +extern  void ( APIENTRY * qglGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +extern  void ( APIENTRY * qglGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +extern  void ( APIENTRY * qglGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +extern  void ( APIENTRY * qglHint )(GLenum target, GLenum mode); +extern  void ( APIENTRY * qglIndexMask )(GLuint mask); +extern  void ( APIENTRY * qglIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +extern  void ( APIENTRY * qglIndexd )(GLdouble c); +extern  void ( APIENTRY * qglIndexdv )(const GLdouble *c); +extern  void ( APIENTRY * qglIndexf )(GLfloat c); +extern  void ( APIENTRY * qglIndexfv )(const GLfloat *c); +extern  void ( APIENTRY * qglIndexi )(GLint c); +extern  void ( APIENTRY * qglIndexiv )(const GLint *c); +extern  void ( APIENTRY * qglIndexs )(GLshort c); +extern  void ( APIENTRY * qglIndexsv )(const GLshort *c); +extern  void ( APIENTRY * qglIndexub )(GLubyte c); +extern  void ( APIENTRY * qglIndexubv )(const GLubyte *c); +extern  void ( APIENTRY * qglInitNames )(void); +extern  void ( APIENTRY * qglInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +extern  GLboolean ( APIENTRY * qglIsEnabled )(GLenum cap); +extern  GLboolean ( APIENTRY * qglIsList )(GLuint list); +extern  GLboolean ( APIENTRY * qglIsTexture )(GLuint texture); +extern  void ( APIENTRY * qglLightModelf )(GLenum pname, GLfloat param); +extern  void ( APIENTRY * qglLightModelfv )(GLenum pname, const GLfloat *params); +extern  void ( APIENTRY * qglLightModeli )(GLenum pname, GLint param); +extern  void ( APIENTRY * qglLightModeliv )(GLenum pname, const GLint *params); +extern  void ( APIENTRY * qglLightf )(GLenum light, GLenum pname, GLfloat param); +extern  void ( APIENTRY * qglLightfv )(GLenum light, GLenum pname, const GLfloat *params); +extern  void ( APIENTRY * qglLighti )(GLenum light, GLenum pname, GLint param); +extern  void ( APIENTRY * qglLightiv )(GLenum light, GLenum pname, const GLint *params); +extern  void ( APIENTRY * qglLineStipple )(GLint factor, GLushort pattern); +extern  void ( APIENTRY * qglLineWidth )(GLfloat width); +extern  void ( APIENTRY * qglListBase )(GLuint base); +extern  void ( APIENTRY * qglLoadIdentity )(void); +extern  void ( APIENTRY * qglLoadMatrixd )(const GLdouble *m); +extern  void ( APIENTRY * qglLoadMatrixf )(const GLfloat *m); +extern  void ( APIENTRY * qglLoadName )(GLuint name); +extern  void ( APIENTRY * qglLogicOp )(GLenum opcode); +extern  void ( APIENTRY * qglMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +extern  void ( APIENTRY * qglMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +extern  void ( APIENTRY * qglMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +extern  void ( APIENTRY * qglMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +extern  void ( APIENTRY * qglMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +extern  void ( APIENTRY * qglMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +extern  void ( APIENTRY * qglMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +extern  void ( APIENTRY * qglMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +extern  void ( APIENTRY * qglMaterialf )(GLenum face, GLenum pname, GLfloat param); +extern  void ( APIENTRY * qglMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +extern  void ( APIENTRY * qglMateriali )(GLenum face, GLenum pname, GLint param); +extern  void ( APIENTRY * qglMaterialiv )(GLenum face, GLenum pname, const GLint *params); +extern  void ( APIENTRY * qglMatrixMode )(GLenum mode); +extern  void ( APIENTRY * qglMultMatrixd )(const GLdouble *m); +extern  void ( APIENTRY * qglMultMatrixf )(const GLfloat *m); +extern  void ( APIENTRY * qglNewList )(GLuint list, GLenum mode); +extern  void ( APIENTRY * qglNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +extern  void ( APIENTRY * qglNormal3bv )(const GLbyte *v); +extern  void ( APIENTRY * qglNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +extern  void ( APIENTRY * qglNormal3dv )(const GLdouble *v); +extern  void ( APIENTRY * qglNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +extern  void ( APIENTRY * qglNormal3fv )(const GLfloat *v); +extern  void ( APIENTRY * qglNormal3i )(GLint nx, GLint ny, GLint nz); +extern  void ( APIENTRY * qglNormal3iv )(const GLint *v); +extern  void ( APIENTRY * qglNormal3s )(GLshort nx, GLshort ny, GLshort nz); +extern  void ( APIENTRY * qglNormal3sv )(const GLshort *v); +extern  void ( APIENTRY * qglNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +extern  void ( APIENTRY * qglOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern  void ( APIENTRY * qglPassThrough )(GLfloat token); +extern  void ( APIENTRY * qglPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values); +extern  void ( APIENTRY * qglPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values); +extern  void ( APIENTRY * qglPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values); +extern  void ( APIENTRY * qglPixelStoref )(GLenum pname, GLfloat param); +extern  void ( APIENTRY * qglPixelStorei )(GLenum pname, GLint param); +extern  void ( APIENTRY * qglPixelTransferf )(GLenum pname, GLfloat param); +extern  void ( APIENTRY * qglPixelTransferi )(GLenum pname, GLint param); +extern  void ( APIENTRY * qglPixelZoom )(GLfloat xfactor, GLfloat yfactor); +extern  void ( APIENTRY * qglPointSize )(GLfloat size); +extern  void ( APIENTRY * qglPolygonMode )(GLenum face, GLenum mode); +extern  void ( APIENTRY * qglPolygonOffset )(GLfloat factor, GLfloat units); +extern  void ( APIENTRY * qglPolygonStipple )(const GLubyte *mask); +extern  void ( APIENTRY * qglPopAttrib )(void); +extern  void ( APIENTRY * qglPopClientAttrib )(void); +extern  void ( APIENTRY * qglPopMatrix )(void); +extern  void ( APIENTRY * qglPopName )(void); +extern  void ( APIENTRY * qglPrioritizeTextures )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +extern  void ( APIENTRY * qglPushAttrib )(GLbitfield mask); +extern  void ( APIENTRY * qglPushClientAttrib )(GLbitfield mask); +extern  void ( APIENTRY * qglPushMatrix )(void); +extern  void ( APIENTRY * qglPushName )(GLuint name); +extern  void ( APIENTRY * qglRasterPos2d )(GLdouble x, GLdouble y); +extern  void ( APIENTRY * qglRasterPos2dv )(const GLdouble *v); +extern  void ( APIENTRY * qglRasterPos2f )(GLfloat x, GLfloat y); +extern  void ( APIENTRY * qglRasterPos2fv )(const GLfloat *v); +extern  void ( APIENTRY * qglRasterPos2i )(GLint x, GLint y); +extern  void ( APIENTRY * qglRasterPos2iv )(const GLint *v); +extern  void ( APIENTRY * qglRasterPos2s )(GLshort x, GLshort y); +extern  void ( APIENTRY * qglRasterPos2sv )(const GLshort *v); +extern  void ( APIENTRY * qglRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +extern  void ( APIENTRY * qglRasterPos3dv )(const GLdouble *v); +extern  void ( APIENTRY * qglRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +extern  void ( APIENTRY * qglRasterPos3fv )(const GLfloat *v); +extern  void ( APIENTRY * qglRasterPos3i )(GLint x, GLint y, GLint z); +extern  void ( APIENTRY * qglRasterPos3iv )(const GLint *v); +extern  void ( APIENTRY * qglRasterPos3s )(GLshort x, GLshort y, GLshort z); +extern  void ( APIENTRY * qglRasterPos3sv )(const GLshort *v); +extern  void ( APIENTRY * qglRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern  void ( APIENTRY * qglRasterPos4dv )(const GLdouble *v); +extern  void ( APIENTRY * qglRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern  void ( APIENTRY * qglRasterPos4fv )(const GLfloat *v); +extern  void ( APIENTRY * qglRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +extern  void ( APIENTRY * qglRasterPos4iv )(const GLint *v); +extern  void ( APIENTRY * qglRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern  void ( APIENTRY * qglRasterPos4sv )(const GLshort *v); +extern  void ( APIENTRY * qglReadBuffer )(GLenum mode); +extern  void ( APIENTRY * qglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +extern  void ( APIENTRY * qglRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +extern  void ( APIENTRY * qglRectdv )(const GLdouble *v1, const GLdouble *v2); +extern  void ( APIENTRY * qglRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +extern  void ( APIENTRY * qglRectfv )(const GLfloat *v1, const GLfloat *v2); +extern  void ( APIENTRY * qglRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +extern  void ( APIENTRY * qglRectiv )(const GLint *v1, const GLint *v2); +extern  void ( APIENTRY * qglRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +extern  void ( APIENTRY * qglRectsv )(const GLshort *v1, const GLshort *v2); +extern  GLint ( APIENTRY * qglRenderMode )(GLenum mode); +extern  void ( APIENTRY * qglRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +extern  void ( APIENTRY * qglRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +extern  void ( APIENTRY * qglScaled )(GLdouble x, GLdouble y, GLdouble z); +extern  void ( APIENTRY * qglScalef )(GLfloat x, GLfloat y, GLfloat z); +extern  void ( APIENTRY * qglScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +extern  void ( APIENTRY * qglSelectBuffer )(GLsizei size, GLuint *buffer); +extern  void ( APIENTRY * qglShadeModel )(GLenum mode); +extern  void ( APIENTRY * qglStencilFunc )(GLenum func, GLint ref, GLuint mask); +extern  void ( APIENTRY * qglStencilMask )(GLuint mask); +extern  void ( APIENTRY * qglStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +extern  void ( APIENTRY * qglTexCoord1d )(GLdouble s); +extern  void ( APIENTRY * qglTexCoord1dv )(const GLdouble *v); +extern  void ( APIENTRY * qglTexCoord1f )(GLfloat s); +extern  void ( APIENTRY * qglTexCoord1fv )(const GLfloat *v); +extern  void ( APIENTRY * qglTexCoord1i )(GLint s); +extern  void ( APIENTRY * qglTexCoord1iv )(const GLint *v); +extern  void ( APIENTRY * qglTexCoord1s )(GLshort s); +extern  void ( APIENTRY * qglTexCoord1sv )(const GLshort *v); +extern  void ( APIENTRY * qglTexCoord2d )(GLdouble s, GLdouble t); +extern  void ( APIENTRY * qglTexCoord2dv )(const GLdouble *v); +extern  void ( APIENTRY * qglTexCoord2f )(GLfloat s, GLfloat t); +extern  void ( APIENTRY * qglTexCoord2fv )(const GLfloat *v); +extern  void ( APIENTRY * qglTexCoord2i )(GLint s, GLint t); +extern  void ( APIENTRY * qglTexCoord2iv )(const GLint *v); +extern  void ( APIENTRY * qglTexCoord2s )(GLshort s, GLshort t); +extern  void ( APIENTRY * qglTexCoord2sv )(const GLshort *v); +extern  void ( APIENTRY * qglTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +extern  void ( APIENTRY * qglTexCoord3dv )(const GLdouble *v); +extern  void ( APIENTRY * qglTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +extern  void ( APIENTRY * qglTexCoord3fv )(const GLfloat *v); +extern  void ( APIENTRY * qglTexCoord3i )(GLint s, GLint t, GLint r); +extern  void ( APIENTRY * qglTexCoord3iv )(const GLint *v); +extern  void ( APIENTRY * qglTexCoord3s )(GLshort s, GLshort t, GLshort r); +extern  void ( APIENTRY * qglTexCoord3sv )(const GLshort *v); +extern  void ( APIENTRY * qglTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +extern  void ( APIENTRY * qglTexCoord4dv )(const GLdouble *v); +extern  void ( APIENTRY * qglTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +extern  void ( APIENTRY * qglTexCoord4fv )(const GLfloat *v); +extern  void ( APIENTRY * qglTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +extern  void ( APIENTRY * qglTexCoord4iv )(const GLint *v); +extern  void ( APIENTRY * qglTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +extern  void ( APIENTRY * qglTexCoord4sv )(const GLshort *v); +extern  void ( APIENTRY * qglTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern  void ( APIENTRY * qglTexEnvf )(GLenum target, GLenum pname, GLfloat param); +extern  void ( APIENTRY * qglTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +extern  void ( APIENTRY * qglTexEnvi )(GLenum target, GLenum pname, GLint param); +extern  void ( APIENTRY * qglTexEnviv )(GLenum target, GLenum pname, const GLint *params); +extern  void ( APIENTRY * qglTexGend )(GLenum coord, GLenum pname, GLdouble param); +extern  void ( APIENTRY * qglTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +extern  void ( APIENTRY * qglTexGenf )(GLenum coord, GLenum pname, GLfloat param); +extern  void ( APIENTRY * qglTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +extern  void ( APIENTRY * qglTexGeni )(GLenum coord, GLenum pname, GLint param); +extern  void ( APIENTRY * qglTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +extern  void ( APIENTRY * qglTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern  void ( APIENTRY * qglTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern  void ( APIENTRY * qglTexParameterf )(GLenum target, GLenum pname, GLfloat param); +extern  void ( APIENTRY * qglTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +extern  void ( APIENTRY * qglTexParameteri )(GLenum target, GLenum pname, GLint param); +extern  void ( APIENTRY * qglTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +extern  void ( APIENTRY * qglTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +extern  void ( APIENTRY * qglTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +extern  void ( APIENTRY * qglTranslated )(GLdouble x, GLdouble y, GLdouble z); +extern  void ( APIENTRY * qglTranslatef )(GLfloat x, GLfloat y, GLfloat z); +extern  void ( APIENTRY * qglVertex2d )(GLdouble x, GLdouble y); +extern  void ( APIENTRY * qglVertex2dv )(const GLdouble *v); +extern  void ( APIENTRY * qglVertex2f )(GLfloat x, GLfloat y); +extern  void ( APIENTRY * qglVertex2fv )(const GLfloat *v); +extern  void ( APIENTRY * qglVertex2i )(GLint x, GLint y); +extern  void ( APIENTRY * qglVertex2iv )(const GLint *v); +extern  void ( APIENTRY * qglVertex2s )(GLshort x, GLshort y); +extern  void ( APIENTRY * qglVertex2sv )(const GLshort *v); +extern  void ( APIENTRY * qglVertex3d )(GLdouble x, GLdouble y, GLdouble z); +extern  void ( APIENTRY * qglVertex3dv )(const GLdouble *v); +extern  void ( APIENTRY * qglVertex3f )(GLfloat x, GLfloat y, GLfloat z); +extern  void ( APIENTRY * qglVertex3fv )(const GLfloat *v); +extern  void ( APIENTRY * qglVertex3i )(GLint x, GLint y, GLint z); +extern  void ( APIENTRY * qglVertex3iv )(const GLint *v); +extern  void ( APIENTRY * qglVertex3s )(GLshort x, GLshort y, GLshort z); +extern  void ( APIENTRY * qglVertex3sv )(const GLshort *v); +extern  void ( APIENTRY * qglVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern  void ( APIENTRY * qglVertex4dv )(const GLdouble *v); +extern  void ( APIENTRY * qglVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern  void ( APIENTRY * qglVertex4fv )(const GLfloat *v); +extern  void ( APIENTRY * qglVertex4i )(GLint x, GLint y, GLint z, GLint w); +extern  void ( APIENTRY * qglVertex4iv )(const GLint *v); +extern  void ( APIENTRY * qglVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern  void ( APIENTRY * qglVertex4sv )(const GLshort *v); +extern  void ( APIENTRY * qglVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern  void ( APIENTRY * qglViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + +#if defined( _WIN32 ) + +extern  int   ( WINAPI * qwglChoosePixelFormat )(HDC, CONST PIXELFORMATDESCRIPTOR *); +extern  int   ( WINAPI * qwglDescribePixelFormat) (HDC, int, UINT, LPPIXELFORMATDESCRIPTOR); +extern  int   ( WINAPI * qwglGetPixelFormat)(HDC); +extern  BOOL  ( WINAPI * qwglSetPixelFormat)(HDC, int, CONST PIXELFORMATDESCRIPTOR *); +extern  BOOL  ( WINAPI * qwglSwapBuffers)(HDC); + +extern	BOOL  ( WINAPI * qwglGetDeviceGammaRamp3DFX)( HDC, LPVOID ); +extern	BOOL  ( WINAPI * qwglSetDeviceGammaRamp3DFX)( HDC, LPVOID ); + +extern BOOL  ( WINAPI * qwglCopyContext)(HGLRC, HGLRC, UINT); +extern HGLRC ( WINAPI * qwglCreateContext)(HDC); +extern HGLRC ( WINAPI * qwglCreateLayerContext)(HDC, int); +extern BOOL  ( WINAPI * qwglDeleteContext)(HGLRC); +extern HGLRC ( WINAPI * qwglGetCurrentContext)(VOID); +extern HDC   ( WINAPI * qwglGetCurrentDC)(VOID); +extern PROC  ( WINAPI * qwglGetProcAddress)(LPCSTR); +extern BOOL  ( WINAPI * qwglMakeCurrent)(HDC, HGLRC); +extern BOOL  ( WINAPI * qwglShareLists)(HGLRC, HGLRC); +extern BOOL  ( WINAPI * qwglUseFontBitmaps)(HDC, DWORD, DWORD, DWORD); + +extern BOOL  ( WINAPI * qwglUseFontOutlines)(HDC, DWORD, DWORD, DWORD, FLOAT, +                                           FLOAT, int, LPGLYPHMETRICSFLOAT); + +extern BOOL ( WINAPI * qwglDescribeLayerPlane)(HDC, int, int, UINT, +                                            LPLAYERPLANEDESCRIPTOR); +extern int  ( WINAPI * qwglSetLayerPaletteEntries)(HDC, int, int, int, +                                                CONST COLORREF *); +extern int  ( WINAPI * qwglGetLayerPaletteEntries)(HDC, int, int, int, +                                                COLORREF *); +extern BOOL ( WINAPI * qwglRealizeLayerPalette)(HDC, int, BOOL); +extern BOOL ( WINAPI * qwglSwapLayerBuffers)(HDC, UINT); + +extern BOOL ( WINAPI * qwglSwapIntervalEXT)( int interval ); + +#endif	// _WIN32 + +#if ( (defined __linux__ )  || (defined __FreeBSD__ ) || (defined __sun) ) // rb010123 + +//FX Mesa Functions +// bk001129 - from cvs1.17 (mkv) +#if defined (__FX__) +extern fxMesaContext (*qfxMesaCreateContext)(GLuint win, GrScreenResolution_t, GrScreenRefresh_t, const GLint attribList[]); +extern fxMesaContext (*qfxMesaCreateBestContext)(GLuint win, GLint width, GLint height, const GLint attribList[]); +extern void (*qfxMesaDestroyContext)(fxMesaContext ctx); +extern void (*qfxMesaMakeCurrent)(fxMesaContext ctx); +extern fxMesaContext (*qfxMesaGetCurrentContext)(void); +extern void (*qfxMesaSwapBuffers)(void); +#endif + +//GLX Functions +extern XVisualInfo * (*qglXChooseVisual)( Display *dpy, int screen, int *attribList ); +extern GLXContext (*qglXCreateContext)( Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct ); +extern void (*qglXDestroyContext)( Display *dpy, GLXContext ctx ); +extern Bool (*qglXMakeCurrent)( Display *dpy, GLXDrawable drawable, GLXContext ctx); +extern void (*qglXCopyContext)( Display *dpy, GLXContext src, GLXContext dst, GLuint mask ); +extern void (*qglXSwapBuffers)( Display *dpy, GLXDrawable drawable ); + +#endif // __linux__ || __FreeBSD__ || __sun // rb010123 + +#endif	// _WIN32 && __linux__ + +#endif diff --git a/src/renderer/qgl_linked.h b/src/renderer/qgl_linked.h new file mode 100644 index 0000000..6f682ae --- /dev/null +++ b/src/renderer/qgl_linked.h @@ -0,0 +1,358 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +=========================================================================== +*/ + +#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 + diff --git a/src/renderer/tr_animation.c b/src/renderer/tr_animation.c new file mode 100644 index 0000000..88b8580 --- /dev/null +++ b/src/renderer/tr_animation.c @@ -0,0 +1,659 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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/src/renderer/tr_backend.c b/src/renderer/tr_backend.c new file mode 100644 index 0000000..9c9b841 --- /dev/null +++ b/src/renderer/tr_backend.c @@ -0,0 +1,1125 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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 +	// +	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 ); +		} +	} + +	// +	// 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]; +		double	plane2[4]; + +		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 ); +		qglClipPlane (GL_CLIP_PLANE0, plane2); +		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; +	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; +	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 = 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; +				} +			} 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 if needed +			// +			if ( oldDepthRange != depthRange ) { +				if ( depthRange ) { +					qglDepthRange (0, 0.3); +				} else { +					qglDepthRange (0, 1); +				} +				oldDepthRange = depthRange; +			} + +			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 ) { +		qglDepthRange (0, 1); +	} + +#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 (); +	qglOrtho (0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, 1); +	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; + +	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; +		qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); +		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 ); +		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );	 +	} 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(); + +	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 (); +} + +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; +		qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); +		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 ); +		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );	 +	} 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; + +	if ( !backEnd.projection2D ) { +		RB_SetGL2D(); +	} + +	qglClear( GL_COLOR_BUFFER_BIT ); + +	qglFinish(); + +	start = ri.Milliseconds(); + +	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; +		} + +		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(); +	} + +	qglFinish(); + +	end = ri.Milliseconds(); +	ri.Printf( PRINT_ALL, "%i msec to draw all images\n", end - start ); + +} + + +/* +============= +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; + +	// 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 ); +	} + + +	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_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/src/renderer/tr_bsp.c b/src/renderer/tr_bsp.c new file mode 100644 index 0000000..2eff834 --- /dev/null +++ b/src/renderer/tr_bsp.c @@ -0,0 +1,1866 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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 ); +	} + +	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 + +		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; +	byte		*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, (void **)&buffer ); +	if ( !buffer ) { +		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; +	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 ); +} + diff --git a/src/renderer/tr_cmds.c b/src/renderer/tr_cmds.c new file mode 100644 index 0000000..d637aec --- /dev/null +++ b/src/renderer/tr_cmds.c @@ -0,0 +1,475 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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); // bk001205 +	// 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; +} + + +/* +==================== +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; + +	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 ); +        } +    } + +	// +	// draw buffer stuff +	// +	cmd = R_GetCommandBuffer( sizeof( *cmd ) ); +	if ( !cmd ) { +		return; +	} +	cmd->commandId = RC_DRAW_BUFFER; + +	if ( glConfig.stereoEnabled ) { +		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 { +		if ( stereoFrame != STEREO_CENTER ) { +			ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is disabled, but stereoFrame was %i", stereoFrame ); +		} +		if ( !Q_stricmp( r_drawBuffer->string, "GL_FRONT" ) ) { +			cmd->buffer = (int)GL_FRONT; +		} else { +			cmd->buffer = (int)GL_BACK; +		} +	} +} + + +/* +============= +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/src/renderer/tr_curve.c b/src/renderer/tr_curve.c new file mode 100644 index 0000000..ecacf3a --- /dev/null +++ b/src/renderer/tr_curve.c @@ -0,0 +1,627 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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/src/renderer/tr_flares.c b/src/renderer/tr_flares.c new file mode 100644 index 0000000..f8fb222 --- /dev/null +++ b/src/renderer/tr_flares.c @@ -0,0 +1,530 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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 + +=============================================================================== +*/ + +/* +================== +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(); +} + diff --git a/src/renderer/tr_font.c b/src/renderer/tr_font.c new file mode 100644 index 0000000..ea7b9e8 --- /dev/null +++ b/src/renderer/tr_font.c @@ -0,0 +1,550 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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, ®isteredFont[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(®isteredFont[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 = ®isteredFonts[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); +      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(®isteredFont[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/src/renderer/tr_image.c b/src/renderer/tr_image.c new file mode 100644 index 0000000..bd51ff2 --- /dev/null +++ b/src/renderer/tr_image.c @@ -0,0 +1,2646 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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" + +/* + * 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-6/jpeglib.h" + + +static void LoadBMP( const char *name, byte **pic, int *width, int *height ); +static void LoadTGA( const char *name, byte **pic, int *width, int *height ); +static void LoadJPG( const char *name, byte **pic, int *width, int *height ); + +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; +		case GL_RGBA8: +			ri.Printf( PRINT_ALL, "RGBA8" ); +			break; +		case GL_RGB8: +			ri.Printf( PRINT_ALL, "RGB8" ); +			break; +		case GL_RGB4_S3TC: +			ri.Printf( PRINT_ALL, "S3TC " ); +			break; +		case GL_RGBA4: +			ri.Printf( PRINT_ALL, "RGBA4" ); +			break; +		case GL_RGB5: +			ri.Printf( PRINT_ALL, "RGB5 " ); +			break; +		default: +			ri.Printf( PRINT_ALL, "???? " ); +		} + +		switch ( image->wrapClampMode ) { +		case GL_REPEAT: +			ri.Printf( PRINT_ALL, "rept " ); +			break; +		case GL_CLAMP: +			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; +	if (!lightMap) { +		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 ) +		{ +			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 = 3; +			} +		} +		else if ( samples == 4 ) +		{ +			if ( r_texturebits->integer == 16 ) +			{ +				internalFormat = GL_RGBA4; +			} +			else if ( r_texturebits->integer == 32 ) +			{ +				internalFormat = GL_RGBA8; +			} +			else +			{ +				internalFormat = 4; +			} +		} +	} else { +		internalFormat = 3; +	} +	// 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) +	{ +		if ( glConfig.textureFilterAnisotropic ) +			qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, +					(GLint)Com_Clamp( 1, glConfig.maxAnisotropy, r_ext_max_anisotropy->integer ) ); + +		qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); +		qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); +	} +	else +	{ +		if ( glConfig.textureFilterAnisotropic ) +			qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1 ); + +		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; +} + + +/* +========================================================= + +BMP LOADING + +========================================================= +*/ +typedef struct +{ +	char id[2]; +	unsigned long fileSize; +	unsigned long reserved0; +	unsigned long bitmapDataOffset; +	unsigned long bitmapHeaderSize; +	unsigned long width; +	unsigned long height; +	unsigned short planes; +	unsigned short bitsPerPixel; +	unsigned long compression; +	unsigned long bitmapDataSize; +	unsigned long hRes; +	unsigned long vRes; +	unsigned long colors; +	unsigned long importantColors; +	unsigned char palette[256][4]; +} BMPHeader_t; + +static void LoadBMP( const char *name, byte **pic, int *width, int *height ) +{ +	int		columns, rows; +	unsigned	numPixels; +	byte	*pixbuf; +	int		row, column; +	byte	*buf_p; +	byte	*buffer; +	int		length; +	BMPHeader_t bmpHeader; +	byte		*bmpRGBA; + +	*pic = NULL; + +	// +	// load the file +	// +	length = ri.FS_ReadFile( ( char * ) name, (void **)&buffer); +	if (!buffer) { +		return; +	} + +	buf_p = buffer; + +	bmpHeader.id[0] = *buf_p++; +	bmpHeader.id[1] = *buf_p++; +	bmpHeader.fileSize = LittleLong( * ( long * ) buf_p ); +	buf_p += 4; +	bmpHeader.reserved0 = LittleLong( * ( long * ) buf_p ); +	buf_p += 4; +	bmpHeader.bitmapDataOffset = LittleLong( * ( long * ) buf_p ); +	buf_p += 4; +	bmpHeader.bitmapHeaderSize = LittleLong( * ( long * ) buf_p ); +	buf_p += 4; +	bmpHeader.width = LittleLong( * ( long * ) buf_p ); +	buf_p += 4; +	bmpHeader.height = LittleLong( * ( long * ) 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( * ( long * ) buf_p ); +	buf_p += 4; +	bmpHeader.bitmapDataSize = LittleLong( * ( long * ) buf_p ); +	buf_p += 4; +	bmpHeader.hRes = LittleLong( * ( long * ) buf_p ); +	buf_p += 4; +	bmpHeader.vRes = LittleLong( * ( long * ) buf_p ); +	buf_p += 4; +	bmpHeader.colors = LittleLong( * ( long * ) buf_p ); +	buf_p += 4; +	bmpHeader.importantColors = LittleLong( * ( long * ) buf_p ); +	buf_p += 4; + +	Com_Memcpy( bmpHeader.palette, buf_p, sizeof( bmpHeader.palette ) ); + +	if ( bmpHeader.bitsPerPixel == 8 ) +		buf_p += 1024; + +	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 (%d vs. %d) (%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 ); +	} + +	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 ( 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; +			default: +				ri.Error( ERR_DROP, "LoadBMP: illegal pixel_size '%d' in file '%s'\n", bmpHeader.bitsPerPixel, name ); +				break; +			} +		} +	} + +	ri.FS_FreeFile( buffer ); + +} + + +/* +================================================================= + +PCX LOADING + +================================================================= +*/ + + +/* +============== +LoadPCX +============== +*/ +static void LoadPCX ( const char *filename, byte **pic, byte **palette, int *width, int *height) +{ +	byte	*raw; +	pcx_t	*pcx; +	int		x, y; +	int		len; +	int		dataByte, runLength; +	byte	*out, *pix; +	unsigned		xmax, ymax; + +	*pic = NULL; +	*palette = NULL; + +	// +	// load the file +	// +	len = ri.FS_ReadFile( ( char * ) filename, (void **)&raw); +	if (!raw) { +		return; +	} + +	// +	// parse the PCX file +	// +	pcx = (pcx_t *)raw; +	raw = &pcx->data; + +  	xmax = LittleShort(pcx->xmax); +    ymax = LittleShort(pcx->ymax); + +	if (pcx->manufacturer != 0x0a +		|| pcx->version != 5 +		|| pcx->encoding != 1 +		|| pcx->bits_per_pixel != 8 +		|| xmax >= 1024 +		|| ymax >= 1024) +	{ +		ri.Printf (PRINT_ALL, "Bad pcx file %s (%i x %i) (%i x %i)\n", filename, xmax+1, ymax+1, pcx->xmax, pcx->ymax); +		return; +	} + +	out = ri.Malloc ( (ymax+1) * (xmax+1) ); + +	*pic = out; + +	pix = out; + +	if (palette) +	{ +		*palette = ri.Malloc(768); +		Com_Memcpy (*palette, (byte *)pcx + len - 768, 768); +	} + +	if (width) +		*width = xmax+1; +	if (height) +		*height = ymax+1; +// FIXME: use bytes_per_line here? + +	for (y=0 ; y<=ymax ; y++, pix += xmax+1) +	{ +		for (x=0 ; x<=xmax ; ) +		{ +			dataByte = *raw++; + +			if((dataByte & 0xC0) == 0xC0) +			{ +				runLength = dataByte & 0x3F; +				dataByte = *raw++; +			} +			else +				runLength = 1; + +			while(runLength-- > 0) +				pix[x++] = dataByte; +		} + +	} + +	if ( raw - (byte *)pcx > len) +	{ +		ri.Printf (PRINT_DEVELOPER, "PCX file %s was malformed", filename); +		ri.Free (*pic); +		*pic = NULL; +	} + +	ri.FS_FreeFile (pcx); +} + + +/* +============== +LoadPCX32 +============== +*/ +static void LoadPCX32 ( const char *filename, byte **pic, int *width, int *height) { +	byte	*palette; +	byte	*pic8; +	int		i, c, p; +	byte	*pic32; + +	LoadPCX (filename, &pic8, &palette, width, height); +	if (!pic8) { +		*pic = NULL; +		return; +	} + +	// LoadPCX32 ensures width, height < 1024 +	c = (*width) * (*height); +	pic32 = *pic = ri.Malloc(4 * c ); +	for (i = 0 ; i < c ; i++) { +		p = pic8[i]; +		pic32[0] = palette[p*3]; +		pic32[1] = palette[p*3 + 1]; +		pic32[2] = palette[p*3 + 2]; +		pic32[3] = 255; +		pic32 += 4; +	} + +	ri.Free (pic8); +	ri.Free (palette); +} + +/* +========================================================= + +TARGA LOADING + +========================================================= +*/ + +/* +============= +LoadTGA +============= +*/ +static void LoadTGA ( const char *name, byte **pic, int *width, int *height) +{ +	unsigned	columns, rows, numPixels; +	byte	*pixbuf; +	int		row, column; +	byte	*buf_p; +	byte	*buffer; +	TargaHeader	targa_header; +	byte		*targa_rgba; + +	*pic = NULL; + +	// +	// load the file +	// +	ri.FS_ReadFile ( ( char * ) name, (void **)&buffer); +	if (!buffer) { +		return; +	} + +	buf_p = buffer; + +	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 (width) +		*width = columns; +	if (height) +		*height = rows; + +	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); +	*pic = targa_rgba; + +	if (targa_header.id_length != 0) +		buf_p += targa_header.id_length;  // skip TARGA image comment +	 +	if ( targa_header.image_type==2 || targa_header.image_type == 3 ) +	{  +		// 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; ) { +				packetHeader= *buf_p++; +				packetSize = 1 + (packetHeader & 0x7f); +				if (packetHeader & 0x80) {        // run-length packet +					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 +					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  +  // bk0101024 - fix from Leonardo +  // 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); +  } + +  ri.FS_FreeFile (buffer); +} + +static void 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; +  byte	*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. +   */ + +  ri.FS_ReadFile ( ( char * ) filename, (void **)&fbuffer); +  if (!fbuffer) { +	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_stdio_src(&cinfo, fbuffer); + +  /* 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); + +  /* 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. + */ + +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. + */ + +boolean empty_output_buffer (j_compress_ptr cinfo) +{ +  return TRUE; +} + + +/* + * Compression initialization. + * Before calling this, all parameters and a data destination must be set up. + * + * We require a write_all_tables parameter as a failsafe check when writing + * multiple datastreams from the same compression object.  Since prior runs + * will have left all the tables marked sent_table=TRUE, a subsequent run + * would emit an abbreviated stream (no tables) by default.  This may be what + * is wanted, but for safety's sake it should not be the default behavior: + * programmers should have to make a deliberate choice to emit abbreviated + * images.  Therefore the documentation and examples should encourage people + * to pass write_all_tables=TRUE; then it will take active thought to do the + * wrong thing. + */ + +GLOBAL void +jpeg_start_compress (j_compress_ptr cinfo, boolean write_all_tables) +{ +  if (cinfo->global_state != CSTATE_START) +    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + +  if (write_all_tables) +    jpeg_suppress_tables(cinfo, FALSE);	/* mark all tables to be written */ + +  /* (Re)initialize error mgr and destination modules */ +  (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); +  (*cinfo->dest->init_destination) (cinfo); +  /* Perform master selection of active modules */ +  jinit_compress_master(cinfo); +  /* Set up for the first pass */ +  (*cinfo->master->prepare_for_pass) (cinfo); +  /* Ready for application to drive first pass through jpeg_write_scanlines +   * or jpeg_write_raw_data. +   */ +  cinfo->next_scanline = 0; +  cinfo->global_state = (cinfo->raw_data_in ? CSTATE_RAW_OK : CSTATE_SCANNING); +} + + +/* + * Write some scanlines of data to the JPEG compressor. + * + * The return value will be the number of lines actually written. + * This should be less than the supplied num_lines only in case that + * the data destination module has requested suspension of the compressor, + * or if more than image_height scanlines are passed in. + * + * Note: we warn about excess calls to jpeg_write_scanlines() since + * this likely signals an application programmer error.  However, + * excess scanlines passed in the last valid call are *silently* ignored, + * so that the application need not adjust num_lines for end-of-image + * when using a multiple-scanline buffer. + */ + +GLOBAL JDIMENSION +jpeg_write_scanlines (j_compress_ptr cinfo, JSAMPARRAY scanlines, +		      JDIMENSION num_lines) +{ +  JDIMENSION row_ctr, rows_left; + +  if (cinfo->global_state != CSTATE_SCANNING) +    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); +  if (cinfo->next_scanline >= cinfo->image_height) +    WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + +  /* Call progress monitor hook if present */ +  if (cinfo->progress != NULL) { +    cinfo->progress->pass_counter = (long) cinfo->next_scanline; +    cinfo->progress->pass_limit = (long) cinfo->image_height; +    (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); +  } + +  /* Give master control module another chance if this is first call to +   * jpeg_write_scanlines.  This lets output of the frame/scan headers be +   * delayed so that application can write COM, etc, markers between +   * jpeg_start_compress and jpeg_write_scanlines. +   */ +  if (cinfo->master->call_pass_startup) +    (*cinfo->master->pass_startup) (cinfo); + +  /* Ignore any extra scanlines at bottom of image. */ +  rows_left = cinfo->image_height - cinfo->next_scanline; +  if (num_lines > rows_left) +    num_lines = rows_left; + +  row_ctr = 0; +  (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, num_lines); +  cinfo->next_scanline += row_ctr; +  return row_ctr; +} + +/* + * 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; + +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. + */ + +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; +} + +//=================================================================== + +/* +================= +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 ) { +	int		len; + +	*pic = NULL; +	*width = 0; +	*height = 0; + +	len = strlen(name); +	if (len<5) { +		return; +	} + +	if ( !Q_stricmp( name+len-4, ".tga" ) ) { +	  LoadTGA( name, pic, width, height );            // try tga first +    if (!*pic) {                                    // +		  char altname[MAX_QPATH];                      // try jpg in place of tga  +      strcpy( altname, name );                       +      len = strlen( altname );                   +      altname[len-3] = 'j'; +      altname[len-2] = 'p'; +      altname[len-1] = 'g'; +			LoadJPG( altname, pic, width, height ); +		} +  } else if ( !Q_stricmp(name+len-4, ".pcx") ) { +    LoadPCX32( name, pic, width, height ); +	} else if ( !Q_stricmp( name+len-4, ".bmp" ) ) { +		LoadBMP( name, pic, width, height ); +	} else if ( !Q_stricmp( name+len-4, ".jpg" ) ) { +		LoadJPG( name, pic, width, height ); +	} +} + + +/* +=============== +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 ) {                                    // if we dont get a successful load +	  char altname[MAX_QPATH];                              // copy the name +    int len;                                              //   +    strcpy( altname, name );                              // +    len = strlen( altname );                              //  +    altname[len-3] = toupper(altname[len-3]);             // and try upper case extension for unix systems +    altname[len-2] = toupper(altname[len-2]);             // +    altname[len-1] = toupper(altname[len-1]);             // +		ri.Printf( PRINT_ALL, "trying %s...\n", altname );    //  +	  R_LoadImage( altname, &pic, &width, &height );        // +    if (pic == NULL) {                                    // if that fails +      return NULL;                                        // bail +    } +	} + +	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 ); +} + + +/* +================= +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; +	float	borderColor[4]; + +	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 ); +	ri.Hunk_FreeTempMemory( data ); + +	borderColor[0] = 1.0; +	borderColor[1] = 1.0; +	borderColor[2] = 1.0; +	borderColor[3] = 1; + +	qglTexParameterfv( GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor ); +} + +/* +================== +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 ); +	} + +	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 ( qglBindTexture ) { +		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; +	char		*text, *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, (void **)&text ); +	if ( !text ) { +		return 0; +	} + +	text_p = text; +	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 ); + + +	// 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/src/renderer/tr_init.c b/src/renderer/tr_init.c new file mode 100644 index 0000000..b9b676a --- /dev/null +++ b/src/renderer/tr_init.c @@ -0,0 +1,1356 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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; +                 +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_displayRefresh; + +cvar_t	*r_detailTextures; + +cvar_t	*r_znear; + +cvar_t	*r_smp; +cvar_t	*r_showSmp; +cvar_t	*r_skipBackEnd; + +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_gamma_control; +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_stereo; +cvar_t	*r_primitives; +cvar_t	*r_texturebits; + +cvar_t	*r_drawBuffer; +cvar_t  *r_glDriver; +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_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_GLlibCoolDownMsec; + +cvar_t	*r_maxpolys; +int		max_polys; +cvar_t	*r_maxpolyverts; +int		max_polyverts; + +static void AssertCvarRange( cvar_t *cv, float minVal, float maxVal, qboolean shouldBeIntegral ) +{ +	if ( shouldBeIntegral ) +	{ +		if ( ( int ) cv->value != cv->integer ) +		{ +			ri.Printf( PRINT_WARNING, "WARNING: cvar '%s' must be integral (%f)\n", cv->name, cv->value ); +			ri.Cvar_Set( cv->name, va( "%d", cv->integer ) ); +		} +	} + +	if ( cv->value < minVal ) +	{ +		ri.Printf( PRINT_WARNING, "WARNING: cvar '%s' out of range (%f < %f)\n", cv->name, cv->value, minVal ); +		ri.Cvar_Set( cv->name, va( "%f", minVal ) ); +	} +	else if ( cv->value > maxVal ) +	{ +		ri.Printf( PRINT_WARNING, "WARNING: cvar '%s' out of range (%f > %f)\n", cv->name, cv->value, maxVal ); +		ri.Cvar_Set( cv->name, va( "%f", maxVal ) ); +	} +} + + +#define GENERIC_HW_R_PICMIP_DEFAULT				"0" +#define GENERIC_HW_R_TEXTUREMODE_DEFAULT	"GL_LINEAR_MIPMAP_LINEAR" + +/* +================== +GL_ResolveHardwareType + +Chipset specific configuration +================== +*/ +void GL_ResolveHardwareType( void ) +{ +	char		buf[ 1024 ]; +	cvar_t	*lastValidRenderer = ri.Cvar_Get( +			"r_lastValidRenderer", "(uninitialized)", CVAR_ARCHIVE ); + +	Q_strncpyz( buf, glConfig.renderer_string, sizeof( buf ) ); +	Q_strlwr( buf ); + +	// NOTE: if changing cvars, do it within this block.  This allows them +	// to be overridden when testing driver fixes, etc. but only sets +	// them to their default state when the hardware is first installed/run. +	if( Q_stricmp( lastValidRenderer->string, glConfig.renderer_string ) ) +	{ +		glConfig.hardwareType = GLHW_GENERIC; + +		ri.Cvar_Set( "r_textureMode", GENERIC_HW_R_TEXTUREMODE_DEFAULT ); + +		// VOODOO GRAPHICS w/ 2MB +		if ( strstr( buf, "voodoo graphics/1 tmu/2 mb" ) ) +		{ +			ri.Cvar_Set( "r_picmip", "2" ); +			ri.Cvar_Get( "r_picmip", "1", CVAR_ARCHIVE | CVAR_LATCH ); +		} +		else +		{ +			ri.Cvar_Set( "r_picmip", GENERIC_HW_R_PICMIP_DEFAULT ); + +			if ( strstr( buf, "rage 128" ) || strstr( buf, "rage128" ) ) +			{ +				ri.Cvar_Set( "r_finish", "0" ); +			} +			// Savage3D and Savage4 should always have trilinear enabled +			else if ( strstr( buf, "savage3d" ) || strstr( buf, "s3 savage4" ) ) +			{ +				ri.Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" ); +			} +		} +	} +	 +	// +	// this is where hardware specific workarounds that should be +	// detected/initialized every startup should go. +	// +	if ( strstr( buf, "banshee" ) || strstr( buf, "voodoo3" ) ) +	{ +		glConfig.hardwareType = GLHW_3DFX_2D3D; +	} +	// VOODOO GRAPHICS w/ 2MB +	else if ( strstr( buf, "voodoo graphics/1 tmu/2 mb" ) ) +	{ +	} +	else if ( strstr( buf, "glzicd" ) ) +	{ +	} +	else if ( strstr( buf, "rage pro" ) || +			strstr( buf, "Rage Pro" ) || +			strstr( buf, "ragepro" ) ) +	{ +		glConfig.hardwareType = GLHW_RAGEPRO; +	} +	else if ( strstr( buf, "rage 128" ) ) +	{ +	} +	else if ( strstr( buf, "permedia2" ) ) +	{ +		glConfig.hardwareType = GLHW_PERMEDIA2; +	} +	else if ( strstr( buf, "riva 128" ) ) +	{ +		glConfig.hardwareType = GLHW_RIVA128; +	} +	else if ( strstr( buf, "riva tnt " ) ) +	{ +	} +} + +/* +** 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_glDriver +	//		- 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 ( ( tr.overbrightBits > 0 ) && 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 ( ( tr.overbrightBits > 0 ) && 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; + +	sprintf( 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 ( ( tr.overbrightBits > 0 ) && 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( ( tr.overbrightBits > 0 ) && 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; + +	qglPolygonMode (GL_FRONT_AND_BACK, GL_FILL); +	qglDepthMask( GL_TRUE ); +	qglDisable( GL_DEPTH_TEST ); +	qglEnable( GL_SCISSOR_TEST ); +	qglDisable( GL_CULL_FACE ); +	qglDisable( GL_BLEND ); +} + + +/* +================ +GfxInfo_f +================ +*/ +void GfxInfo_f( void )  +{ +	cvar_t *sys_cpustring = ri.Cvar_Get( "sys_cpustring", "", 0 ); +	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_ACTIVE_TEXTURES_ARB: %d\n", glConfig.maxActiveTextures ); +	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 ); +	} +	ri.Printf( PRINT_ALL, "CPU: %s\n", sys_cpustring->string ); + +	// 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_glDriver = ri.Cvar_Get( "r_glDriver", OPENGL_DRIVER_NAME, CVAR_ARCHIVE | CVAR_LATCH ); +	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_gamma_control = ri.Cvar_Get( "r_ext_gamma_control", "1", 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", "0", CVAR_ARCHIVE | CVAR_LATCH); + +	r_picmip = ri.Cvar_Get ("r_picmip", GENERIC_HW_R_PICMIP_DEFAULT, +			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_roundImagesDown = ri.Cvar_Get ("r_roundImagesDown", "1", CVAR_ARCHIVE | CVAR_LATCH ); +	r_colorMipLevels = ri.Cvar_Get ("r_colorMipLevels", "0", CVAR_LATCH ); +	AssertCvarRange( 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_stereo = ri.Cvar_Get( "r_stereo", "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_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 ); +#if USE_SDL_VIDEO +	r_fullscreen = ri.Cvar_Get( "r_fullscreen", "1", CVAR_ARCHIVE ); +#else +	r_fullscreen = ri.Cvar_Get( "r_fullscreen", "1", CVAR_ARCHIVE | CVAR_LATCH ); +#endif +	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_ignoreFastPath = ri.Cvar_Get( "r_ignoreFastPath", "1", CVAR_ARCHIVE | CVAR_LATCH ); + +	// +	// temporary latched variables that can only change over a restart +	// +	r_displayRefresh = ri.Cvar_Get( "r_displayRefresh", "0", CVAR_LATCH ); +	AssertCvarRange( r_displayRefresh, 0, 200, qtrue ); +	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", "1", CVAR_CHEAT ); +	AssertCvarRange( r_znear, 0.001f, 200, qtrue ); +	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", +			GENERIC_HW_R_TEXTUREMODE_DEFAULT, CVAR_ARCHIVE ); +#if USE_SDL_VIDEO +	r_swapInterval = ri.Cvar_Get( "r_swapInterval", "0", +					CVAR_ARCHIVE | CVAR_LATCH ); +#else +	r_swapInterval = ri.Cvar_Get( "r_swapInterval", "0", CVAR_ARCHIVE ); +#endif +	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 ); + +	// +	// 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_maxpolys = ri.Cvar_Get( "r_maxpolys", va("%d", MAX_POLYS), 0); +	r_maxpolyverts = ri.Cvar_Get( "r_maxpolyverts", va("%d", MAX_POLYVERTS), 0); + +	r_GLlibCoolDownMsec = ri.Cvar_Get( "r_GLlibCoolDownMsec", "0", CVAR_ARCHIVE ); +   +	// 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 ) ); + +//	Swap_Init(); + +	if ( (int)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/src/renderer/tr_light.c b/src/renderer/tr_light.c new file mode 100644 index 0000000..1be629e --- /dev/null +++ b/src/renderer/tr_light.c @@ -0,0 +1,396 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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 ); // bk010103 - 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; +	 +	// bk010103 - this segfaults with -nolight maps +	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/src/renderer/tr_local.h b/src/renderer/tr_local.h new file mode 100644 index 0000000..7c69475 --- /dev/null +++ b/src/renderer/tr_local.h @@ -0,0 +1,1668 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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" + +#define GL_INDEX_TYPE		GL_UNSIGNED_INT +typedef unsigned int glIndex_t; + +// 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 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; + +#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 + +	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; +} 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 + +// +// 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_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_stereo;				// desired pixelformat stereo flag +extern cvar_t	*r_texturebits;			// number of desired texture bits +										// 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_gamma; +extern cvar_t	*r_displayRefresh;		// optional display refresh option +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_gamma_control; +extern cvar_t	*r_ext_texenv_op; +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_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_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_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] ); + +void		GL_ResolveHardwareType( void ); + +/* +==================================================================== + +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 + +/* +============================================================= +============================================================= +*/ +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 enum { +	RC_END_OF_LIST, +	RC_SET_COLOR, +	RC_STRETCH_PIC, +	RC_DRAW_SURFS, +	RC_DRAW_BUFFER, +	RC_SWAP_BUFFERS, +	RC_SCREENSHOT, +	RC_VIDEOFRAME +} 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/src/renderer/tr_main.c b/src/renderer/tr_main.c new file mode 100644 index 0000000..bbe9423 --- /dev/null +++ b/src/renderer/tr_main.c @@ -0,0 +1,1351 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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 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_SetupProjection +=============== +*/ +void R_SetupProjection( void ) { +	float	xmin, xmax, ymin, ymax; +	float	width, height, depth; +	float	zNear, zFar; + +	// dynamically compute far clip plane distance +	SetFarClip(); + +	// +	// set up projection matrix +	// +	zNear	= r_znear->value; +	zFar	= tr.viewParms.zFar; + +	ymax = zNear * tan( tr.refdef.fov_y * M_PI / 360.0f ); +	ymin = -ymax; + +	xmax = zNear * tan( tr.refdef.fov_x * M_PI / 360.0f ); +	xmin = -xmax; + +	width = xmax - xmin; +	height = ymax - ymin; +	depth = zFar - zNear; + +	tr.viewParms.projectionMatrix[0] = 2 * zNear / width; +	tr.viewParms.projectionMatrix[4] = 0; +	tr.viewParms.projectionMatrix[8] = ( xmax + xmin ) / width;	// normally 0 +	tr.viewParms.projectionMatrix[12] = 0; + +	tr.viewParms.projectionMatrix[1] = 0; +	tr.viewParms.projectionMatrix[5] = 2 * zNear / height; +	tr.viewParms.projectionMatrix[9] = ( ymax + ymin ) / height;	// normally 0 +	tr.viewParms.projectionMatrix[13] = 0; + +	tr.viewParms.projectionMatrix[2] = 0; +	tr.viewParms.projectionMatrix[6] = 0; +	tr.viewParms.projectionMatrix[10] = -( zFar + zNear ) / depth; +	tr.viewParms.projectionMatrix[14] = -2 * zFar * zNear / depth; + +	tr.viewParms.projectionMatrix[3] = 0; +	tr.viewParms.projectionMatrix[7] = 0; +	tr.viewParms.projectionMatrix[11] = -1; +	tr.viewParms.projectionMatrix[15] = 0; +} + +/* +================= +R_SetupFrustum + +Setup that culling frustum planes for the current view +================= +*/ +void R_SetupFrustum (void) { +	int		i; +	float	xs, xc; +	float	ang; + +	ang = tr.viewParms.fovX / 180 * M_PI * 0.5f; +	xs = sin( ang ); +	xc = cos( ang ); + +	VectorScale( tr.viewParms.or.axis[0], xs, tr.viewParms.frustum[0].normal ); +	VectorMA( tr.viewParms.frustum[0].normal, xc, tr.viewParms.or.axis[1], tr.viewParms.frustum[0].normal ); + +	VectorScale( tr.viewParms.or.axis[0], xs, tr.viewParms.frustum[1].normal ); +	VectorMA( tr.viewParms.frustum[1].normal, -xc, tr.viewParms.or.axis[1], tr.viewParms.frustum[1].normal ); + +	ang = tr.viewParms.fovY / 180 * M_PI * 0.5f; +	xs = sin( ang ); +	xc = cos( ang ); + +	VectorScale( tr.viewParms.or.axis[0], xs, tr.viewParms.frustum[2].normal ); +	VectorMA( tr.viewParms.frustum[2].normal, xc, tr.viewParms.or.axis[2], tr.viewParms.frustum[2].normal ); + +	VectorScale( tr.viewParms.or.axis[0], xs, tr.viewParms.frustum[3].normal ); +	VectorMA( tr.viewParms.frustum[3].normal, -xc, tr.viewParms.or.axis[2], tr.viewParms.frustum[3].normal ); + +	for (i=0 ; i<4 ; i++) { +		tr.viewParms.frustum[i].type = PLANE_NON_AXIAL; +		tr.viewParms.frustum[i].dist = DotProduct (tr.viewParms.or.origin, tr.viewParms.frustum[i].normal); +		SetPlaneSignbits( &tr.viewParms.frustum[i] ); +	} +} + + +/* +================= +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 +	R_SetupProjection (); + +	R_AddEntitySurfaces (); +} + +/* +================ +R_DebugPolygon +================ +*/ +void R_DebugPolygon( int color, int numPoints, float *points ) { +	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 ); +} + +/* +==================== +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_SetupFrustum (); + +	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/src/renderer/tr_marks.c b/src/renderer/tr_marks.c new file mode 100644 index 0000000..2523ea7 --- /dev/null +++ b/src/renderer/tr_marks.c @@ -0,0 +1,444 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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) 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; +	srfSurfaceFace_t *surf; +	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) { + +			surf = ( srfSurfaceFace_t * ) surfaces[i]; +			// check the normal of this face +			if (DotProduct(surf->plane.normal, projectionDir) > -0.5) { +				continue; +			} + +			/* +			VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); +			VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); +			CrossProduct(v1, v2, normal); +			VectorNormalize(normal); +			if (DotProduct(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 +				} +			} +			continue; +		} +		else { +			// ignore all other world surfaces +			// might be cool to also project polygons on a triangle soup +			// however this will probably create huge amounts of extra polys +			// even more than the projection onto curves +			continue; +		} +	} +	return returnedFragments; +} + diff --git a/src/renderer/tr_mesh.c b/src/renderer/tr_mesh.c new file mode 100644 index 0000000..d35f5d1 --- /dev/null +++ b/src/renderer/tr_mesh.c @@ -0,0 +1,419 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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/src/renderer/tr_model.c b/src/renderer/tr_model.c new file mode 100644 index 0000000..8fdd0ad --- /dev/null +++ b/src/renderer/tr_model.c @@ -0,0 +1,1100 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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; +	unsigned	*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); +		if(!buf) +		{ +			ri.Printf (PRINT_WARNING,"RE_RegisterModel: couldn't load %s\n", name); +			mod->type = MOD_BAD; +			return 0; +		} +		 +		ident = LittleLong(*(unsigned *)buf); +		if(ident == MDR_IDENT) +			loaded = R_LoadMDR(mod, buf, filesize, name); + +		ri.FS_FreeFile (buf); +		 +		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, (void **)&buf ); +		if ( !buf ) { +			continue; +		} +		 +		loadmodel = mod; +		 +		ident = LittleLong(*(unsigned *)buf); +		if ( ident == MD4_IDENT ) { +			loaded = R_LoadMD4( mod, buf, 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, name ); +		} +		 +		ri.FS_FreeFile (buf); + +		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; +} + + + +/* +================= +R_LoadMDR +================= +*/ +#ifdef RAVENMD4 +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; + +	pinmodel->numFrames = LittleLong(pinmodel->numFrames); +	pinmodel->numBones = LittleLong(pinmodel->numBones); +	pinmodel->ofsFrames = LittleLong(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))); +	} +	 +	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 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++) +	{ +		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++) { +			// 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++) +			{ +				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 = LittleLong(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)); +			 +			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)); +	 +	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 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/src/renderer/tr_noise.c b/src/renderer/tr_noise.c new file mode 100644 index 0000000..c8eeeed --- /dev/null +++ b/src/renderer/tr_noise.c @@ -0,0 +1,96 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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; + +	srand( 1001 ); + +	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/src/renderer/tr_public.h b/src/renderer/tr_public.h new file mode 100644 index 0000000..e4e4d04 --- /dev/null +++ b/src/renderer/tr_public.h @@ -0,0 +1,171 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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	(*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/src/renderer/tr_scene.c b/src/renderer/tr_scene.c new file mode 100644 index 0000000..a45d8f8 --- /dev/null +++ b/src/renderer/tr_scene.c @@ -0,0 +1,410 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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; +	} +  // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=402 +	if ( r_numentities >= ENTITYNUM_WORLD ) { +		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; + +	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/src/renderer/tr_shade.c b/src/renderer/tr_shade.c new file mode 100644 index 0000000..0214083 --- /dev/null +++ b/src/renderer/tr_shade.c @@ -0,0 +1,1463 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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. +*/ + +/* +================ +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 +} + + +/* +============================================================= + +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 ); +	qglColor3f (1,1,1); + +	GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE ); +	qglDepthRange( 0, 0 ); + +	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" ); +	} +	qglDepthRange( 0, 1 ); +} + + +/* +================ +DrawNormals + +Draws vertex normals for debugging +================ +*/ +static void DrawNormals (shaderCommands_t *input) { +	int		i; +	vec3_t	temp; + +	GL_Bind( tr.whiteImage ); +	qglColor3f (1,1,1); +	qglDepthRange( 0, 0 );	// never occluded +	GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE ); + +	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 ); +} + +/* +============== +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 ); + +	// 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 ); +	} + +	// +	// 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]; +	unsigned	hitIndexes[SHADER_MAX_INDEXES]; +	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; + +		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 ) { +			int		a, b, c; + +			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]; +	unsigned	hitIndexes[SHADER_MAX_INDEXES]; +	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; + +		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 ) { +			int		a, b, c; + +			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; +		} +	} +} + +/* +=============== +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/src/renderer/tr_shade_calc.c b/src/renderer/tr_shade_calc.c new file mode 100644 index 0000000..ee2f6ec --- /dev/null +++ b/src/renderer/tr_shade_calc.c @@ -0,0 +1,1232 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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/src/renderer/tr_shader.c b/src/renderer/tr_shader.c new file mode 100644 index 0000000..10223a5 --- /dev/null +++ b/src/renderer/tr_shader.c @@ -0,0 +1,3047 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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 ); +			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] ); +#ifdef GL_CLAMP_TO_EDGE +			shader.sky.outerbox[i] = R_FindImageFile( ( char * ) pathname, qtrue, qtrue, GL_CLAMP_TO_EDGE ); +#else +			shader.sky.outerbox[i] = R_FindImageFile( ( char * ) pathname, qtrue, qtrue, GL_CLAMP ); +#endif +			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] == '{' ) +		{ +			// 20051019 misantropia -- fix buffer overrun. +			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; stage++ ) { +		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; +			continue; +		} + +		// +		// ditch this stage if it's detail and detail textures are disabled +		// +		if ( pStage->isDetail && !r_detailTextures->integer ) { +			if ( stage < ( MAX_SHADER_STAGES - 1 ) ) { +				memmove( pStage, pStage + 1, sizeof( *pStage ) * ( MAX_SHADER_STAGES - stage - 1 ) ); +				Com_Memset(  pStage + 1, 0, sizeof( *pStage ) ); +			} +			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; +				} +			} +		} +	} + +	// 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) ) {  // bk001205 +		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]; +	char		fileName[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; +	} + +	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 TGA, BMP, or PCX +	// +	Q_strncpyz( fileName, name, sizeof( fileName ) ); +	COM_DefaultExtension( fileName, sizeof( fileName ), ".tga" ); +	image = R_FindImageFile( fileName, mipRawImage, mipRawImage, mipRawImage ? GL_REPEAT : GL_CLAMP ); +	if ( !image ) { +		ri.Printf( PRINT_DEVELOPER, "Couldn't find image 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); + +	// 20051020 misantropia -- 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 ); // bk: FIXME name +		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 numShaders; +	int i; +	char *oldp, *token, *hashMem; +	int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash, size; + +	long sum = 0; +	// scan for shader files +	shaderFiles = ri.FS_ListFiles( "scripts", ".shader", &numShaders ); + +	if ( !shaderFiles || !numShaders ) +	{ +		ri.Printf( PRINT_WARNING, "WARNING: no shader files found\n" ); +		return; +	} + +	if ( numShaders > MAX_SHADER_FILES ) { +		numShaders = MAX_SHADER_FILES; +	} + +	// load and parse shader files +	for ( i = 0; i < numShaders; i++ ) +	{ +		char filename[MAX_QPATH]; + +		Com_sprintf( filename, sizeof( filename ), "scripts/%s", shaderFiles[i] ); +		ri.Printf( PRINT_ALL, "...loading '%s'\n", filename ); +		sum += ri.FS_ReadFile( filename, (void **)&buffers[i] ); +		if ( !buffers[i] ) { +			ri.Error( ERR_DROP, "Couldn't load %s", filename ); +		} +	} + +	// build single large buffer +	s_shaderText = ri.Hunk_Alloc( sum + numShaders*2, h_low ); + +	// free in reverse order, so the temp files are all dumped +	for ( i = numShaders - 1; i >= 0 ; i-- ) { +		strcat( s_shaderText, "\n" ); +		p = &s_shaderText[strlen(s_shaderText)]; +		strcat( s_shaderText, buffers[i] ); +		ri.FS_FreeFile( buffers[i] ); +		buffers[i] = p; +		COM_Compress(p); +	} + +	// free up memory +	ri.FS_FreeFileList( shaderFiles ); + +	Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); +	size = 0; +	// +	for ( i = 0; i < numShaders; i++ ) { +		// pointer to the first shader file +		p = buffers[i]; +		// look for label +		while ( 1 ) { +			token = COM_ParseExt( &p, qtrue ); +			if ( token[0] == 0 ) { +				break; +			} + +			hash = generateHashValue(token, MAX_SHADERTEXT_HASH); +			shaderTextHashTableSizes[hash]++; +			size++; +			SkipBracedSection(&p); +			// if we passed the pointer to the next shader file +			if ( i < numShaders - 1 ) { +				if ( p > buffers[i+1] ) { +					break; +				} +			} +		} +	} + +	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)); +	// +	for ( i = 0; i < numShaders; i++ ) { +		// pointer to the first shader file +		p = buffers[i]; +		// look for label +		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); +			// if we passed the pointer to the next shader file +			if ( i < numShaders - 1 ) { +				if ( p > buffers[i+1] ) { +					break; +				} +			} +		} +	} + +	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/src/renderer/tr_shadows.c b/src/renderer/tr_shadows.c new file mode 100644 index 0000000..19d4046 --- /dev/null +++ b/src/renderer/tr_shadows.c @@ -0,0 +1,342 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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 ) { +				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(); +				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; + +	// 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 ); +	qglColor3f( 0.2f, 0.2f, 0.2f ); + +	// don't write to the color buffer +	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( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); +} + + +/* +================= +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 ) { +	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 (); + +	qglColor3f( 0.6f, 0.6f, 0.6f ); +	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 ); + +	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); +	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/src/renderer/tr_sky.c b/src/renderer/tr_sky.c new file mode 100644 index 0000000..08c8c89 --- /dev/null +++ b/src/renderer/tr_sky.c @@ -0,0 +1,846 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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]; + +/* +=================================================================================== + +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; + +	GL_Bind( image ); + +	for ( t = mins[1]+HALF_SKY_SUBDIVISIONS; t < maxs[1]+HALF_SKY_SUBDIVISIONS; t++ ) +	{ +		qglBegin( GL_TRIANGLE_STRIP ); + +		for ( s = mins[0]+HALF_SKY_SUBDIVISIONS; s <= maxs[0]+HALF_SKY_SUBDIVISIONS; s++ ) +		{ +			qglTexCoord2fv( s_skyTexCoords[t][s] ); +			qglVertex3fv( s_skyPoints[t][s] ); + +			qglTexCoord2fv( s_skyTexCoords[t+1][s] ); +			qglVertex3fv( s_skyPoints[t+1][s] ); +		} + +		qglEnd(); +	} +} + +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 ) { +		qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight ); +		 +		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/src/renderer/tr_surface.c b/src/renderer/tr_surface.c new file mode 100644 index 0000000..2a291c8 --- /dev/null +++ b/src/renderer/tr_surface.c @@ -0,0 +1,1249 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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 +============= +*/ +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 +============= +*/ +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 +============== +*/ +void RB_SurfaceBeam( void )  +{ +#define NUM_BEAM_SEGS 6 +	refEntity_t *e; +	int	i; +	vec3_t perpvec; +	vec3_t direction, normalized_direction; +	vec3_t	start_points[NUM_BEAM_SEGS], end_points[NUM_BEAM_SEGS]; +	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++ ) +	{ +		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] ); +	} + +	GL_Bind( tr.whiteImage ); + +	GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + +	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(); +} + +//================================================================================ + +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 +*/ +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 +*/ +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 +*/ +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 +============= +*/ +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 +============== +*/ +void RB_SurfaceFace( srfSurfaceFace_t *surf ) { +	int			i; +	unsigned	*indices, *tessIndexes; +	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 +============= +*/ +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 +=================== +*/ +void RB_SurfaceAxis( void ) { +	GL_Bind( tr.whiteImage ); +	qglLineWidth( 3 ); +	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(); +	qglLineWidth( 1 ); +} + +//=========================================================================== + +/* +==================== +RB_SurfaceEntity + +Entities that have a single procedurally generated surface +==================== +*/ +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; +} + +void RB_SurfaceBad( surfaceType_t *surfType ) { +	ri.Printf( PRINT_ALL, "Bad surface tesselated.\n" ); +} + +void RB_SurfaceFlare(srfFlare_t *surf) +{ +	if (r_flares->integer) +		RB_AddFlare(surf, tess.fogNum, surf->origin, surf->color, surf->normal); +} + +void RB_SurfaceDisplayList( srfDisplayList_t *surf ) { +	// all apropriate state must be set in RB_BeginSurface +	// this isn't implemented yet... +	qglCallList( surf->listNum ); +} + +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/src/renderer/tr_types.h b/src/renderer/tr_types.h new file mode 100644 index 0000000..5aa08ae --- /dev/null +++ b/src/renderer/tr_types.h @@ -0,0 +1,239 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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			1		// allways have some light (viewmodel, some items) +#define	RF_THIRD_PERSON		2		// don't draw through eyes, only mirrors (player bodies, chat sprites) +#define	RF_FIRST_PERSON		4		// only draw through eyes (view weapon, damage blood blob) +#define	RF_DEPTHHACK		8		// for view weapon Z crunching +#define	RF_NOSHADOW			64		// don't add stencil shadows + +#define RF_LIGHTING_ORIGIN	128		// 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		256		// use refEntity->shadowPlane +#define	RF_WRAP_FRAMES		512		// mod the model frames by the maxframes to allow continuous +									// animation without needing to know the frame count + +// refdef flags +#define RDF_NOWORLDMODEL	1		// used for player configuration screen +#define RDF_HYPERSPACE		4		// 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 +} 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						maxActiveTextures;		// 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 + +	qboolean				textureFilterAnisotropic; +	int							maxAnisotropy; +                 +} glconfig_t; + +// FIXME: VM should be OS agnostic .. in theory + +/* +#ifdef Q3_VM + +#define _3DFX_DRIVER_NAME	"Voodoo" +#define OPENGL_DRIVER_NAME	"Default" + +#elif defined(_WIN32) +*/ + +#if defined(Q3_VM) || defined(_WIN32) + +#define _3DFX_DRIVER_NAME	"3dfxvgl" +#define OPENGL_DRIVER_NAME	"opengl32" + +#elif defined(MACOS_X) + +#define _3DFX_DRIVER_NAME	"libMesaVoodooGL.dylib" +#define OPENGL_DRIVER_NAME	"/System/Library/Frameworks/OpenGL.framework/Libraries/libGL.dylib" + +#else + +#define _3DFX_DRIVER_NAME	"libMesaVoodooGL.so" +// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=524 +#define OPENGL_DRIVER_NAME	"libGL.so.1" + +#endif	// !defined _WIN32 + +#endif	// __TR_TYPES_H diff --git a/src/renderer/tr_world.c b/src/renderer/tr_world.c new file mode 100644 index 0000000..c5158b2 --- /dev/null +++ b/src/renderer/tr_world.c @@ -0,0 +1,669 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; 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 ); +}  | 
