This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/vid_gl/r_sky.c

657 lines
15 KiB
C

/*
Copyright (C) 1999 Stephen C. Taylor
Copyright (C) 2002-2007 Victor Luchits
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// r_sky.c
#include "r_local.h"
#include "mathlib.h"
#include "matrix_lib.h"
#define MAX_CLIP_VERTS 64
#define SIDE_SIZE 9
#define POINTS_LEN ( SIDE_SIZE * SIDE_SIZE )
#define ELEM_LEN (( SIDE_SIZE-1 ) * ( SIDE_SIZE-1 ) * 6 )
#define SPHERE_RAD 10.0f
#define EYE_RAD 9.0f
#define SCALE_S 4.0f // arbitrary (?) texture scaling factors
#define SCALE_T 4.0f
#define ST_MIN 1.0f / 512f
#define ST_MAX 511.0f / 512f
#define BOX_SIZE 1.0f
#define BOX_STEP BOX_SIZE / ( SIDE_SIZE-1 ) * 2.0f
elem_t r_skydome_elems[6][ELEM_LEN];
meshbuffer_t r_skydome_mbuffer;
static mfog_t *r_skyfog;
static msurface_t *r_warpface;
static bool r_warpfacevis;
static void Gen_BoxSide( skydome_t *skydome, int side, vec3_t orig, vec3_t drow, vec3_t dcol, float skyheight );
static void MakeSkyVec( float x, float y, float z, int axis, vec3_t v );
static void Gen_Box( skydome_t *skydome, float skyheight );
/*
==============
R_CreateSkydome
==============
*/
skydome_t *R_CreateSkydome( byte *mempool, float skyheight, ref_shader_t **farboxShaders, ref_shader_t **nearboxShaders )
{
int i, size;
mesh_t *mesh;
skydome_t *skydome;
byte *buffer;
size = sizeof( skydome_t ) + sizeof( mesh_t ) * 6 + sizeof( vec4_t ) * POINTS_LEN * 6 +
sizeof( vec4_t ) * POINTS_LEN * 6 + sizeof( vec2_t ) * POINTS_LEN * 12;
buffer = Mem_Alloc( mempool, size );
skydome = ( skydome_t * )buffer;
Mem_Copy( skydome->farboxShaders, farboxShaders, sizeof( ref_shader_t* ) * 6 );
Mem_Copy( skydome->nearboxShaders, nearboxShaders, sizeof( ref_shader_t* ) * 6 );
buffer += sizeof( skydome_t );
skydome->meshes = ( mesh_t * )buffer;
buffer += sizeof( mesh_t ) * 6;
for( i = 0, mesh = skydome->meshes; i < 6; i++, mesh++ )
{
mesh->numVertexes = POINTS_LEN;
mesh->xyzArray = ( vec4_t * )buffer; buffer += sizeof( vec4_t ) * POINTS_LEN;
mesh->normalsArray = ( vec4_t * )buffer; buffer += sizeof( vec4_t ) * POINTS_LEN;
skydome->sphereStCoords[i] = ( vec2_t * )buffer; buffer += sizeof( vec2_t ) * POINTS_LEN;
skydome->linearStCoords[i] = ( vec2_t * )buffer; buffer += sizeof( vec2_t ) * POINTS_LEN;
mesh->numElems = ELEM_LEN;
mesh->elems = r_skydome_elems[i];
}
Gen_Box( skydome, skyheight );
return skydome;
}
/*
==============
R_FreeSkydome
==============
*/
void R_FreeSkydome( skydome_t *skydome )
{
if( skydome ) Mem_Free( skydome );
}
/*
==============
Gen_Box
==============
*/
static void Gen_Box( skydome_t *skydome, float skyheight )
{
int axis;
vec3_t orig, drow, dcol;
for( axis = 0; axis < 6; axis++ )
{
MakeSkyVec( -BOX_SIZE, -BOX_SIZE, BOX_SIZE, axis, orig );
MakeSkyVec( 0, BOX_STEP, 0, axis, drow );
MakeSkyVec( BOX_STEP, 0, 0, axis, dcol );
Gen_BoxSide( skydome, axis, orig, drow, dcol, skyheight );
}
}
/*
================
Gen_BoxSide
I don't know exactly what Q3A does for skybox texturing, but
this is at least fairly close. We tile the texture onto the
inside of a large sphere, and put the camera near the top of
the sphere. We place the box around the camera, and cast rays
through the box verts to the sphere to find the texture coordinates.
================
*/
static void Gen_BoxSide( skydome_t *skydome, int side, vec3_t orig, vec3_t drow, vec3_t dcol, float skyheight )
{
vec3_t pos, w, row, norm;
float *v, *n, *st, *st2;
float t, d, d2, b, b2, q[2], s;
int r, c;
s = 1.0 / ( SIDE_SIZE-1 );
d = EYE_RAD; // sphere center to camera distance
d2 = d * d;
b = SPHERE_RAD; // sphere radius
b2 = b * b;
q[0] = 1.0 / ( 2.0 * SCALE_S );
q[1] = 1.0 / ( 2.0 * SCALE_T );
v = skydome->meshes[side].xyzArray[0];
n = skydome->meshes[side].normalsArray[0];
st = skydome->sphereStCoords[side][0];
st2 = skydome->linearStCoords[side][0];
VectorCopy( orig, row );
VectorClear( norm );
for( r = 0; r < SIDE_SIZE; r++ )
{
VectorCopy( row, pos );
for( c = 0; c < SIDE_SIZE; c++ )
{
// pos points from eye to vertex on box
VectorScale( pos, skyheight, v );
VectorCopy( pos, w );
// Normalize pos -> w
VectorNormalize( w );
// Find distance along w to sphere
t = sqrt( d2 * ( w[2] * w[2] - 1.0 ) + b2 ) - d * w[2];
w[0] *= t;
w[1] *= t;
// use x and y on sphere as s and t
// minus is here so skies scoll in correct (Q3A's) direction
st[0] = -w[0] * q[0];
st[1] = -w[1] * q[1];
// avoid bilerp seam
st[0] = ( bound( -1, st[0], 1 ) + 1.0 ) * 0.5;
st[1] = ( bound( -1, st[1], 1 ) + 1.0 ) * 0.5;
st2[0] = c * s;
st2[1] = 1.0 - r * s;
VectorAdd( pos, dcol, pos );
VectorCopy( norm, n );
v += 4;
n += 4;
st += 2;
st2 += 2;
}
VectorAdd( row, drow, row );
}
}
/*
==============
R_DrawSkySide
==============
*/
static void R_DrawSkySide( skydome_t *skydome, int side, ref_shader_t *shader, int features )
{
meshbuffer_t *mbuffer = &r_skydome_mbuffer;
if( RI.skyMins[0][side] >= RI.skyMaxs[0][side] || RI.skyMins[1][side] >= RI.skyMaxs[1][side] )
return;
mbuffer->shaderkey = shader->sortkey;
mbuffer->dlightbits = 0;
mbuffer->sortkey = MB_FOG2NUM( r_skyfog );
skydome->meshes[side].stArray = skydome->linearStCoords[side];
R_PushMesh( &skydome->meshes[side], features );
R_RenderMeshBuffer( mbuffer );
}
/*
==============
R_DrawSkyBox
==============
*/
static void R_DrawSkyBox( skydome_t *skydome, ref_shader_t **shaders )
{
int i, features;
const int skytexorder[6] = { SKYBOX_RIGHT, SKYBOX_FRONT, SKYBOX_LEFT, SKYBOX_BACK, SKYBOX_TOP, SKYBOX_BOTTOM };
features = shaders[0]->features;
if( r_shownormals->integer )
features |= MF_NORMALS;
for( i = 0; i < 6; i++ )
R_DrawSkySide( skydome, i, shaders[skytexorder[i]], features );
}
/*
==============
R_DrawBlackBottom
Draw dummy skybox side to prevent the HOM effect
==============
*/
static void R_DrawBlackBottom( skydome_t *skydome )
{
int features;
features = tr.defaultShader->features;
if( r_shownormals->integer )
features |= MF_NORMALS;
R_DrawSkySide( skydome, 5, tr.defaultShader, features );
}
/*
==============
R_DrawSky
==============
*/
void R_DrawSky( ref_shader_t *shader )
{
int i;
vec3_t mins, maxs;
matrix4x4 m, oldm;
elem_t *elem;
skydome_t *skydome;
meshbuffer_t *mbuffer = &r_skydome_mbuffer;
int u, v, umin, umax, vmin, vmax;
if( !shader ) return;
skydome = shader->skyParms ? shader->skyParms : NULL;
if( !skydome) return;
ClearBounds( mins, maxs );
for( i = 0; i < 6; i++ )
{
if( RI.skyMins[0][i] >= RI.skyMaxs[0][i] || RI.skyMins[1][i] >= RI.skyMaxs[1][i] )
continue;
umin = (int)(( RI.skyMins[0][i] + 1.0f ) * 0.5f * (float)( SIDE_SIZE-1 ));
umax = (int)(( RI.skyMaxs[0][i] + 1.0f ) * 0.5f * (float)( SIDE_SIZE-1 )) + 1;
vmin = (int)(( RI.skyMins[1][i] + 1.0f ) * 0.5f * (float)( SIDE_SIZE-1 ));
vmax = (int)(( RI.skyMaxs[1][i] + 1.0f ) * 0.5f * (float)( SIDE_SIZE-1 )) + 1;
umin = bound( 0, umin, SIDE_SIZE-1 );
umax = bound( 0, umax, SIDE_SIZE-1 );
vmin = bound( 0, vmin, SIDE_SIZE-1 );
vmax = bound( 0, vmax, SIDE_SIZE-1 );
// box elems in tristrip order
elem = skydome->meshes[i].elems;
for( v = vmin; v < vmax; v++ )
{
for( u = umin; u < umax; u++ )
{
elem[0] = v * SIDE_SIZE + u;
elem[1] = elem[4] = elem[0] + SIDE_SIZE;
elem[2] = elem[3] = elem[0] + 1;
elem[5] = elem[1] + 1;
elem += 6;
}
}
AddPointToBounds( skydome->meshes[i].xyzArray[vmin*SIDE_SIZE+umin], mins, maxs );
AddPointToBounds( skydome->meshes[i].xyzArray[vmax*SIDE_SIZE+umax], mins, maxs );
skydome->meshes[i].numElems = ( vmax-vmin )*( umax-umin ) * 6;
}
VectorAdd( mins, RI.viewOrigin, mins );
VectorAdd( maxs, RI.viewOrigin, maxs );
if( RI.refdef.flags & RDF_SKYPORTALINVIEW )
{
R_DrawSkyPortal( &RI.refdef.skyportal, mins, maxs );
return;
}
// center skydome on camera to give the illusion of a larger space
Matrix4x4_Copy( oldm, RI.modelviewMatrix );
Matrix4x4_Copy( RI.modelviewMatrix, RI.worldviewMatrix );
Matrix4x4_Copy( m, RI.worldviewMatrix );
if( shader->skySpeed )
{
float angle = shader->skySpeed * RI.refdef.time;
Matrix4x4_ConcatRotate( m, angle, shader->skyAxis[0], shader->skyAxis[1], shader->skyAxis[2] );
}
Matrix4x4_SetOrigin( m, 0, 0, 0 );
m[3][3] = 1.0f;
GL_LoadMatrix( m );
gldepthmin = 1;
gldepthmax = 1;
pglDepthRange( gldepthmin, gldepthmax );
if( RI.params & RP_CLIPPLANE )
pglDisable( GL_CLIP_PLANE0 );
// it can happen that sky surfaces have no fog hull specified
// yet there's a global fog hull (see wvwq3dm7)
if( !r_skyfog ) r_skyfog = r_worldbrushmodel->globalfog;
if( skydome->farboxShaders[0] )
R_DrawSkyBox( skydome, skydome->farboxShaders );
else R_DrawBlackBottom( skydome );
if( shader->num_stages )
{
bool flush = false;
int features = shader->features;
if( r_shownormals->integer )
features |= MF_NORMALS;
for( i = 0; i < 6; i++ )
{
if( RI.skyMins[0][i] >= RI.skyMaxs[0][i] || RI.skyMins[1][i] >= RI.skyMaxs[1][i] )
continue;
flush = true;
mbuffer->shaderkey = shader->sortkey;
mbuffer->dlightbits = 0;
mbuffer->sortkey = MB_FOG2NUM( r_skyfog );
skydome->meshes[i].stArray = skydome->sphereStCoords[i];
R_PushMesh( &skydome->meshes[i], features );
}
if( flush ) R_RenderMeshBuffer( mbuffer );
}
if( skydome->nearboxShaders[0] )
R_DrawSkyBox( skydome, skydome->nearboxShaders );
if( RI.params & RP_CLIPPLANE )
pglEnable( GL_CLIP_PLANE0 );
Matrix4x4_Copy( RI.modelviewMatrix, oldm );
GL_LoadMatrix( RI.worldviewMatrix );
gldepthmin = 0;
gldepthmax = 1;
pglDepthRange( gldepthmin, gldepthmax );
r_skyfog = NULL;
}
//===================================================================
vec3_t skyclip[6] = {
{ 1, 1, 0 },
{ 1, -1, 0 },
{ 0, -1, 1 },
{ 0, 1, 1 },
{ 1, 0, 1 },
{ -1, 0, 1 }
};
// 1 = s, 2 = t, 3 = 2048
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
};
// s = [0]/[2], t = [1]/[2]
int vec_to_st[6][3] =
{
{ -2, 3, 1 },
{ 2, 3, -1 },
{ 1, 3, 2 },
{ -1, 3, -2 },
{ -2, -1, 3 },
{ -2, 1, -3 }
};
/*
==============
DrawSkyPolygon
==============
*/
void DrawSkyPolygon( int nump, vec3_t vecs )
{
int i, j;
vec3_t v, av;
float s, t, dv;
int axis;
float *vp;
// decide which face it maps to
VectorClear( 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] ))
axis = ( v[0] < 0 ) ? 1 : 0;
else if(( av[1] > av[2] ) && ( av[1] > av[0] ))
axis = ( v[1] < 0 ) ? 3 : 2;
else axis = ( v[2] < 0 ) ? 5 : 4;
if( !r_skyfog )
r_skyfog = r_warpface->fog;
r_warpfacevis = true;
// project new texture coords
for( i = 0; i < nump; i++, vecs += 3 )
{
j = vec_to_st[axis][2];
dv = ( j > 0 ) ? vecs[j - 1] : -vecs[-j - 1];
if( dv < 0.001f )
continue; // don't divide by zero
dv = 1.0f / dv;
j = vec_to_st[axis][0];
s = ( j < 0 ) ? -vecs[-j -1] * dv : vecs[j-1] * dv;
j = vec_to_st[axis][1];
t = ( j < 0 ) ? -vecs[-j -1] * dv : vecs[j-1] * dv;
if( s < RI.skyMins[0][axis] ) RI.skyMins[0][axis] = s;
if( t < RI.skyMins[1][axis] ) RI.skyMins[1][axis] = t;
if( s > RI.skyMaxs[0][axis] ) RI.skyMaxs[0][axis] = s;
if( t > RI.skyMaxs[1][axis] ) RI.skyMaxs[1][axis] = t;
}
}
/*
==============
ClipSkyPolygon
==============
*/
void ClipSkyPolygon( int nump, vec3_t vecs, int stage )
{
float *norm;
float *v;
bool front, back;
float d, e;
float dists[MAX_CLIP_VERTS + 1];
int sides[MAX_CLIP_VERTS + 1];
vec3_t newv[2][MAX_CLIP_VERTS + 1];
int newc[2];
int i, j;
if( nump > MAX_CLIP_VERTS )
Host_Error( "ClipSkyPolygon: MAX_CLIP_VERTS\n" );
loc1:
if( stage == 6 )
{
// fully clipped, so draw it
DrawSkyPolygon( nump, vecs );
return;
}
front = back = false;
norm = skyclip[stage];
for( i = 0, v = vecs; i < nump; i++, v += 3 )
{
d = DotProduct( v, norm );
if( d > ON_EPSILON )
{
front = true;
sides[i] = SIDE_FRONT;
}
else if( d < -ON_EPSILON )
{
back = true;
sides[i] = SIDE_BACK;
}
else
{
sides[i] = SIDE_ON;
}
dists[i] = d;
}
if( !front || !back )
{
// not clipped
stage++;
goto loc1;
}
// 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 );
}
/*
=================
R_AddSkySurface
=================
*/
bool R_AddSkySurface( msurface_t *fa )
{
int i;
vec4_t *vert;
elem_t *elem;
mesh_t *mesh;
vec3_t verts[4];
// calculate vertex values for sky box
r_warpface = fa;
r_warpfacevis = false;
if( fa->shader->skySpeed )
{
// HACK: force full sky to draw when rotating
for( i = 0; i < 6; i++ )
{
RI.skyMins[0][i] = RI.skyMins[1][i] = -1;
RI.skyMaxs[0][i] = RI.skyMaxs[1][i] = 1;
}
}
mesh = fa->mesh;
elem = mesh->elems;
vert = mesh->xyzArray;
for( i = 0; i < mesh->numElems; i += 3, elem += 3 )
{
VectorSubtract( vert[elem[0]], RI.viewOrigin, verts[0] );
VectorSubtract( vert[elem[1]], RI.viewOrigin, verts[1] );
VectorSubtract( vert[elem[2]], RI.viewOrigin, verts[2] );
ClipSkyPolygon( 3, verts[0], 0 );
}
return r_warpfacevis;
}
/*
==============
R_ClearSkyBox
==============
*/
void R_ClearSkyBox( void )
{
int i;
RI.params |= RP_NOSKY;
for( i = 0; i < 6; i++ )
{
RI.skyMins[0][i] = RI.skyMins[1][i] = 9999999;
RI.skyMaxs[0][i] = RI.skyMaxs[1][i] = -9999999;
}
}
void MakeSkyVec( float x, float y, float z, int axis, vec3_t v )
{
int j, k;
vec3_t b;
b[0] = x;
b[1] = y;
b[2] = z;
for( j = 0; j < 3; j++ )
{
k = st_to_vec[axis][j];
if( k < 0 ) v[j] = -b[-k-1];
else v[j] = b[k-1];
}
}