Paranoia2/cl_dll/render/gl_grass.cpp
2020-08-31 19:50:41 +03:00

1775 lines
48 KiB
C++

/*
gl_grass.cpp - auto grass system
Copyright (C) 2014 Uncle Mike
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 3 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.
*/
#include "hud.h"
#include "cl_util.h"
#include "gl_local.h"
#include "gl_world.h"
#include "gl_grass.h"
#include "gl_shader.h"
#include <utlarray.h>
#include <stringlib.h>
#include "vertex_fmt.h"
#define LEAF_MAX_EXPAND 48.0f
#define DENSITY_FACTOR 0.0001f
grasstexture_t grasstexs[GRASS_TEXTURES];
CUtlArray<grassentry_t> grassInfo;
// intermediate arrays used for creating VBO's
static gvert_t m_arraygvert[MAX_GRASS_VERTS];
static word m_indexarray[MAX_GRASS_ELEMS];
static int m_iNumVertex, m_iNumIndex, m_iVertexState;
static int m_iTotalVerts = 0;
static int m_iTextureWidth;
static int m_iTextureHeight;
static float m_flGrassFadeStart;
static float m_flGrassFadeDist;
static float m_flGrassFadeEnd;
static void CreateBufferBaseGL21( grass_t *pOut, gvert_t *arrayxvert )
{
static gvert_v0_gl21_t arraygvert[MAX_GRASS_VERTS];
// convert to GLSL-compacted array
for( int i = 0; i < pOut->numVerts; i++ )
{
arraygvert[i].center[0] = arrayxvert[i].center[0];
arraygvert[i].center[1] = arrayxvert[i].center[1];
arraygvert[i].center[2] = arrayxvert[i].center[2];
arraygvert[i].center[3] = arrayxvert[i].center[3];
arraygvert[i].light[0] = arrayxvert[i].light[0];
arraygvert[i].light[1] = arrayxvert[i].light[1];
arraygvert[i].light[2] = arrayxvert[i].light[2];
arraygvert[i].light[3] = arrayxvert[i].light[3];
arraygvert[i].normal[0] = arrayxvert[i].normal[0];
arraygvert[i].normal[1] = arrayxvert[i].normal[1];
arraygvert[i].normal[2] = arrayxvert[i].normal[2];
arraygvert[i].normal[3] = arrayxvert[i].normal[3];
}
pOut->cacheSize = pOut->numVerts * sizeof( gvert_v0_gl21_t );
// create grass static buffer
pglGenBuffersARB( 1, &pOut->vbo );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo );
pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraygvert[0], GL_STATIC_DRAW_ARB );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );
}
static void BindBufferBaseGL21( grass_t *pOut, int attrFlags )
{
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo );
pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v0_gl21_t ), (void *)offsetof( gvert_v0_gl21_t, center ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION );
pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v0_gl21_t ), (void *)offsetof( gvert_v0_gl21_t, normal ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL );
pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v0_gl21_t ), (void *)offsetof( gvert_v0_gl21_t, light ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_COLOR );
pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof( gvert_v0_gl21_t ), (void *)offsetof( gvert_v0_gl21_t, styles ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES );
}
static void CreateBufferBaseGL30( grass_t *pOut, gvert_t *arrayxvert )
{
static gvert_v0_gl30_t arraygvert[MAX_GRASS_VERTS];
// convert to GLSL-compacted array
for( int i = 0; i < pOut->numVerts; i++ )
{
arraygvert[i].center[0] = FloatToHalf( arrayxvert[i].center[0] );
arraygvert[i].center[1] = FloatToHalf( arrayxvert[i].center[1] );
arraygvert[i].center[2] = FloatToHalf( arrayxvert[i].center[2] );
arraygvert[i].center[3] = FloatToHalf( arrayxvert[i].center[3] );
arraygvert[i].light[0] = arrayxvert[i].light[0];
arraygvert[i].light[1] = arrayxvert[i].light[1];
arraygvert[i].light[2] = arrayxvert[i].light[2];
arraygvert[i].light[3] = arrayxvert[i].light[3];
CompressNormalizedVector( arraygvert[i].normal, arrayxvert[i].normal );
arraygvert[i].normal[3] = arrayxvert[i].normal[3];
}
pOut->cacheSize = pOut->numVerts * sizeof( gvert_v0_gl30_t );
// create grass static buffer
pglGenBuffersARB( 1, &pOut->vbo );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo );
pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraygvert[0], GL_STATIC_DRAW_ARB );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );
}
static void BindBufferBaseGL30( grass_t *pOut, int attrFlags )
{
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo );
pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 4, GL_HALF_FLOAT_ARB, GL_FALSE, sizeof( gvert_v0_gl30_t ), (void *)offsetof( gvert_v0_gl30_t, center ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION );
pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 4, GL_BYTE, GL_FALSE, sizeof( gvert_v0_gl30_t ), (void *)offsetof( gvert_v0_gl30_t, normal ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL );
pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v0_gl30_t ), (void *)offsetof( gvert_v0_gl30_t, light ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_COLOR );
pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof( gvert_v0_gl30_t ), (void *)offsetof( gvert_v0_gl30_t, styles ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES );
}
static void CreateBufferBaseBumpGL21( grass_t *pOut, gvert_t *arrayxvert )
{
static gvert_v1_gl21_t arraygvert[MAX_GRASS_VERTS];
// convert to GLSL-compacted array
for( int i = 0; i < pOut->numVerts; i++ )
{
arraygvert[i].center[0] = arrayxvert[i].center[0];
arraygvert[i].center[1] = arrayxvert[i].center[1];
arraygvert[i].center[2] = arrayxvert[i].center[2];
arraygvert[i].center[3] = arrayxvert[i].center[3];
arraygvert[i].light[0] = arrayxvert[i].light[0];
arraygvert[i].light[1] = arrayxvert[i].light[1];
arraygvert[i].light[2] = arrayxvert[i].light[2];
arraygvert[i].light[3] = arrayxvert[i].light[3];
arraygvert[i].delux[0] = arrayxvert[i].delux[0];
arraygvert[i].delux[1] = arrayxvert[i].delux[1];
arraygvert[i].delux[2] = arrayxvert[i].delux[2];
arraygvert[i].delux[3] = arrayxvert[i].delux[3];
arraygvert[i].normal[0] = arrayxvert[i].normal[0];
arraygvert[i].normal[1] = arrayxvert[i].normal[1];
arraygvert[i].normal[2] = arrayxvert[i].normal[2];
arraygvert[i].normal[3] = arrayxvert[i].normal[3];
}
pOut->cacheSize = pOut->numVerts * sizeof( gvert_v1_gl21_t );
// create grass static buffer
pglGenBuffersARB( 1, &pOut->vbo );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo );
pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraygvert[0], GL_STATIC_DRAW_ARB );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );
}
static void BindBufferBaseBumpGL21( grass_t *pOut, int attrFlags )
{
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo );
pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v1_gl21_t ), (void *)offsetof( gvert_v1_gl21_t, center ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION );
pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v1_gl21_t ), (void *)offsetof( gvert_v1_gl21_t, normal ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL );
pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v1_gl21_t ), (void *)offsetof( gvert_v1_gl21_t, light ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_COLOR );
pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_VECS, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v1_gl21_t ), (void *)offsetof( gvert_v1_gl21_t, delux ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_VECS );
pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof( gvert_v1_gl21_t ), (void *)offsetof( gvert_v1_gl21_t, styles ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES );
}
static void CreateBufferBaseBumpGL30( grass_t *pOut, gvert_t *arrayxvert )
{
static gvert_v1_gl30_t arraygvert[MAX_GRASS_VERTS];
// convert to GLSL-compacted array
for( int i = 0; i < pOut->numVerts; i++ )
{
arraygvert[i].center[0] = FloatToHalf( arrayxvert[i].center[0] );
arraygvert[i].center[1] = FloatToHalf( arrayxvert[i].center[1] );
arraygvert[i].center[2] = FloatToHalf( arrayxvert[i].center[2] );
arraygvert[i].center[3] = FloatToHalf( arrayxvert[i].center[3] );
arraygvert[i].light[0] = arrayxvert[i].light[0];
arraygvert[i].light[1] = arrayxvert[i].light[1];
arraygvert[i].light[2] = arrayxvert[i].light[2];
arraygvert[i].light[3] = arrayxvert[i].light[3];
arraygvert[i].delux[0] = arrayxvert[i].delux[0];
arraygvert[i].delux[1] = arrayxvert[i].delux[1];
arraygvert[i].delux[2] = arrayxvert[i].delux[2];
arraygvert[i].delux[3] = arrayxvert[i].delux[3];
CompressNormalizedVector( arraygvert[i].normal, arrayxvert[i].normal );
arraygvert[i].normal[3] = arrayxvert[i].normal[3];
}
pOut->cacheSize = pOut->numVerts * sizeof( gvert_v1_gl30_t );
// create grass static buffer
pglGenBuffersARB( 1, &pOut->vbo );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo );
pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->numVerts * sizeof( gvert_v1_gl30_t ), &arraygvert[0], GL_STATIC_DRAW_ARB );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );
}
static void BindBufferBaseBumpGL30( grass_t *pOut, int attrFlags )
{
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo );
pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 4, GL_HALF_FLOAT_ARB, GL_FALSE, sizeof( gvert_v1_gl30_t ), (void *)offsetof( gvert_v1_gl30_t, center ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION );
pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 4, GL_BYTE, GL_FALSE, sizeof( gvert_v1_gl30_t ), (void *)offsetof( gvert_v1_gl30_t, normal ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL );
pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v1_gl30_t ), (void *)offsetof( gvert_v1_gl30_t, light ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_COLOR );
pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_VECS, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v1_gl30_t ), (void *)offsetof( gvert_v1_gl30_t, delux ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_VECS );
pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof( gvert_v1_gl30_t ), (void *)offsetof( gvert_v1_gl30_t, styles ));
pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES );
}
static void CreateIndexBuffer( grass_t *pOut, unsigned short *arrayelems )
{
uint cacheSize = pOut->numElems * sizeof( word );
pglGenBuffersARB( 1, &pOut->ibo );
pglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, pOut->ibo );
pglBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, cacheSize, &arrayelems[0], GL_STATIC_DRAW_ARB );
pglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 );
// update total buffer size
pOut->cacheSize += cacheSize;
}
static void BindIndexBuffer( grass_t *pOut )
{
pglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, pOut->ibo );
}
unsigned int SelectGrassLoader( void )
{
if( FBitSet( world->features, WORLD_HAS_DELUXEMAP ) && r_grass->value > 1.0f )
return GRASSLOADER_BASEBUMP;
return GRASSLOADER_BASE;
}
static grass_loader_t pfnMeshLoaderGL21[GRASSLOADER_COUNT] =
{
{ CreateBufferBaseGL21, BindBufferBaseGL21, "BaseBuffer" },
{ CreateBufferBaseBumpGL21, BindBufferBaseBumpGL21, "BumpBaseBuffer" },
};
static grass_loader_t pfnMeshLoaderGL30[GRASSLOADER_COUNT] =
{
{ CreateBufferBaseGL30, BindBufferBaseGL30, "BaseBuffer" },
{ CreateBufferBaseBumpGL30, BindBufferBaseBumpGL30, "BumpBaseBuffer" },
};
/*
================
R_GetPointForBush
compute 16 points for single bush
================
*/
static const Vector R_GetPointForBush( int vertexNum, const Vector &pos, float scale )
{
float s1 = ( m_iTextureWidth * 0.075f ) * scale;
float s2 = ( m_iTextureWidth * 0.1f ) * scale;
float s3 = ( m_iTextureHeight * 0.1f ) * scale;
switch(( vertexNum & 15 ))
{
case 0: return Vector( pos.x - s2, pos.y, pos.z );
case 1: return Vector( pos.x - s2, pos.y, pos.z + s3 );
case 2: return Vector( pos.x + s2, pos.y, pos.z + s3 );
case 3: return Vector( pos.x + s2, pos.y, pos.z );
case 4: return Vector( pos.x, pos.y - s2, pos.z );
case 5: return Vector( pos.x, pos.y - s2, pos.z + s3 );
case 6: return Vector( pos.x, pos.y + s2, pos.z + s3 );
case 7: return Vector( pos.x, pos.y + s2, pos.z );
case 8: return Vector( pos.x - s1, pos.y - s1, pos.z );
case 9: return Vector( pos.x - s1, pos.y - s1, pos.z + s3 );
case 10: return Vector( pos.x + s1, pos.y + s1, pos.z + s3 );
case 11: return Vector( pos.x + s1, pos.y + s1, pos.z );
case 12: return Vector( pos.x - s1, pos.y + s1, pos.z );
case 13: return Vector( pos.x - s1, pos.y + s1, pos.z + s3 );
case 14: return Vector( pos.x + s1, pos.y - s1, pos.z + s3 );
case 15: return Vector( pos.x + s1, pos.y - s1, pos.z );
}
return g_vecZero; // error
}
/*
================
R_GetTexCoordForBush
get fixed texcoord for bush
================
*/
static const Vector2D R_GetTexCoordForBush( int vertexNum )
{
switch(( vertexNum & 7 ))
{
case 0: return Vector2D( 0.0f, 1.0f );
case 1: return Vector2D( 0.0f, 0.0f );
case 2: return Vector2D( 1.0f, 0.0f );
case 3: return Vector2D( 1.0f, 1.0f );
case 4: return Vector2D( 1.0f, 1.0f );
case 5: return Vector2D( 1.0f, 0.0f );
case 6: return Vector2D( 0.0f, 0.0f );
case 7: return Vector2D( 0.0f, 1.0f );
}
return Vector2D( 0.0f, 0.0f ); // error
}
/*
================
R_GetNormalForBush
get fixed normals for single bush
================
*/
static const Vector R_GetNormalForBush( int vertexNum )
{
switch(( vertexNum & 15 ))
{
case 0: return Vector( 0.0f, 1.0f, 0.0f );
case 1: return Vector( 0.0f, 1.0f, 0.0f );
case 2: return Vector( 0.0f, 1.0f, 0.0f );
case 3: return Vector( 0.0f, 1.0f, 0.0f );
case 4: return Vector( -1.0f, 0.0f, 0.0f );
case 5: return Vector( -1.0f, 0.0f, 0.0f );
case 6: return Vector( -1.0f, 0.0f, 0.0f );
case 7: return Vector( -1.0f, 0.0f, 0.0f );
case 8: return Vector( -0.707107f, 0.707107f, 0.0f );
case 9: return Vector( -0.707107f, 0.707107f, 0.0f );
case 10: return Vector( -0.707107f, 0.707107f, 0.0f );
case 11: return Vector( -0.707107f, 0.707107f, 0.0f );
case 12: return Vector( 0.707107f, 0.707107f, 0.0f );
case 13: return Vector( 0.707107f, 0.707107f, 0.0f );
case 14: return Vector( 0.707107f, 0.707107f, 0.0f );
case 15: return Vector( 0.707107f, 0.707107f, 0.0f );
}
return g_vecZero; // error
}
/*
================
R_AdvanceVertex
routine to build quad sequences
================
*/
bool R_GrassAdvanceVertex( void )
{
if((( m_iNumIndex + 6 ) >= MAX_GRASS_ELEMS ) || (( m_iNumVertex + 4 ) >= MAX_GRASS_VERTS ))
return false;
if( m_iVertexState++ < 3 )
{
m_indexarray[m_iNumIndex++] = m_iNumVertex;
}
else
{
// we've already done triangle (0, 1, 2), now draw (2, 3, 0)
m_indexarray[m_iNumIndex++] = m_iNumVertex - 1;
m_indexarray[m_iNumIndex++] = m_iNumVertex;
m_indexarray[m_iNumIndex++] = m_iNumVertex - 3;
m_iVertexState = 0;
}
m_iNumVertex++;
return true;
}
/*
================
R_GrassLightForVertex
We already have a valid spot on texture
Just find lightmap point and update grass color
================
*/
void R_GrassLightForVertex( msurface_t *fa, mextrasurf_t *es, const Vector &vertex, float posz, float light[MAXLIGHTMAPS], float delux[MAXLIGHTMAPS] )
{
if( !worldmodel->lightdata || !fa->samples )
return;
float sample_size = Mod_SampleSizeForFace( fa );
Vector pos = Vector( vertex.x, vertex.y, posz ); // drop to floor
float s = DotProduct( pos, fa->info->lmvecs[0] ) + fa->info->lmvecs[0][3] - fa->info->lightmapmins[0];
float t = DotProduct( pos, fa->info->lmvecs[1] ) + fa->info->lmvecs[1][3] - fa->info->lightmapmins[1];
int smax = (fa->info->lightextents[0] / sample_size) + 1;
int tmax = (fa->info->lightextents[1] / sample_size) + 1;
int size = smax * tmax;
int map;
// some bushes may be out of current poly
s = bound( 0, s, fa->info->lightextents[0] );
t = bound( 0, t, fa->info->lightextents[1] );
s /= sample_size;
t /= sample_size;
color24 *lm = fa->samples + Q_rint( t ) * smax + Q_rint( s );
for( map = 0; map < MAXLIGHTMAPS && fa->styles[map] != 255; map++ )
{
color24 out;
out.r = TEXTURE_TO_TEXGAMMA( lm->r );
out.g = TEXTURE_TO_TEXGAMMA( lm->g );
out.b = TEXTURE_TO_TEXGAMMA( lm->b );
light[map] = PackColor( out );
lm += size; // skip to next lightmap
}
if( !es->normals ) return;
color24 *dm = es->normals + Q_rint( t ) * smax + Q_rint( s );
Vector vec_x, vec_y, vec_z;
matrix3x3 mat;
// flat TBN for better results
vec_x = Vector( fa->info->lmvecs[0] );
vec_y = Vector( fa->info->lmvecs[1] );
if( FBitSet( fa->flags, SURF_PLANEBACK ))
vec_z = -fa->plane->normal;
else vec_z = fa->plane->normal;
// create tangent space rotational matrix
mat.SetForward( vec_x.Normalize( ));
mat.SetRight( -vec_y.Normalize( ));
mat.SetUp( vec_z.Normalize( ));
for( map = 0; map < MAXLIGHTMAPS && fa->styles[map] != 255; map++ )
{
float f = (1.0f / 128.0f);
Vector normal = Vector(((float)dm->r - 128.0f) * f, ((float)dm->g - 128.0f) * f, ((float)dm->b - 128.0f) * f);
delux[map] = NormalToFloat( mat.VectorRotate( normal ));
dm += size; // skip to next lightmap
}
}
/*
================
R_CreateSingleBush
create a bush with specified pos
================
*/
bool R_CreateSingleBush( msurface_t *surf, mextrasurf_t *es, grasshdr_t *hdr, const Vector &pos, float size )
{
for( int i = 0; i < 16; i++ )
{
gvert_t *entry = &m_arraygvert[m_iNumVertex];
Vector vertex = R_GetPointForBush( i, pos, size );
memcpy( entry->styles, surf->styles, sizeof( entry->styles ));
R_GrassLightForVertex( surf, es, vertex, pos.z, entry->light, entry->delux );
AddPointToBounds( vertex, hdr->mins, hdr->maxs ); // build bbox for grass
Vector dir = ( vertex - pos ).Normalize();
float scale = ( vertex - pos ).Length();
entry->center[0] = pos.x;
entry->center[1] = pos.y;
entry->center[2] = pos.z;
entry->center[3] = scale;
entry->normal[0] = dir.x;
entry->normal[1] = dir.y;
entry->normal[2] = dir.z;
entry->normal[3] = i;
// generate indices
if( !R_GrassAdvanceVertex( ))
{
// vertexes is out
return false;
}
}
return true;
}
void R_CreateSurfaceVBO( grass_t *pOut )
{
if( !m_iNumVertex ) return; // empty mesh?
GL_CheckVertexArrayBinding();
// determine optimal mesh loader
uint attribs = 0; // unused
uint type = SelectGrassLoader();
// move data to video memory
if( glConfig.version < ACTUAL_GL_VERSION )
pfnMeshLoaderGL21[type].CreateBuffer( pOut, m_arraygvert );
else pfnMeshLoaderGL30[type].CreateBuffer( pOut, m_arraygvert );
CreateIndexBuffer( pOut, m_indexarray );
// link it with vertex array object
pglGenVertexArrays( 1, &pOut->vao );
pglBindVertexArray( pOut->vao );
if( glConfig.version < ACTUAL_GL_VERSION )
pfnMeshLoaderGL21[type].BindBuffer( pOut, attribs );
else pfnMeshLoaderGL30[type].BindBuffer( pOut, attribs );
BindIndexBuffer( pOut );
pglBindVertexArray( GL_FALSE );
// update stats
tr.total_vbo_memory += pOut->cacheSize;
}
void R_DeleteSurfaceVBO( grass_t *pOut )
{
if( pOut->vao ) pglDeleteVertexArrays( 1, &pOut->vao );
if( pOut->vbo ) pglDeleteBuffersARB( 1, &pOut->vbo );
if( pOut->ibo ) pglDeleteBuffersARB( 1, &pOut->ibo );
tr.total_vbo_memory -= pOut->cacheSize;
pOut->cacheSize = 0;
}
/*
================
R_BuildGrassMesh
build mesh with single texture
================
*/
bool R_BuildGrassMesh( msurface_t *surf, mextrasurf_t *es, grassentry_t *entry, grasshdr_t *hdr, grass_t *out )
{
mfaceinfo_t *land = surf->texinfo->faceinfo;
bvert_t *v0, *v1, *v2;
// update random set to get predictable positions for grass 'random' placement
RANDOM_SEED(( surf - worldmodel->surfaces ) * entry->seed );
m_iNumVertex = m_iNumIndex = m_iVertexState = 0;
m_iTextureWidth = RENDER_GET_PARM( PARM_TEX_WIDTH, grasstexs[entry->texture].gl_texturenum );
m_iTextureHeight = RENDER_GET_PARM( PARM_TEX_HEIGHT, grasstexs[entry->texture].gl_texturenum );
// turn the face into a bunch of polygons, and compute the area of each
v0 = &world->vertexes[es->firstvertex];
for( int i = 1; i < es->numverts - 1; i++ )
{
v1 = &world->vertexes[es->firstvertex+i+0];
v2 = &world->vertexes[es->firstvertex+i+1];
// compute two triangle edges
Vector e1 = v1->vertex - v0->vertex;
Vector e2 = v2->vertex - v0->vertex;
// compute the triangle area
Vector areaVec = CrossProduct( e1, e2 );
float normalLength = areaVec.Length();
float area = 0.5f * normalLength;
// compute the number of samples to take
int numSamples = area * entry->density * DENSITY_FACTOR;
// now take a sample, and randomly place an object there
for( int j = 0; j < numSamples; j++ )
{
// Create a random sample...
float u = RANDOM_FLOAT( 0.0f, 1.0f );
float v = RANDOM_FLOAT( 0.0f, 1.0f );
if( v > ( 1.0f - u ))
{
u = 1.0f - u;
v = 1.0f - v;
}
float size = RANDOM_FLOAT( entry->min, entry->max );
Vector pos = v0->vertex + e1 * u + e2 * v;
if( !Mod_CheckLayerNameForPixel( land, pos, entry->name ))
continue; // rejected by heightmap
if( !R_CreateSingleBush( surf, es, hdr, pos, size ))
goto build_mesh; // vertices is out (more than 2048 bushes per surface created)
}
}
// nothing to added?
if( !m_iNumVertex ) return false;
build_mesh:
// give lightnums from surface
memcpy( out->lights, es->lights, sizeof( byte ) * MAXDYNLIGHTS );
out->texture = entry->texture;
out->numVerts = m_iNumVertex;
out->numElems = m_iNumIndex;
R_CreateSurfaceVBO( out );
return true;
}
/*
================
R_ConstructGrassForSurface
compile all the grassdata with
specified texture into single VBO
================
*/
void R_ConstructGrassForSurface( msurface_t *surf, mextrasurf_t *es )
{
// already init or not specified?
if( es->grass || !es->grasscount )
return;
size_t grasshdr_size = sizeof( grasshdr_t ) + sizeof( grass_t ) * ( es->grasscount - 1 );
grasshdr_t *hdr = es->grass = (grasshdr_t *)IEngineStudio.Mem_Calloc( 1, grasshdr_size );
hdr->mins = es->mins;
hdr->maxs = es->maxs;
hdr->count = 0;
for( int i = 0; i < grassInfo.Count(); i++ )
{
grassentry_t *entry = &grassInfo[i];
if( !Mod_CheckLayerNameForSurf( surf, entry->name ))
continue;
// create a single mesh for all the bushes that have same texture
if( !R_BuildGrassMesh( surf, es, entry, hdr, &hdr->g[hdr->count] ))
continue; // failed to build for some reasons
hdr->count++;
}
ClearBits( surf->flags, SURF_GRASS_UPDATE );
// bah! failed to create
if( !hdr->count )
{
Mem_Free( es->grass );
es->grasscount = 0;
es->grass = NULL;
}
// restore normal randomization
RANDOM_SEED( 0 );
}
void R_RemoveGrassForSurface( mextrasurf_t *es )
{
ClearBits( es->surf->flags, SURF_GRASS_UPDATE );
// not specified?
if( !es->grass ) return;
grasshdr_t *hdr = es->grass;
for( int i = 0; i < hdr->count; i++ )
R_DeleteSurfaceVBO( &hdr->g[i] );
es->grass = NULL;
Mem_Free( hdr );
}
void R_DrawGrassMeshFromBuffer( const grass_t *mesh )
{
pglBindVertexArray( mesh->vao );
if( GL_Support( R_DRAW_RANGEELEMENTS_EXT ))
pglDrawRangeElementsEXT( GL_TRIANGLES, 0, mesh->numVerts - 1, mesh->numElems, GL_UNSIGNED_SHORT, 0 );
else pglDrawElements( GL_TRIANGLES, mesh->numElems, GL_UNSIGNED_SHORT, 0 );
r_stats.c_total_tris += (mesh->numVerts / 2);
r_stats.num_flushes++;
}
/*
================
R_SetSurfaceUniforms
================
*/
void R_SetGrassUniforms( word hProgram, grass_t *grass )
{
Vector lightdir;
float *v;
if( RI->currentshader != &glsl_programs[hProgram] )
GL_BindShader( &glsl_programs[hProgram] );
glsl_program_t *shader = RI->currentshader;
gl_state_t *glm = GL_GetCache( grass->hCachedMatrix );
CDynLight *pl = RI->currentlight; // may be NULL
// sometime we can't set the uniforms
if( !shader || !shader->numUniforms || !shader->uniforms )
return;
// setup specified uniforms (and texture bindings)
for( int i = 0; i < shader->numUniforms; i++ )
{
uniform_t *u = &shader->uniforms[i];
switch( u->type )
{
case UT_COLORMAP:
u->SetValue( grasstexs[grass->texture].gl_texturenum );
break;
case UT_PROJECTMAP:
if( pl && pl->type == LIGHT_SPOT )
u->SetValue( pl->spotlightTexture );
else u->SetValue( tr.whiteTexture );
break;
case UT_SHADOWMAP:
case UT_SHADOWMAP0:
if( pl ) u->SetValue( pl->shadowTexture[0] );
else u->SetValue( tr.depthTexture );
break;
case UT_SHADOWMAP1:
if( pl ) u->SetValue( pl->shadowTexture[1] );
else u->SetValue( tr.depthTexture );
break;
case UT_SHADOWMAP2:
if( pl ) u->SetValue( pl->shadowTexture[2] );
else u->SetValue( tr.depthTexture );
break;
case UT_SHADOWMAP3:
if( pl ) u->SetValue( pl->shadowTexture[3] );
else u->SetValue( tr.depthTexture );
break;
case UT_LIGHTMAP:
case UT_DELUXEMAP:
case UT_DECALMAP:
// unacceptable for grass
u->SetValue( tr.whiteTexture );
break;
case UT_SCREENMAP:
u->SetValue( tr.screen_color );
break;
case UT_DEPTHMAP:
u->SetValue( tr.screen_depth );
break;
case UT_FITNORMALMAP:
u->SetValue( tr.normalsFitting );
break;
case UT_MODELMATRIX:
u->SetValue( &glm->modelMatrix[0] );
break;
case UT_SCREENSIZEINV:
u->SetValue( 1.0f / (float)glState.width, 1.0f / (float)glState.height );
break;
case UT_ZFAR:
u->SetValue( -tr.farclip * 1.74f );
break;
case UT_LIGHTSTYLEVALUES:
u->SetValue( &tr.lightstyle[0], MAX_LIGHTSTYLES );
break;
case UT_REALTIME:
u->SetValue( (float)tr.time );
break;
case UT_FOGPARAMS:
u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogDensity );
break;
case UT_SHADOWPARMS:
if( pl != NULL )
{
float shadowWidth = 1.0f / (float)RENDER_GET_PARM( PARM_TEX_WIDTH, pl->shadowTexture[0] );
float shadowHeight = 1.0f / (float)RENDER_GET_PARM( PARM_TEX_HEIGHT, pl->shadowTexture[0] );
// depth scale and bias and shadowmap resolution
u->SetValue( shadowWidth, shadowHeight, -pl->projectionMatrix[2][2], pl->projectionMatrix[3][2] );
}
else u->SetValue( 0.0f, 0.0f, 0.0f, 0.0f );
break;
case UT_VIEWORIGIN:
u->SetValue( tr.cached_vieworigin.x, tr.cached_vieworigin.y, tr.cached_vieworigin.z );
break;
case UT_SHADOWMATRIX:
if( pl ) u->SetValue( &pl->gl_shadowMatrix[0][0], MAX_SHADOWMAPS );
break;
case UT_SHADOWSPLITDIST:
v = RI->view.parallelSplitDistances;
u->SetValue( v[0], v[1], v[2], v[3] );
break;
case UT_TEXELSIZE:
u->SetValue( 1.0f / (float)sunSize[0], 1.0f / (float)sunSize[1], 1.0f / (float)sunSize[2], 1.0f / (float)sunSize[3] );
break;
case UT_GAMMATABLE:
u->SetValue( &tr.gamma_table[0][0], 64 );
break;
case UT_LIGHTDIR:
if( pl )
{
if( pl->type == LIGHT_DIRECTIONAL ) lightdir = -tr.sky_normal;
else lightdir = pl->frustum.GetPlane( FRUSTUM_FAR )->normal;
u->SetValue( lightdir.x, lightdir.y, lightdir.z, pl->fov );
}
break;
case UT_LIGHTDIFFUSE:
if( pl ) u->SetValue( pl->color.x, pl->color.y, pl->color.z );
break;
case UT_LIGHTORIGIN:
if( pl ) u->SetValue( pl->origin.x, pl->origin.y, pl->origin.z, ( 1.0f / pl->radius ));
break;
case UT_LIGHTVIEWPROJMATRIX:
if( pl )
{
GLfloat gl_lightViewProjMatrix[16];
pl->lightviewProjMatrix.CopyToArray( gl_lightViewProjMatrix );
u->SetValue( &gl_lightViewProjMatrix[0] );
}
break;
case UT_DIFFUSEFACTOR:
u->SetValue( tr.diffuseFactor );
break;
case UT_AMBIENTFACTOR:
if( pl && pl->type == LIGHT_DIRECTIONAL )
u->SetValue( tr.sun_ambient );
else u->SetValue( tr.ambientFactor );
break;
case UT_SUNREFRACT:
u->SetValue( tr.sun_refract );
break;
case UT_LIGHTNUMS0:
u->SetValue( (float)grass->lights[0], (float)grass->lights[1], (float)grass->lights[2], (float)grass->lights[3] );
break;
case UT_LIGHTNUMS1:
u->SetValue( (float)grass->lights[4], (float)grass->lights[5], (float)grass->lights[6], (float)grass->lights[7] );
break;
case UT_GRASSPARAMS:
u->SetValue( m_flGrassFadeStart, m_flGrassFadeDist, m_flGrassFadeEnd );
break;
default:
ALERT( at_error, "%s: unhandled uniform %s\n", RI->currentshader->name, u->name );
break;
}
}
}
/*
=================
R_SortGrassMeshes
sort by texture
=================
*/
static int R_SortGrassMeshes( struct grass_s *const *pa, struct grass_s *const *pb )
{
grass_t *a = (grass_t *)*pa;
grass_t *b = (grass_t *)*pb;
if( a->texture > b->texture )
return -1;
if( a->texture < b->texture )
return 1;
return 0;
}
/*
================
R_RenderGrassOnList
rendering the grass
================
*/
void R_RenderGrassOnList( void )
{
word hCachedMatrix = -1;
word hLastShader = -1;
word hCurrentShader;
byte hLastTexture = -1;
byte cached_lights[MAXDYNLIGHTS] = { 255 };
bool update_params = false;
int parms_updated = 0;
if( !RI->frame.grass_list.Count())
return; // don't waste time
if( !FBitSet( RI->params, RP_DEFERREDLIGHT ))
GL_AlphaTest( GL_TRUE );
else GL_AlphaTest( GL_FALSE );
pglAlphaFunc( GL_GREATER, r_grass_alpha->value );
GL_Cull( GL_NONE ); // grass is double-sided poly
GL_DepthMask( GL_TRUE );
GL_Blend( GL_FALSE );
// sorting list to reduce shader switches
if( !CVAR_TO_BOOL( cv_nosort ))
RI->frame.grass_list.Sort( R_SortGrassMeshes );
for( int i = 0; i < RI->frame.grass_list.Count(); i++ )
{
grass_t *g = RI->frame.grass_list[i];
if( FBitSet( RI->params, RP_DEFERREDSCENE ))
hCurrentShader = g->deferredScene.GetHandle();
else if( FBitSet( RI->params, RP_DEFERREDLIGHT ))
hCurrentShader = g->deferredLight.GetHandle();
else hCurrentShader = g->forwardScene.GetHandle();
if( !hCurrentShader ) continue;
if( FBitSet( RI->params, RP_DEFERREDSCENE|RP_DEFERREDLIGHT ) && memcmp( g->lights, cached_lights, MAXDYNLIGHTS ))
{
memcpy( cached_lights, g->lights, MAXDYNLIGHTS );
update_params = true;
}
if( hCachedMatrix != g->hCachedMatrix )
{
hCachedMatrix = g->hCachedMatrix;
update_params = true;
}
if( FBitSet( g->flags, FRGASS_SKYENTITY ))
{
GL_DepthRange( 0.8f, 0.9f );
GL_ClipPlane( false );
}
else
{
GL_DepthRange( gldepthmin, gldepthmax );
GL_ClipPlane( true );
}
if( hLastShader != hCurrentShader )
{
hLastShader = hCurrentShader;
update_params = true;
}
if( hLastTexture != g->texture )
{
hLastTexture = g->texture;
update_params = true;
}
if( update_params )
{
R_SetGrassUniforms( hCurrentShader, g );
update_params = false;
}
R_DrawGrassMeshFromBuffer( g );
}
// restore old binding
GL_DepthRange( gldepthmin, gldepthmax );
GL_ClipPlane( true );
GL_Cull( GL_FRONT );
}
/*
================
R_DrawLightForGrass
draw lighting for grass
================
*/
void R_DrawLightForGrass( CDynLight *pl )
{
word hCachedMatrix = -1;
word hLastShader = -1;
byte hLastTexture = -1;
bool update_params = false;
word hLightShader;
if( !RI->frame.light_grass.Count())
return; // don't waste time
GL_AlphaTest( GL_TRUE );
pglAlphaFunc( GL_GREATER, r_grass_alpha->value );
GL_Cull( GL_NONE );
// sorting list to reduce shader switches
if( !CVAR_TO_BOOL( cv_nosort ))
RI->frame.light_grass.Sort( R_SortGrassMeshes );
for( int i = 0; i < RI->frame.light_grass.Count(); i++ )
{
grass_t *g = RI->frame.light_grass[i];
switch( pl->type )
{
case LIGHT_SPOT:
hLightShader = g->forwardLightSpot.GetHandle();
break;
case LIGHT_OMNI:
hLightShader = g->forwardLightOmni.GetHandle();
break;
case LIGHT_DIRECTIONAL:
hLightShader = g->forwardLightProj.GetHandle();
break;
default:
hLightShader = 0;
break;
}
if( !hLightShader ) continue;
if( hCachedMatrix != g->hCachedMatrix )
{
hCachedMatrix = g->hCachedMatrix;
update_params = true;
}
if( hLastShader != hLightShader )
{
hLastShader = hLightShader;
update_params = true;
}
if( hLastTexture != g->texture )
{
hLastTexture = g->texture;
update_params = true;
}
if( update_params )
{
R_SetGrassUniforms( hLightShader, g );
update_params = false;
}
R_DrawGrassMeshFromBuffer( g );
}
GL_Cull( GL_FRONT );
}
/*
================
R_RenderShadowGrassOnList
depth pass
================
*/
void R_RenderShadowGrassOnList( void )
{
word hCachedMatrix = -1;
word hLastShader = -1;
byte hLastTexture = -1;
bool update_params = false;
if( !RI->frame.grass_list.Count())
return; // don't waste time
GL_AlphaTest( GL_TRUE );
pglAlphaFunc( GL_GREATER, r_grass_alpha->value );
GL_Cull( GL_NONE ); // grass is double-sided poly
// sorting list to reduce shader switches
if( !CVAR_TO_BOOL( cv_nosort ))
RI->frame.grass_list.Sort( R_SortGrassMeshes );
for( int i = 0; i < RI->frame.grass_list.Count(); i++ )
{
grass_t *g = RI->frame.grass_list[i];
if( hCachedMatrix != g->hCachedMatrix )
{
hCachedMatrix = g->hCachedMatrix;
update_params = true;
}
if( hLastShader != g->forwardDepth.GetHandle() )
{
hLastShader = g->forwardDepth.GetHandle();
update_params = true;
}
if( hLastTexture != g->texture )
{
hLastTexture = g->texture;
update_params = true;
}
if( update_params )
{
R_SetGrassUniforms( g->forwardDepth.GetHandle(), g );
update_params = false;
}
R_DrawGrassMeshFromBuffer( g );
}
// restore old binding
GL_Cull( GL_FRONT );
}
/*
================
R_GrassTextureForName
find or add unique texture for grass
================
*/
byte R_GrassTextureForName( const char *name )
{
for( byte i = 0; i < GRASS_TEXTURES && grasstexs[i].name[0]; i++ )
{
if( !Q_stricmp( grasstexs[i].name, name ))
return i; // found
}
// allocate a new one
Q_strncpy( grasstexs[i].name, name, sizeof( grasstexs[i].name ));
grasstexs[i].gl_texturenum = LOAD_TEXTURE( name, NULL, 0, TF_GRASS );
if( !grasstexs[i].gl_texturenum )
{
ALERT( at_warning, "couldn't load %s\n", name );
grasstexs[i].gl_texturenum = tr.defaultTexture;
}
return i;
}
/*
================
R_GrassInitForSurface
lookup all the descriptions
because this system allow many
sorts of grass per one surface
================
*/
void R_GrassInitForSurface( msurface_t *surf )
{
if( !surf || !surf->texinfo || !surf->texinfo->texture )
return; // some bad polygons
mextrasurf_t *es = surf->info;
bvert_t *v0, *v1, *v2;
for( int i = 0; i < grassInfo.Count(); i++ )
{
grassentry_t *entry = &grassInfo[i];
if( !Mod_CheckLayerNameForSurf( surf, entry->name ))
continue;
if( !FBitSet( surf->flags, SURF_PLANEBACK ) && surf->plane->normal.z < 0.40f )
continue; // vertical too much
if( FBitSet( surf->flags, SURF_PLANEBACK ) && surf->plane->normal.z > -0.40f )
continue; // vertical too much
// turn the face into a bunch of polygons, and compute the area of each
v0 = &world->vertexes[es->firstvertex];
int count = 0;
for( int j = 1; j < es->numverts - 1; j++ )
{
v1 = &world->vertexes[es->firstvertex+j];
v2 = &world->vertexes[es->firstvertex+j+1];
// compute two triangle edges
Vector e1 = v1->vertex - v0->vertex;
Vector e2 = v2->vertex - v0->vertex;
// compute the triangle area
Vector areaVec = CrossProduct( e1, e2 );
float normalLength = areaVec.Length();
float area = 0.5f * normalLength;
// compute the number of samples to take
count += area * entry->density * DENSITY_FACTOR;
}
if( count ) es->grasscount++; // mesh added
if( es->grasscount > 0 ) // at least one bush was detected
SetBits( world->features, WORLD_HAS_GRASS );
}
}
/*
================
R_GrassSetupFrame
clear the chains
================
*/
void R_GrassSetupFrame( void )
{
m_flGrassFadeStart = r_grass_fade_start->value;
if( m_flGrassFadeStart < GRASS_ANIM_DIST )
m_flGrassFadeStart = GRASS_ANIM_DIST;
m_flGrassFadeDist = max( 0.0f, r_grass_fade_dist->value );
m_flGrassFadeEnd = m_flGrassFadeStart + m_flGrassFadeDist;
}
/*
================
R_GrassShaderSceneForward
compute albedo with static lighting
================
*/
static word R_GrassShaderSceneForward( msurface_t *s, grass_t *g )
{
char glname[64];
char options[MAX_OPTIONS_LENGTH];
mextrasurf_t *es = s->info;
if( g->forwardScene.IsValid( ))
return g->forwardScene.GetHandle(); // valid
ASSERT( RI->currententity != NULL );
Q_strncpy( glname, "forward/scene_grass", sizeof( glname ));
memset( options, 0, sizeof( options ));
material_t *mat = R_TextureAnimation( s )->material;
if( FBitSet( mat->flags, BRUSH_FULLBRIGHT ) || R_FullBright( ))
{
GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" );
}
else
{
if( r_lightmap->value > 0.0f && r_lightmap->value <= 2.0f )
{
if( r_lightmap->value == 1.0f && worldmodel->lightdata )
GL_AddShaderDirective( options, "LIGHTMAP_DEBUG" );
else if( r_lightmap->value == 2.0f && FBitSet( world->features, WORLD_HAS_DELUXEMAP ))
GL_AddShaderDirective( options, "LIGHTVEC_DEBUG" );
}
if( es->normals != NULL && r_grass->value > 1.0f )
GL_AddShaderDirective( options, "HAS_DELUXEMAP" );
// process lightstyles
for( int i = 0; i < MAXLIGHTMAPS && s->styles[i] != LS_NONE; i++ )
GL_AddShaderDirective( options, va( "APPLY_STYLE%i", i ));
}
if( RI->currententity->curstate.renderfx != SKYBOX_ENTITY )
GL_AddShaderDirective( options, "APPLY_FADE_DIST" );
if( tr.fogEnabled )
GL_AddShaderDirective( options, "APPLY_FOG_EXP" );
word shaderNum = GL_FindUberShader( glname, options );
if( !shaderNum )
{
SetBits( g->flags, FGRASS_NODRAW );
return 0; // something bad happens
}
g->forwardScene.SetShader( shaderNum );
ClearBits( g->flags, FGRASS_NODRAW );
return shaderNum;
}
/*
================
R_GrassShaderLightForward
compute dynamic lighting
================
*/
static word R_GrassShaderLightForward( CDynLight *dl, grass_t *g )
{
char glname[64];
char options[MAX_OPTIONS_LENGTH];
switch( dl->type )
{
case LIGHT_SPOT:
if( g->forwardLightSpot.IsValid( ))
return g->forwardLightSpot.GetHandle(); // valid
break;
case LIGHT_OMNI:
if( g->forwardLightOmni.IsValid( ))
return g->forwardLightOmni.GetHandle(); // valid
break;
case LIGHT_DIRECTIONAL:
if( g->forwardLightProj.IsValid( ))
return g->forwardLightProj.GetHandle(); // valid
break;
}
Q_strncpy( glname, "forward/light_grass", sizeof( glname ));
memset( options, 0, sizeof( options ));
switch( dl->type )
{
case LIGHT_SPOT:
GL_AddShaderDirective( options, "LIGHT_SPOT" );
break;
case LIGHT_OMNI:
GL_AddShaderDirective( options, "LIGHT_OMNI" );
break;
case LIGHT_DIRECTIONAL:
GL_AddShaderDirective( options, "LIGHT_PROJ" );
break;
}
if( RI->currententity->curstate.renderfx != SKYBOX_ENTITY )
GL_AddShaderDirective( options, "APPLY_FADE_DIST" );
if( CVAR_TO_BOOL( r_shadows ) && !FBitSet( dl->flags, DLF_NOSHADOWS ))
{
// shadow cubemaps only support if GL_EXT_gpu_shader4 is support
if( dl->type == LIGHT_DIRECTIONAL && CVAR_TO_BOOL( r_sunshadows ))
GL_AddShaderDirective( options, "APPLY_SHADOW" );
else if( dl->type == LIGHT_SPOT || GL_Support( R_EXT_GPU_SHADER4 ))
GL_AddShaderDirective( options, "APPLY_SHADOW" );
}
word shaderNum = GL_FindUberShader( glname, options );
if( !shaderNum )
{
if( dl->type == LIGHT_DIRECTIONAL )
SetBits( g->flags, FGRASS_NOSUNLIGHT );
else SetBits( g->flags, FGRASS_NODLIGHT );
return 0; // something bad happens
}
// done
switch( dl->type )
{
case LIGHT_SPOT:
g->forwardLightSpot.SetShader( shaderNum );
ClearBits( g->flags, FGRASS_NODLIGHT );
break;
case LIGHT_OMNI:
g->forwardLightOmni.SetShader( shaderNum );
ClearBits( g->flags, FGRASS_NODLIGHT );
break;
case LIGHT_DIRECTIONAL:
g->forwardLightProj.SetShader( shaderNum );
ClearBits( g->flags, FGRASS_NOSUNLIGHT );
break;
}
return shaderNum;
}
/*
================
R_GrassShaderSceneDeferred
grass scene deferred
================
*/
word R_GrassShaderSceneDeferred( msurface_t *s, grass_t *g )
{
char glname[64];
char options[MAX_OPTIONS_LENGTH];
mextrasurf_t *es = s->info;
if( g->deferredScene.IsValid( ))
return g->deferredScene.GetHandle(); // valid
Q_strncpy( glname, "deferred/scene_grass", sizeof( glname ));
memset( options, 0, sizeof( options ));
material_t *mat = s->texinfo->texture->material;
if( RI->currententity->curstate.renderfx != SKYBOX_ENTITY )
GL_AddShaderDirective( options, "APPLY_FADE_DIST" );
word shaderNum = GL_FindUberShader( glname, options );
if( !shaderNum )
{
SetBits( g->flags, FGRASS_NODRAW );
return 0; // something bad happens
}
ClearBits( g->flags, FGRASS_NODRAW );
g->deferredScene.SetShader( shaderNum );
return shaderNum;
}
/*
================
R_GrassShaderLightDeferred
grass light deferred
================
*/
word R_GrassShaderLightDeferred( msurface_t *s, grass_t *g )
{
char glname[64];
char options[MAX_OPTIONS_LENGTH];
mextrasurf_t *es = s->info;
if( g->deferredLight.IsValid( ))
return g->deferredLight.GetHandle(); // valid
Q_strncpy( glname, "deferred/light_grass", sizeof( glname ));
memset( options, 0, sizeof( options ));
material_t *mat = s->texinfo->texture->material;
if( RI->currententity->curstate.renderfx != SKYBOX_ENTITY )
GL_AddShaderDirective( options, "APPLY_FADE_DIST" );
word shaderNum = GL_FindUberShader( glname, options );
if( !shaderNum )
{
SetBits( g->flags, FGRASS_NODRAW );
return 0; // something bad happens
}
g->deferredLight.SetShader( shaderNum );
ClearBits( g->flags, FGRASS_NODRAW );
return shaderNum;
}
/*
================
R_GrassShaderSceneDepth
return grass depth-shader
================
*/
static word R_GrassShaderSceneDepth( msurface_t *s, grass_t *g )
{
char glname[64];
char options[MAX_OPTIONS_LENGTH];
mextrasurf_t *es = s->info;
if( g->forwardDepth.IsValid( ))
return g->forwardDepth.GetHandle(); // valid
ASSERT( RI->currententity != NULL );
Q_strncpy( glname, "forward/depth_grass", sizeof( glname ));
memset( options, 0, sizeof( options ));
if( RI->currententity->curstate.renderfx != SKYBOX_ENTITY )
GL_AddShaderDirective( options, "APPLY_FADE_DIST" );
word shaderNum = GL_FindUberShader( glname, options );
if( !shaderNum ) return 0; // something bad happens
g->forwardDepth.SetShader( shaderNum );
return shaderNum;
}
/*
================
R_PrecacheGrass
precache grass in the world
================
*/
void R_PrecacheGrass( msurface_t *s, mextraleaf_t *leaf )
{
mextrasurf_t *es = s->info;
grasshdr_t *hdr = es->grass;
cl_entity_t *e = GET_ENTITY( 0 );
if( !es->grasscount ) return; // no grass for this face
if( hdr )
{
if( FBitSet( s->flags, SURF_GRASS_UPDATE ))
{
// rebuild mesh with new gamma
R_RemoveGrassForSurface( es );
}
else
{
// already created?
return;
}
}
float curdist = VectorDistance( tr.cached_vieworigin, es->origin );
// but grass that attached to sky entities will be ignoring distance
if( curdist > m_flGrassFadeEnd && ( e->curstate.renderfx != SKYBOX_ENTITY ))
return; // too far
// initialize grass for surface
R_ConstructGrassForSurface( s, es );
hdr = es->grass; // refresh the pointer
if( hdr && leaf )
{
// prevent to expand leafs too much
AddPointToBounds( hdr->mins, leaf->mins, leaf->maxs, LEAF_MAX_EXPAND );
AddPointToBounds( hdr->maxs, leaf->mins, leaf->maxs, LEAF_MAX_EXPAND );
}
}
/*
================
R_AddGrassToDrawList
build the visible grass list
================
*/
void R_AddGrassToDrawList( msurface_t *s, drawlist_t drawlist_type )
{
if( !FBitSet( world->features, WORLD_HAS_GRASS ))
return; // completely has no grass
if( !CVAR_TO_BOOL( r_grass ) || FBitSet( RI->params, RP_NOGRASS ))
return; // disabled by request
if( drawlist_type == DRAWLIST_SHADOW && !CVAR_TO_BOOL( r_grass_shadows ))
return;// don't cast shadows from grass
if( drawlist_type == DRAWLIST_LIGHT && !CVAR_TO_BOOL( r_grass_lighting ))
return;// don't cast shadows from grass
mextrasurf_t *es = s->info;
cl_entity_t *e = es->parent;
Vector absmin, absmax;
// do simple culling by distance
if( es->grasscount <= 0 ) return; // surface doesn't contain grass
float curdist = VectorDistance( es->origin, tr.cached_vieworigin );
// but grass that attached to sky entities will be ignoring distance
if( curdist > m_flGrassFadeEnd && ( e->curstate.renderfx != SKYBOX_ENTITY ))
return; // too far
// rebuild mesh with new gamma
if( es->grass && FBitSet( s->flags, SURF_GRASS_UPDATE ))
R_RemoveGrassForSurface( es );
if( !es->grass && RP_NORMALPASS( ))
R_ConstructGrassForSurface( s, es );
grasshdr_t *hdr = es->grass;
if( !hdr ) return; // face completely missed grass or creation was failed
gl_state_t *glm = GL_GetCache( e->hCachedMatrix );
if( e->hCachedMatrix == WORLD_MATRIX )
{
// default case
absmin = hdr->mins;
absmax = hdr->maxs;
}
else
{
// moving entity
TransformAABB( glm->transform, hdr->mins, hdr->maxs, absmin, absmax );
}
CFrustum *frustum = NULL;
if( RI->currentlight != NULL )
frustum = &RI->currentlight->frustum;
else frustum = &RI->view.frustum;
if( frustum && frustum->CullBox( absmin, absmax ))
return;
// NOTE: at this point we have surface that passed visibility and frustum tests
// each mesh should be added individually
for( int i = 0; i < hdr->count; i++ )
{
grass_t *grass = &hdr->g[i];
if( e->curstate.renderfx == SKYBOX_ENTITY )
SetBits( grass->flags, FRGASS_SKYENTITY );
else ClearBits( grass->flags, FRGASS_SKYENTITY );
grass->hCachedMatrix = e->hCachedMatrix;
switch( drawlist_type )
{
case DRAWLIST_SOLID:
if( FBitSet( grass->flags, FGRASS_NODRAW ))
continue;
if( FBitSet( RI->params, RP_DEFERREDSCENE|RP_DEFERREDLIGHT ))
{
// precache shaders
R_GrassShaderSceneDeferred( s, grass );
R_GrassShaderLightDeferred( s, grass );
}
else R_GrassShaderSceneForward( s, grass );
RI->frame.grass_list.AddToTail( grass );
break;
case DRAWLIST_SHADOW:
if( FBitSet( grass->flags, FGRASS_NODRAW ))
continue;
R_GrassShaderSceneDepth( s, grass );
RI->frame.grass_list.AddToTail( grass );
break;
case DRAWLIST_LIGHT:
if( RI->currentlight->type == LIGHT_DIRECTIONAL )
{
if( FBitSet( grass->flags, FGRASS_NOSUNLIGHT ))
continue;
}
else
{
if( FBitSet( grass->flags, FGRASS_NODLIGHT ))
continue;
}
R_GrassShaderLightForward( RI->currentlight, grass );
RI->frame.light_grass.AddToTail( grass );
break;
default: // invalid mode
return;
}
}
}
/*
================
R_UnloadFarGrass
release far VBO's
================
*/
void R_UnloadFarGrass( void )
{
if( !FBitSet( world->features, WORLD_HAS_GRASS ))
return; // don't waste time
if( ++tr.grassunloadframe < 300 )
return; // run every three seconds
// check surfaces
for( int i = 0; i < worldmodel->numsurfaces; i++ )
{
msurface_t *surf = &worldmodel->surfaces[i];
mextrasurf_t *es = surf->info;
if( !es->grasscount ) continue; // surface doesn't contain grass
float curdist = VectorDistance( tr.cached_vieworigin, es->origin );
if( curdist > m_flGrassFadeEnd )
{
if( curdist > ( m_flGrassFadeEnd * 2.0f ) && es->grass )
R_RemoveGrassForSurface( es );
}
}
tr.grassunloadframe = 0;
}
/*
================
R_GrassInit
parse grass definitions and load textures
================
*/
void R_GrassInit( void )
{
static int random_seed = 1; // starts from 1
char *afile = (char *)gEngfuncs.COM_LoadFile( "gfx/grass/grassinfo.txt", 5, NULL );
if( !afile ) ALERT( at_error, "couldn't load grassinfo.txt\n" );
// remove grass description from the pervious map
grassInfo.Purge();
memset( grasstexs, 0, sizeof( grasstexs ));
if( afile )
{
grassentry_t entry;
char *pfile = afile;
int parse_line = 1;
char token[1024];
while(( pfile = COM_ParseFile( pfile, token )) != NULL )
{
Q_strncpy( entry.name, token, sizeof( entry.name ));
pfile = COM_ParseLine( pfile, token );
if( !pfile )
{
ALERT( at_error, "R_GrassInit: missed grass texture path at line %i\n", parse_line );
parse_line++;
continue;
}
entry.texture = R_GrassTextureForName( token );
pfile = COM_ParseLine( pfile, token );
if( !pfile )
{
ALERT( at_error, "R_GrassInit: missed grass density at line %i\n", parse_line );
parse_line++;
continue;
}
entry.density = Q_atof( token );
entry.density = bound( 0.1f, entry.density, 512.0f );
pfile = COM_ParseLine( pfile, token );
if( !pfile )
{
ALERT( at_error, "R_GrassInit: missed grass min scale at line %i\n", parse_line );
parse_line++;
continue;
}
entry.min = Q_atof( token );
entry.min = bound( 0.01f, entry.min, 100.0f );
pfile = COM_ParseLine( pfile, token );
if( !pfile )
{
ALERT( at_error, "R_GrassInit: missed grass max scale at line %i\n", parse_line );
parse_line++;
continue;
}
entry.max = Q_atof( token );
entry.max = bound( entry.min, entry.max, 100.0f );
if( entry.min > entry.max ) entry.min = entry.max;
pfile = COM_ParseLine( pfile, token );
if( pfile )
{
// seed is optional
entry.seed = Q_atoi( token );
entry.seed = max( 1, entry.seed );
}
else entry.seed = random_seed++;
grassInfo.AddToTail( entry );
parse_line++;
}
gEngfuncs.COM_FreeFile( afile );
}
}
/*
================
R_GrassShutdown
prepare grass system to shutdown
================
*/
void R_GrassShutdown( void )
{
// release all grass textures
for( int i = 0; i < GRASS_TEXTURES; i++ )
{
if( !grasstexs[i].gl_texturenum )
continue;
FREE_TEXTURE( grasstexs[i].gl_texturenum );
}
}