Portable Half-Life SDK. GoldSource and Xash3D. Crossplatform.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1037 lines
30 KiB

//
// Bump Mapping in Half-Life
//
// Original code copyright (C) Francis "DeathWish" Woodhouse, 2004.
//
// You are free to use this code in your own projects, so long as
// (a) The original author of this code is credited and
// (b) This header remains intact on both of the main source files
//
// I'd be interested to know if you do decide to use this in something, so I'd
// appreciate it if you drop me a line at deathwish@valve-erc.com if you do.
//
#include <windows.h>
#include <gl/gl.h>
#include <gl/glu.h>
#include <gl/glaux.h>
#include <gl/glext.h>
#include <cg/cg.h>
#include <cg/cgGL.h>
#include "hud.h"
#include "cl_util.h"
#include "cvardef.h"
#include "r_studioint.h"
#include "com_model.h"
#include "bumpmap.h"
extern globalvars_t* gpGlobals;
//
// CVARs AVAILABLE
//
// bm_enable 1/0 - enable/disable bumpmapping altogether
// bm_allflat 1/0 - don't use the supplied bumpmaps; render everything as if the surface is flat
//
#define MAX_STYLE_LEN 100
#define NUM_STYLES 12
// every 10th of a second. 'z' is max light, 'a' is darkness
char g_LightStyles[NUM_STYLES][MAX_STYLE_LEN+1] =
{
"z",
"mmnmmommommnonmmonqnmmo",
"abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba",
"mmmmmaaaaammmmmaaaaaabcdefgabcdefg",
"mamamamamama",
"jklmnopqrstuvwxyzyxwvutsrqponmlkj",
"nmonqnmomnmomomno",
"mmmaaaabcdefgmmmmaaaammmaamm",
"mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa",
"aaaaaaaazzzzzzzz",
"mmamammmmammamamaaamammma",
"abcdefghijklmnopqrrqponmlkjihgfedcba"
};
// we have to do this to stop the linker complaining, since we're using the evil GLaux
extern "C" long _ftol( double ); //defined by VC6 C libs
extern "C" long _ftol2( double dblSource ) { return _ftol( dblSource ); }
#define M_PI 3.141592653589793238
#define DEG2RAD(d) ((d) * (M_PI / 180))
inline float Clamp(float f, float low, float high)
{
if (f < low)
f = low;
if (f > high)
f = high;
return f;
}
inline Vector RotateX(Vector V, float angle) // angle in degrees
{
Vector ret;
angle = DEG2RAD(angle);
ret.x = V.x;
ret.y = V.y*cos(angle) + V.z*sin(angle);
ret.z = V.z*cos(angle) - V.y*sin(angle);
return ret;
}
inline Vector RotateY(Vector V, float angle) // angle in degrees
{
Vector ret;
angle = DEG2RAD(angle);
ret.x = V.x*cos(angle) - V.z*sin(angle);
ret.y = V.y;
ret.z = V.z*cos(angle) + V.x*sin(angle);
return ret;
}
inline Vector RotateZ(Vector V, float angle) // angle in degrees
{
Vector ret;
angle = DEG2RAD(angle);
ret.x = V.x*cos(angle) + V.y*sin(angle);
ret.y = V.y*cos(angle) - V.x*sin(angle);
ret.z = V.z;
return ret;
}
inline Vector vecNCMVector(int i, int x, int y)
{
float s = ((float)x + 0.5) / BUMP_NORM_CUBE_MAP_SIZE;
float t = ((float)y + 0.5) / BUMP_NORM_CUBE_MAP_SIZE;
float sc = s*2.0 - 1.0;
float tc = t*2.0 - 1.0;
Vector V;
switch (i)
{
case 0:
V.x = 1.0;
V.y = -tc;
V.z = -sc;
break;
case 1:
V.x = -1.0;
V.y = -tc;
V.z = sc;
break;
case 2:
V.x = sc;
V.y = 1.0;
V.z = tc;
break;
case 3:
V.x = sc;
V.y = -1.0;
V.z = -tc;
break;
case 4:
V.x = sc;
V.y = -tc;
V.z = 1.0;
break;
case 5:
V.x = -sc;
V.y = -tc;
V.z = -1.0;
break;
}
V = V.Normalize();
return V;
}
// Global instance of the bumpmap manager
CBumpmapMgr g_BumpmapMgr;
// OpenGL multitexture functions
PFNGLACTIVETEXTUREARBPROC glActiveTextureARB;
PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2fARB;
PFNGLMULTITEXCOORD3FVARBPROC glMultiTexCoord3fvARB;
// OpenGL 3D texture functions
PFNGLTEXIMAGE3DEXTPROC glTexImage3DEXT;
bool CBumpmapMgr::Initialise(void)
{
if (m_bInitialised)
Shutdown();
gEngfuncs.pfnRegisterVariable("bm_enable", "0", 0);
gEngfuncs.pfnRegisterVariable("bm_allflat", "0", 0);
m_bFailedInit = true;
if (!IsHardwareCapable())
return false;
if (!InitialiseOGLExtensions())
return false;
if (!CreateSceneTextures())
return false;
if (!LoadBumpTextures())
return false;
if (!InitialiseCg())
return false;
if (!LoadFragmentPrograms())
return false;
m_bInitialised = true;
m_bFailedInit = false;
return true;
}
bool CBumpmapMgr::IsHardwareCapable(void)
{
// Minimum hardware is a GeForce 3 or a Radeon 9500. I don't see the point in making bumpmapping run
// on GeForce 2 or lower - the cards don't have a fast enough fillrate and much texture memory anyway.
// grab the string saying which OpenGL extensions are available on this graphics card
std::string sExtensions;
sExtensions.assign((const char*)glGetString(GL_EXTENSIONS), strlen((const char*)glGetString(GL_EXTENSIONS)));
// check for GL_ARB_multitexture (why the hell are they trying to run this on a Voodoo 2, anyway?)
if (sExtensions.find("GL_ARB_multitexture") == -1)
{
MessageBox(NULL, "Bumpmapping error: Your hardware does not support GL_ARB_multitexture.", "Fatal Error", NULL);
return false;
}
// check for GL_ARB_texture_cube_map
if (sExtensions.find("GL_ARB_texture_cube_map") == -1)
{
MessageBox(NULL, "Bumpmapping error: Your hardware does not support GL_ARB_texture_cube_map.", "Fatal Error", NULL);
return false;
}
// check for GL_NV_texture_rectangle or GL_EXT_texture_rectangle
if (sExtensions.find("GL_NV_texture_rectangle") == -1 && sExtensions.find("GL_EXT_texture_rectangle") == -1)
{
MessageBox(NULL, "Bumpmapping error: Your hardware does not support GL_NV_texture_rectangle or GL_EXT_texture_rectangle.", "Fatal Error", NULL);
return false;
}
// check for GL_EXT_texture3D
if (sExtensions.find("GL_EXT_texture3D") == -1)
{
MessageBox(NULL, "Bumpmapping error: Your hardware does not support GL_EXT_texture3D.", "Fatal Error", NULL);
return false;
}
// see if we have the requisite number of texture units
int iTexUnits;
glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &iTexUnits);
if (iTexUnits < 4)
{
MessageBox(NULL, "Bumpmapping error: Your hardware does not have four texture units.", "Fatal Error", NULL);
return false;
}
// Check if fragment programs can be run (this'll cull the largest proportion of video cards)
// FP20 = GL_NV_register_combiners and GL_NV_texture_shader
// FP30 = GL_NV_fragment_program
// ARBFP1 = GL_ARB_fragment_program
// There's no GL_ATI_fragment_shader support because Cg doesn't include support for compiling to that target.
// You could write a straight shader in shader ASM, but it isn't really worth it for the few extra cards it'd add
// support for.
if (!cgGLIsProfileSupported(CG_PROFILE_FP20) && !cgGLIsProfileSupported(CG_PROFILE_FP30) && !cgGLIsProfileSupported(CG_PROFILE_ARBFP1))
{
MessageBox(NULL, "Bumpmapping error: Your hardware does not support GL_NV_register_combiners and GL_NV_texture_shader, GL_NV_fragment_program or GL_ARB_fragment_program.", "Fatal Error", NULL);
return false;
}
return true;
}
bool CBumpmapMgr::CreateSceneTextures(void)
{
// create the DIFFUSE scene texture
m_uiDiffuseScene = m_uiNextTexIdx++;
// create the LIGHTMAPPED scene texture
m_uiLightmapScene = m_uiNextTexIdx++;
// create the BUMPMAPPED scene texture
m_uiBumpScene = m_uiNextTexIdx++;
// create the NORMALISATION CUBE MAP
MakeNormCubeMap(&m_uiNormCubeMap);
// create the ATTENUATION MAP
MakeAttenuationMap(&m_uiAttenuationMap);
// create the NORMAL MAP for FLAT SURFACES
MakeFlatNormalMap(&m_uiFlatSurfaceNorm);
return true;
}
bool CBumpmapMgr::LoadBumpTextures(void)
{
char szFile[256];
char szTex[256];
char* pBumpFile;
std::string sMap(gEngfuncs.pfnGetLevelName());
sprintf(szFile, "%s/%s_bump.txt", gEngfuncs.pfnGetGameDirectory(), sMap.substr(0, sMap.rfind('.')).c_str());
FILE* pFile = fopen(szFile, "r");
if (!pFile)
{
// If we can't find the file, just use the flat bumpmap for everything in this map.
return true;
}
model_t* pWorld = gEngfuncs.GetEntityByIndex(0)->model; // worldspawn model
unsigned int uiTex;
char szTexPath[256];
memset(szTex, 0, 256);
// go through each line of the file
while (fgets(szTex, 256, pFile))
{
if (szTex[strlen(szTex)-1] == '\n')
szTex[strlen(szTex)-1] = '\0';
// set pBumpFile to point to the string after the space, and set the space to a null so that szTex only
// references the string before the space
pBumpFile = strchr(szTex, ' ');
*pBumpFile = '\0';
pBumpFile++;
// go through each texinfo in the map
for (int i = 0; i < pWorld->numtexinfo; i++)
{
// we found the texture
if (!_stricmp(pWorld->texinfo[i].texture->name, szTex))
{
sprintf(szTexPath, "%s/detail/%s", gEngfuncs.pfnGetGameDirectory(), pBumpFile);
// load up the specified normal map
if (LoadTexture(szTexPath, &uiTex))
{
// shove it into the map of diffuse texture ID to normal map texture ID
m_aNormalMaps.insert(std::make_pair(pWorld->texinfo[i].texture->gl_texturenum, uiTex));
break;
}
}
}
memset(szTex, 0, 256);
}
fclose(pFile);
return true;
}
bool CBumpmapMgr::InitialiseOGLExtensions(void)
{
// MULTITEXTURE EXTENSIONS
// Used for... well, multitexturing.
glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");
if (!glActiveTextureARB)
{
MessageBox(NULL, "Bumpmapping error: Could not acquire pointer to glActiveTextureARB.", "Fatal Error", NULL);
return false;
}
glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress("glMultiTexCoord2fARB");
if (!glMultiTexCoord2fARB)
{
MessageBox(NULL, "Bumpmapping error: Could not acquire pointer to glMultiTexCoord2fARB.", "Fatal Error", NULL);
return false;
}
glMultiTexCoord3fvARB = (PFNGLMULTITEXCOORD3FVARBPROC)wglGetProcAddress("glMultiTexCoord3fvARB");
if (!glMultiTexCoord3fvARB)
{
MessageBox(NULL, "Bumpmapping error: Could not acquire pointer to glMultiTexCoord3fvARB.", "Fatal Error", NULL);
return false;
}
// 3D TEXTURE EXTENSIONS
// A 3D texture is used for the attenuation map.
glTexImage3DEXT = (PFNGLTEXIMAGE3DEXTPROC)wglGetProcAddress("glTexImage3DEXT");
if (!glTexImage3DEXT)
{
MessageBox(NULL, "Bumpmapping error: Could not acquire pointer to glTexImage3DEXT.", "Fatal Error", NULL);
return false;
}
return true;
}
bool CBumpmapMgr::InitialiseCg(void)
{
// create a Cg context
m_cg_Context = cgCreateContext();
if (!m_cg_Context)
{
MessageBox(NULL, "Bumpmapping error: Could not create Cg context!", "Fatal Error", NULL);
return false;
}
// get the best vertex and fragment program profiles
m_cg_vertProfile = cgGLGetLatestProfile(CG_GL_VERTEX);
m_cg_fragProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT);
return true;
}
bool CBumpmapMgr::LoadFragmentPrograms(void)
{
// fullbright map
if (!LoadCgProgram(&m_fp_Diffuse, m_cg_fragProfile, "render/fp_diffuse.cg"))
{
MessageBox(NULL, "Bumpmapping error: Could not load fp_diffuse.cg!", "Fatal Error", NULL);
return false;
}
// map with only lightmaps
if (!LoadCgProgram(&m_fp_Lightmaps, m_cg_fragProfile, "render/fp_lightmaps.cg"))
{
MessageBox(NULL, "Bumpmapping error: Could not load fp_lightmaps.cg!", "Fatal Error", NULL);
return false;
}
// bumpmapping
if (!LoadCgProgram(&m_fp_Bump, m_cg_fragProfile, "render/fp_bump.cg"))
{
MessageBox(NULL, "Bumpmapping error: Could not load fp_bump.cg!", "Fatal Error", NULL);
return false;
}
// fullbright model
if (!LoadCgProgram(&m_fp_ModelPass0, m_cg_fragProfile, "render/fp_model_p0.cg"))
{
MessageBox(NULL, "Bumpmapping error: Could not load fp_model_p0.cg!", "Fatal Error", NULL);
return false;
}
// model with diffuse colour
if (!LoadCgProgram(&m_fp_ModelPass1, m_cg_fragProfile, "render/fp_model_p1.cg"))
{
MessageBox(NULL, "Bumpmapping error: Could not load fp_model_p1.cg!", "Fatal Error", NULL);
return false;
}
m_parm_BumpLightColour = cgGetNamedParameter(m_fp_Bump, "LightColour");
m_parm_BumpLightStrength = cgGetNamedParameter(m_fp_Bump, "LightStrength");
return true;
}
bool CBumpmapMgr::LoadCgProgram(CGprogram* pDest, CGprofile eProfile, const char* szFile)
{
// map the path
char file[512];
sprintf(file, "%s/%s", gEngfuncs.pfnGetGameDirectory(), szFile);
// load the program
*pDest = cgCreateProgramFromFile(m_cg_Context, CG_SOURCE, file, eProfile, "main", NULL);
if (!(*pDest))
return false;
cgGLLoadProgram(*pDest);
return true;
}
bool CBumpmapMgr::LoadTexture(const char* szFile, unsigned int* pTexIdx)
{
// We're using GLaux for this, because I can't be bothered to code a proper bitmap loading function.
// It works, so no complaining.
// load the image
AUX_RGBImageRec* pImg = auxDIBImageLoad(szFile);
if (!pImg)
return false;
// assign a texture ID and set the various parameters
*pTexIdx = m_uiNextTexIdx++;
glBindTexture(GL_TEXTURE_2D, *pTexIdx);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// load the texture, generating mipmaps for it at the same time
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, pImg->sizeX, pImg->sizeY, GL_RGB, GL_UNSIGNED_BYTE, pImg->data);
free(pImg->data);
free(pImg);
return true;
}
void CBumpmapMgr::Shutdown(void)
{
cgDestroyProgram(m_fp_Diffuse);
cgDestroyProgram(m_fp_Lightmaps);
cgDestroyProgram(m_fp_Bump);
cgDestroyProgram(m_fp_ModelPass0);
cgDestroyProgram(m_fp_ModelPass1);
cgDestroyContext(m_cg_Context);
Reset();
}
void CBumpmapMgr::Reset(void)
{
m_iNumFrameLights = 0;
m_bInitialised = false;
m_bFailedInit = false;
m_iLargestVisFrame = 0;
m_aBumpedScenes.clear();
m_aNormalMaps.clear();
}
void CBumpmapMgr::Render(int pass)
{
if (!m_bInitialised && !m_bFailedInit)
Initialise();
if (CVAR_GET_FLOAT("bm_enable") == 0 || g_BumpmapMgr.m_bFailedInit)
return;
int i;
// We must clear to black to enable non-bumpmapped items to be re-combined with the scene properly,
// and also to enable us to just render the bump mapping with additive blending for every light.
glClearColor(0,0,0,1);
m_iCurPass = pass;
switch (pass)
{
case 0:
// Render the scene with only the DIFFUSE COLOUR (fullbright)
cgGLEnableProfile(m_cg_fragProfile);
cgGLBindProgram(m_fp_Diffuse);
break;
case 1:
// In pass 0, we rendered the scene with just the diffuse texture. Grab this to a texture.
glBindTexture(GL_TEXTURE_RECTANGLE_NV, m_uiDiffuseScene);
glCopyTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_RGB, 0, 0, ScreenWidth, ScreenHeight, 0);
// Here is the best time to render the bump-mapped scenes. We make use of the existing z-buffer data
// to (a) stop us having to update the z-buffer and (b) to create black "holes" where items
// such as models and brush entities go.
glClear(GL_COLOR_BUFFER_BIT); // clear the colour buffer, but NOT the depth buffer
glDepthMask(GL_FALSE); // disable modification of the depth buffer
glDepthFunc(GL_LEQUAL);
// Each light adds its contribution to the rest
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
for (i = 0; i < m_aBumpedScenes.size(); i++)
{
RenderLight(i);
}
glDisable(GL_BLEND);
glDepthMask(GL_TRUE); // re-enable modification of the depth buffer
glDepthFunc(GL_LESS);
glEnable(GL_TEXTURE_RECTANGLE_NV);
glBindTexture(GL_TEXTURE_RECTANGLE_NV, m_uiBumpScene);
glCopyTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_RGB, 0, 0, ScreenWidth, ScreenHeight, 0);
glDisable(GL_TEXTURE_RECTANGLE_NV);
glClear(GL_COLOR_BUFFER_BIT);
// We now render the scene with just the LIGHTMAPS.
cgGLBindProgram(m_fp_Lightmaps);
break;
case 2:
// In pass 1, we rendered the scene with just the lightmaps. Grab this to a texture.
glBindTexture(GL_TEXTURE_RECTANGLE_NV, m_uiLightmapScene);
glCopyTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_RGB, 0, 0, ScreenWidth, ScreenHeight, 0);
// Fragment programs aren't needed any more.
cgGLDisableProfile(m_cg_fragProfile);
// Switch to an orthogonal projection to render full-screen quads and combine all the scenes
// together to give the final result.
DoOrthoProjection(true);
CombineScenes();
DoOrthoProjection(false);
break;
default:
gEngfuncs.Con_Printf("BUMPMAPPING: Unknown pass number %i passed to Render\n", pass);
}
}
void CBumpmapMgr::RenderLight(int light)
{
// get a ptr to the light in question
bumplight_t* pLight = &m_aBumpedScenes[light];
if (!pLight->enabled)
return;
// calculate the movewith
if (pLight->moveWithEnt)
{
// first, adjust the light's position by any change that the movewith entity has undergone
pLight->pos = pLight->origPos + (pLight->moveWithEnt->origin - pLight->entOrigPos);
// rotate the light around the movewith entity by any change in its angles
Vector anglesChange = pLight->moveWithEnt->angles - pLight->entOrigAngles;
Vector newPos = pLight->pos - pLight->moveWithEnt->origin;
newPos = RotateZ(newPos, -anglesChange.y);
newPos = RotateY(newPos, -anglesChange.x);
newPos = RotateX(newPos, -anglesChange.z);
newPos = newPos + pLight->moveWithEnt->origin;
pLight->pos = newPos;
}
float styleStrength = (float)(g_LightStyles[pLight->style][(int)(gpGlobals->time * 10) % strlen(g_LightStyles[pLight->style])] - 'a') / ('z' - 'a');
if (styleStrength == 0)
return;
// bind the right fragment program and set the parameters
cgGLBindProgram(m_fp_Bump);
cgGLSetParameter1f(m_parm_BumpLightStrength, pLight->strength * styleStrength);
cgGLSetParameter3fv(m_parm_BumpLightColour, pLight->colour);
// bind the normalisation cube map to tex unit 1
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_RECTANGLE_NV);
glDisable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, m_uiNormCubeMap);
// bind the attenuation map to tex unit 2
glActiveTextureARB(GL_TEXTURE2_ARB);
glEnable(GL_TEXTURE_3D_EXT);
glBindTexture(GL_TEXTURE_3D_EXT, m_uiAttenuationMap);
// normal maps will be bound to tex unit 0
glActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_TEXTURE_RECTANGLE_NV);
glEnable(GL_TEXTURE_2D);
// create a bunch of variables
model_t* pWorld = gEngfuncs.GetEntityByIndex(0)->model; // worldspawn model
msurface_t** pSurf = pWorld->marksurfaces; // ptr to array of surfaces
glpoly_t* pPoly;
Vector N, T, B, L, L2, V, V1, V2, V3, E1, E2, AttenCoords;
TexNormalMaps_It texID;
glShadeModel(GL_SMOOTH);
// go through each surface
for (int i = 0; i < pWorld->nummarksurfaces; i++)
{
// This is a semi-hack to use the pre-calculated visibilty info. We don't have access
// to what the current visframe is, but we can assume that the surfaces with the highest
// visframe are those which are currently being rendered. If the visframe changes, then
// there might be more overdraw for one frame, but this isn't noticeable.
//
// To stop surfaces being rendered twice (which is disastrous, since we are using additive
// blending) we set the visframe to the negative value of itself once we've rendered the surface.
// If we then find a surface that has a negative visframe, we know that we've already
// rendered that one.
if (pSurf[i]->visframe > 0 && pSurf[i]->visframe >= m_iLargestVisFrame)
{
m_iLargestVisFrame = pSurf[i]->visframe;
pPoly = pSurf[i]->polys;
float* vert = pPoly->verts[0];
// find the normal map corresponding to this surface's texture (use the flat map if there isn't one)
texID = m_aNormalMaps.find(pSurf[i]->texinfo->texture->gl_texturenum);
if (CVAR_GET_FLOAT("bm_allflat") == 1 || texID == m_aNormalMaps.end())
glBindTexture(GL_TEXTURE_2D, m_uiFlatSurfaceNorm);
else
glBindTexture(GL_TEXTURE_2D, texID->second);
// tangent and binormal simply come from the UV vectors
T = pSurf[i]->texinfo->vecs[0];
B = pSurf[i]->texinfo->vecs[1];
T = T.Normalize();
B = B.Normalize();
// We must now calculate the normal of the surface. We can't cross-product the tangent and binormal,
// as we can't guarantee which way the normal will be facing (textures can be mirrored both ways).
// So, we must calculate it by doing the cross product of two edge vectors. However, we can't just
// choose any old edge vectors, since some turn out to be colinear, which gives bogus normals.
// The solution is to go through each vertex until we find two edge vectors that aren't colinear.
V1 = Vector(vert[0], vert[1], vert[2]);
V2 = Vector(vert[VERTEXSIZE], vert[VERTEXSIZE+1], vert[VERTEXSIZE+2]);
E1 = (V1 - V2).Normalize();
bool bFound = false;
for (int j = 2; j < pPoly->numverts; j++)
{
V3 = Vector(vert[VERTEXSIZE*j], vert[VERTEXSIZE*j + 1], vert[VERTEXSIZE*j + 2]);
E2 = (V1 - V3).Normalize();
if (fabs(fabs(DotProduct(E1, E2))-1) > 0.01f) // vectors aren't colinear (dp 1 = equal, dp -1 = opposite) - check for > epsilon to account for FP error
{
N = -1 * CrossProduct(E1, E2).Normalize(); // we can use these edge vectors to calculate the normal
bFound = true;
break;
}
}
if (!bFound)
N = -1 * CrossProduct(E1, (V1 - Vector(vert + VERTEXSIZE*2)).Normalize()).Normalize();
// render this surface
glBegin(GL_POLYGON);
for (int v = 0; v < pPoly->numverts; v++, vert+=VERTEXSIZE)
{
// Since we can't use a vertex program, we have to calculate the light vector here. We first
// calculate the light vector in world space, and then transform it into tangent space
// by doing the equivalent of multiplying it by the TBN matrix, and finally scale it into the range [0,1].
V = Vector(vert);
L = pLight->pos - V;
L2.x = DotProduct(T, L)*0.5 + 0.5;
L2.y = -DotProduct(B, L)*0.5 + 0.5; // for some reason, the y component of the light vector must be flipped - probably a by-product of the normal maps being flipped on loading
L2.z = DotProduct(N, L)*0.5 + 0.5;
// We also have to calculate the coordinates in the attenuation texture
AttenCoords = (L / (pLight->radius * 2)) + Vector(0.5,0.5,0.5);
// send the data to OGL
glTexCoord2f(vert[3], 1 - vert[4]); // the normal maps get flipped on loading, for some reason - reverse that here
glMultiTexCoord3fvARB(GL_TEXTURE1_ARB, L2);
glMultiTexCoord3fvARB(GL_TEXTURE2_ARB, AttenCoords);
glVertex3fv(vert);
}
glEnd();
}
// set the negative visframe to say we've rendered this surface
if (pSurf[i]->visframe > 0)
pSurf[i]->visframe = -pSurf[i]->visframe;
}
// go back and make all visframes positive again, so that HL can render the map properly
for (i = 0; i < pWorld->nummarksurfaces; i++)
{
if (pSurf[i]->visframe < 0)
pSurf[i]->visframe = -pSurf[i]->visframe;
}
glActiveTextureARB(GL_TEXTURE2_ARB);
glDisable(GL_TEXTURE_3D_EXT);
glActiveTextureARB(GL_TEXTURE0_ARB);
}
void CBumpmapMgr::CombineScenes(void)
{
// find out how many texture units we've got to mess with
int iTexUnits;
glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &iTexUnits);
// Add together the lightmap and the bump mapping, and modulate this by the textures
for (int i = 0; i < iTexUnits; i++)
{
glActiveTextureARB(GL_TEXTURE0_ARB + i);
glDisable(GL_TEXTURE_RECTANGLE_NV);
glDisable(GL_TEXTURE_2D);
}
glColor4f(1,1,1,1);
glActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_TEXTURE_2D);
glEnable(GL_TEXTURE_RECTANGLE_NV);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glBindTexture(GL_TEXTURE_RECTANGLE_NV, m_uiLightmapScene);
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_2D);
glEnable(GL_TEXTURE_RECTANGLE_NV);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);
glBindTexture(GL_TEXTURE_RECTANGLE_NV, m_uiBumpScene);
glActiveTextureARB(GL_TEXTURE2_ARB);
glDisable(GL_TEXTURE_2D);
glEnable(GL_TEXTURE_RECTANGLE_NV);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glBindTexture(GL_TEXTURE_RECTANGLE_NV, m_uiDiffuseScene);
RenderScreenQuad((float)ScreenWidth, (float)ScreenHeight, iTexUnits);
// Clean up - de-activate texturing on all units (except the first one, on which
// we enable regular 2D texturing, since HL doesn't do this...)
for (i = 0; i < iTexUnits; i++)
{
glActiveTextureARB(GL_TEXTURE0_ARB + i);
glDisable(GL_TEXTURE_RECTANGLE_NV);
}
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
}
void CBumpmapMgr::RenderScreenQuad(float texU, float texV, int iNumTexUnits)
{
int i;
glBegin(GL_QUADS);
for (i = 0; i < iNumTexUnits; i++) glMultiTexCoord2fARB(GL_TEXTURE0_ARB + i, 0, texV);
glVertex2f(0,0);
for (i = 0; i < iNumTexUnits; i++) glMultiTexCoord2fARB(GL_TEXTURE0_ARB + i, texU, texV);
glVertex2f(1,0);
for (i = 0; i < iNumTexUnits; i++) glMultiTexCoord2fARB(GL_TEXTURE0_ARB + i, texU, 0);
glVertex2f(1,1);
for (i = 0; i < iNumTexUnits; i++) glMultiTexCoord2fARB(GL_TEXTURE0_ARB + i, 0, 0);
glVertex2f(0,1);
glEnd();
}
void CBumpmapMgr::DoOrthoProjection(bool activate)
{
if (activate)
{
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(0, 1, 1, 0, -1, 1);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
}
else
{
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
}
}
void CBumpmapMgr::AddLight(const char* targetname, Vector pos, Vector colour, float strength, float radius,
bool enabled /*= true*/, int style /*= 0*/, int moveWithEntID /*= -1*/, bool moveWithExtraInfo /*= false*/,
Vector moveWithEntPos, Vector moveWithEntAngles)
{
bumplight_t light;
light.enabled = enabled;
light.pos = pos;
light.origPos = pos;
light.colour = colour;
light.strength = strength;
light.radius = radius;
light.style = (style >= NUM_STYLES ? 0 : style);
memset(light.targetname, 0, 64);
memcpy(light.targetname, targetname, strlen(targetname));
if (moveWithEntID == -1)
light.moveWithEnt = NULL;
else
{
light.moveWithEnt = gEngfuncs.GetEntityByIndex(moveWithEntID);
if (light.moveWithEnt != NULL)
{
if (moveWithExtraInfo)
{
light.entOrigPos = moveWithEntPos;
light.entOrigAngles = moveWithEntAngles;
}
else
{
light.entOrigPos = light.moveWithEnt->origin;
light.entOrigAngles = light.moveWithEnt->angles;
}
}
}
m_aBumpedScenes.push_back(light);
m_iNumFrameLights++;
}
void CBumpmapMgr::EnableLight(const char* targetname, bool enable)
{
for (BumpSceneList_It it = m_aBumpedScenes.begin(); it != m_aBumpedScenes.end(); ++it)
{
if (!_stricmp(targetname, it->targetname))
it->enabled = enable;
}
}
void CBumpmapMgr::RenderStudioModel(bool bPreRender)
{
if (CVAR_GET_FLOAT("bm_enable") == 0 || g_BumpmapMgr.m_bFailedInit)
return;
// bPreRender is true before rendering a studio model, false afterwards
if (bPreRender)
{
if (m_iCurPass == 0)
cgGLBindProgram(m_fp_ModelPass0);
else
cgGLBindProgram(m_fp_ModelPass1);
}
else
{
if (m_iCurPass == 0)
cgGLBindProgram(m_fp_Diffuse);
else
cgGLBindProgram(m_fp_Lightmaps);
}
}
void CBumpmapMgr::MakeNormCubeMap(unsigned int* pTexIdx)
{
unsigned char* pTex = new unsigned char[BUMP_NORM_CUBE_MAP_SIZE*BUMP_NORM_CUBE_MAP_SIZE*3];
*pTexIdx = m_uiNextTexIdx++;
// set the texture's parameters
glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, *pTexIdx);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP);
Vector V;
// For each face of the cube map, generate a flat texture that gives a normalised version of the
// vector used to access that texel in the cube texture.
for (int i = 0; i < 6; i++)
{
for (int y = 0; y < BUMP_NORM_CUBE_MAP_SIZE; y++)
{
for (int x = 0; x < BUMP_NORM_CUBE_MAP_SIZE; x++)
{
V = vecNCMVector(i, x, y);
pTex[(x + y*BUMP_NORM_CUBE_MAP_SIZE)*3] = (unsigned char)((V.x * 0.5 + 0.5)*255);
pTex[(x + y*BUMP_NORM_CUBE_MAP_SIZE)*3+1] = (unsigned char)((V.y * 0.5 + 0.5)*255);
pTex[(x + y*BUMP_NORM_CUBE_MAP_SIZE)*3+2] = (unsigned char)((V.z * 0.5 + 0.5)*255);
}
}
// store this face's image
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + i, 0, 3, BUMP_NORM_CUBE_MAP_SIZE, BUMP_NORM_CUBE_MAP_SIZE,
0, GL_RGB, GL_UNSIGNED_BYTE, pTex);
}
delete[] pTex;
}
void CBumpmapMgr::MakeAttenuationMap(unsigned int* pTexIdx)
{
unsigned char* pTex = new unsigned char[BUMP_ATTENUATION_MAP_SIZE*BUMP_ATTENUATION_MAP_SIZE*BUMP_ATTENUATION_MAP_SIZE];
*pTexIdx = m_uiNextTexIdx++;
glBindTexture(GL_TEXTURE_3D_EXT, *pTexIdx);
glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_R_EXT, GL_CLAMP);
glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP);
Vector V;
// For each texel in the attenuation map, calculate x^2 + y^2 + z^2 and store it, clamping to [0,1].
for (int z = 0; z < BUMP_ATTENUATION_MAP_SIZE; z++)
{
for (int y = 0; y < BUMP_ATTENUATION_MAP_SIZE; y++)
{
for (int x = 0; x < BUMP_ATTENUATION_MAP_SIZE; x++)
{
V = Vector(((float)x / BUMP_ATTENUATION_MAP_SIZE) * 2 - 1,
((float)y / BUMP_ATTENUATION_MAP_SIZE) * 2 - 1,
((float)z / BUMP_ATTENUATION_MAP_SIZE) * 2 - 1) * 1.05;
pTex[x + y*BUMP_ATTENUATION_MAP_SIZE + z*BUMP_ATTENUATION_MAP_SIZE*BUMP_ATTENUATION_MAP_SIZE] = (unsigned char)Clamp((V.x*V.x + V.y*V.y + V.z*V.z) * 255, 0, 255);
}
}
}
// load the texture data
glTexImage3DEXT(GL_TEXTURE_3D_EXT, 0, GL_RGB, BUMP_ATTENUATION_MAP_SIZE, BUMP_ATTENUATION_MAP_SIZE, BUMP_ATTENUATION_MAP_SIZE,
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, pTex);
delete[] pTex;
}
void CBumpmapMgr::MakeFlatNormalMap(unsigned int* pTexIdx)
{
*pTexIdx = m_uiNextTexIdx++;
glBindTexture(GL_TEXTURE_2D, *pTexIdx);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
unsigned char pTex[1*1*3]; // it doesn't need any clarity, seeing as it's all one colour - just make it 1x1
pTex[0] = 128; // unpacked, this gives 0
pTex[1] = 128; // unpacked, this gives 0
pTex[2] = 255; // unpacked, this gives 1
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, pTex);
}