diff --git a/ref/gl/gl_local.h b/ref/gl/gl_local.h index 38ca3d55..a0df7c55 100644 --- a/ref/gl/gl_local.h +++ b/ref/gl/gl_local.h @@ -489,6 +489,11 @@ void R_ClearSkyBox( void ); void R_DrawSkyBox( void ); void R_DrawClouds( void ); void EmitWaterPolys( msurface_t *warp, qboolean reverse ); +void R_InitRipples( void ); +void R_ResetRipples( void ); +void R_AnimateRipples( void ); +void R_UpdateRippleTexParams( void ); +void R_UploadRipples( texture_t *image ); // // gl_vgui.c @@ -750,6 +755,9 @@ extern convar_t r_vbo; extern convar_t r_vbo_dlightmode; extern convar_t r_studio_sort_textures; extern convar_t r_studio_drawelements; +extern convar_t r_ripple; +extern convar_t r_ripple_updatetime; +extern convar_t r_ripple_spawntime; // // engine shared convars diff --git a/ref/gl/gl_opengl.c b/ref/gl/gl_opengl.c index ee25dfb4..6f31c8a8 100644 --- a/ref/gl/gl_opengl.c +++ b/ref/gl/gl_opengl.c @@ -30,6 +30,10 @@ CVAR_DEFINE_AUTO( r_traceglow, "0", FCVAR_GLCONFIG, "cull flares behind models" CVAR_DEFINE_AUTO( gl_round_down, "2", FCVAR_GLCONFIG|FCVAR_READ_ONLY, "round texture sizes to nearest POT value" ); CVAR_DEFINE( r_vbo, "gl_vbo", "0", FCVAR_ARCHIVE, "draw world using VBO (known to be glitchy)" ); CVAR_DEFINE( r_vbo_dlightmode, "gl_vbo_dlightmode", "1", FCVAR_ARCHIVE, "vbo dlight rendering mode (0-1)" ); +CVAR_DEFINE_AUTO( r_ripple, "0", FCVAR_GLCONFIG, "enable software-like water texture ripple simulation" ); +CVAR_DEFINE_AUTO( r_ripple_updatetime, "0.05", FCVAR_GLCONFIG, "how fast ripple simulation is" ); +CVAR_DEFINE_AUTO( r_ripple_spawntime, "0.1", FCVAR_GLCONFIG, "how fast new ripples spawn" ); + DEFINE_ENGINE_SHARED_CVAR_LIST() @@ -1186,6 +1190,9 @@ void GL_InitCommands( void ) gEngfuncs.Cvar_RegisterVariable( &r_traceglow ); gEngfuncs.Cvar_RegisterVariable( &r_studio_sort_textures ); gEngfuncs.Cvar_RegisterVariable( &r_studio_drawelements ); + gEngfuncs.Cvar_RegisterVariable( &r_ripple ); + gEngfuncs.Cvar_RegisterVariable( &r_ripple_updatetime ); + gEngfuncs.Cvar_RegisterVariable( &r_ripple_spawntime ); gEngfuncs.Cvar_RegisterVariable( &gl_extensions ); gEngfuncs.Cvar_RegisterVariable( &gl_texture_nearest ); diff --git a/ref/gl/gl_warp.c b/ref/gl/gl_warp.c index 30dbe1b9..cf0d48a4 100644 --- a/ref/gl/gl_warp.c +++ b/ref/gl/gl_warp.c @@ -62,6 +62,29 @@ static float r_turbsin[] = #include "warpsin.h" }; +#define RIPPLES_CACHEWIDTH_BITS 7 +#define RIPPLES_CACHEWIDTH ( 1 << RIPPLES_CACHEWIDTH_BITS ) +#define RIPPLES_CACHEWIDTH_MASK (( RIPPLES_CACHEWIDTH ) - 1 ) +#define RIPPLES_TEXSIZE ( RIPPLES_CACHEWIDTH * RIPPLES_CACHEWIDTH ) +#define RIPPLES_TEXSIZE_MASK ( RIPPLES_TEXSIZE - 1 ) + +STATIC_ASSERT( RIPPLES_TEXSIZE == 0x4000, "fix the algorithm to work with custom resolution" ); + +static struct +{ + double time; + double oldtime; + + short *curbuf, *oldbuf; + short buf[2][RIPPLES_TEXSIZE]; + qboolean update; + + uint32_t texture[RIPPLES_TEXSIZE]; + int gl_texturenum; + int rippletexturenum; + int texturescale; // not all textures are 128x128, scale the texcoords down +} g_ripple; + static qboolean CheckSkybox( const char *name, char out[6][MAX_STRING] ) { const char *skybox_ext[3] = { "dds", "tga", "bmp" }; @@ -767,7 +790,9 @@ void EmitWaterPolys( msurface_t *warp, qboolean reverse ) if( !warp->polys ) return; // set the current waveheight - if( warp->polys->verts[0][2] >= RI.vieworg[2] ) + if( r_ripple.value ) + waveHeight = 0; + else if( warp->polys->verts[0][2] >= RI.vieworg[2] ) waveHeight = -RI.currententity->curstate.scale; else waveHeight = RI.currententity->curstate.scale; @@ -799,10 +824,18 @@ void EmitWaterPolys( msurface_t *warp, qboolean reverse ) os = v[3]; ot = v[4]; - s = os + r_turbsin[(int)((ot * 0.125f + gpGlobals->time) * TURBSCALE) & 255]; - s *= ( 1.0f / SUBDIVIDE_SIZE ); + if( !r_ripple.value ) + { + s = os + r_turbsin[(int)((ot * 0.125f + gpGlobals->time) * TURBSCALE) & 255]; + t = ot + r_turbsin[(int)((os * 0.125f + gpGlobals->time) * TURBSCALE) & 255]; + } + else + { + s = os / g_ripple.texturescale; + t = ot / g_ripple.texturescale; + } - t = ot + r_turbsin[(int)((os * 0.125f + gpGlobals->time) * TURBSCALE) & 255]; + s *= ( 1.0f / SUBDIVIDE_SIZE ); t *= ( 1.0f / SUBDIVIDE_SIZE ); pglTexCoord2f( s, t ); @@ -822,3 +855,180 @@ void EmitWaterPolys( msurface_t *warp, qboolean reverse ) GL_SetupFogColorForSurfaces(); } + +/* +============================================================ + + HALF-LIFE SOFTWARE WATER + +============================================================ +*/ +void R_ResetRipples( void ) +{ + g_ripple.curbuf = g_ripple.buf[0]; + g_ripple.oldbuf = g_ripple.buf[1]; + g_ripple.time = g_ripple.oldtime = gpGlobals->time - 0.1; + memset( g_ripple.buf, 0, sizeof( g_ripple.buf )); +} + +void R_InitRipples( void ) +{ + rgbdata_t pic = { 0 }; + + pic.width = pic.height = RIPPLES_CACHEWIDTH; + pic.depth = 1; + pic.flags = IMAGE_HAS_COLOR; + pic.buffer = (byte *)g_ripple.texture; + pic.type = PF_RGBA_32; + pic.size = sizeof( g_ripple.texture ); + pic.numMips = 1; + memset( pic.buffer, 0, pic.size ); + + g_ripple.rippletexturenum = GL_LoadTextureInternal( "*rippletex", &pic, TF_NOMIPMAP ); +} + +static void R_SwapBufs( void ) +{ + short *tempbufp = g_ripple.curbuf; + g_ripple.curbuf = g_ripple.oldbuf; + g_ripple.oldbuf = tempbufp; +} + +static void R_SpawnNewRipple( int x, int y, short val ) +{ +#define PIXEL( x, y ) ((( x ) & RIPPLES_CACHEWIDTH_MASK ) + ((( y ) & RIPPLES_CACHEWIDTH_MASK) << 7 )) + g_ripple.oldbuf[PIXEL( x, y )] += val; + + val >>= 2; + g_ripple.oldbuf[PIXEL( x + 1, y )] += val; + g_ripple.oldbuf[PIXEL( x - 1, y )] += val; + g_ripple.oldbuf[PIXEL( x, y + 1 )] += val; + g_ripple.oldbuf[PIXEL( x, y - 1 )] += val; +#undef PIXEL +} + +static void R_RunRipplesAnimation( const short *oldbuf, short *pbuf ) +{ + unsigned int i = 0; + + for( i = 0; i < RIPPLES_TEXSIZE; i++, pbuf++ ) + { + int p = RIPPLES_CACHEWIDTH + i; + int val; + + val = ((int)oldbuf[(p - ( RIPPLES_CACHEWIDTH * 2 )) & RIPPLES_TEXSIZE_MASK] + + (int)oldbuf[(p - ( RIPPLES_CACHEWIDTH + 1 )) & RIPPLES_TEXSIZE_MASK] + + (int)oldbuf[(p - ( RIPPLES_CACHEWIDTH - 1 )) & RIPPLES_TEXSIZE_MASK] + + (int)oldbuf[p & RIPPLES_TEXSIZE_MASK]) >> 1; + + val -= *pbuf; + + *pbuf = (short)val - (short)( val >> 6 ); + } +} + +static int MostSignificantBit( unsigned int v ) +{ +#if __GNUC__ + return 31 - __builtin_clz( v ); +#else + int i; + for( i = 0, v >>= 1; v; v >>= 1, i++ ); + return i; +#endif +} + +void R_AnimateRipples( void ) +{ + double frametime = gpGlobals->time - g_ripple.time; + + g_ripple.update = r_ripple.value && frametime >= r_ripple_updatetime.value; + + if( !g_ripple.update ) + return; + + g_ripple.time = gpGlobals->time; + + R_SwapBufs(); + + if( g_ripple.time - g_ripple.oldtime > r_ripple_spawntime.value ) + { + int x, y, val; + + g_ripple.oldtime = g_ripple.time; + + x = gEngfuncs.COM_RandomLong( 0, 0x7fff ); + y = gEngfuncs.COM_RandomLong( 0, 0x7fff ); + val = gEngfuncs.COM_RandomLong( 0, 0x3ff ); + + R_SpawnNewRipple( x, y, val ); + } + + R_RunRipplesAnimation( g_ripple.oldbuf, g_ripple.curbuf ); +} + +void R_UpdateRippleTexParams( void ) +{ + gl_texture_t *tex = R_GetTexture( g_ripple.rippletexturenum ); + + GL_Bind( XASH_TEXTURE0, g_ripple.rippletexturenum ); + + if( gl_texture_nearest.value ) + { + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + } + else + { + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } +} + +void R_UploadRipples( texture_t *image ) +{ + gl_texture_t *glt; + uint32_t *pixels; + int v, wmask, hmask; + + // discard unuseful textures + if( !r_ripple.value || image->width > RIPPLES_CACHEWIDTH || image->width != image->height ) + { + GL_Bind( XASH_TEXTURE0, image->gl_texturenum ); + return; + } + + glt = R_GetTexture( image->gl_texturenum ); + if( !glt || !glt->original || !glt->original->buffer || !FBitSet( glt->flags, TF_EXPAND_SOURCE )) + { + GL_Bind( XASH_TEXTURE0, image->gl_texturenum ); + return; + } + + GL_Bind( XASH_TEXTURE0, g_ripple.rippletexturenum ); + + // no updates this frame + if( !g_ripple.update && image->gl_texturenum == g_ripple.gl_texturenum ) + return; + + g_ripple.gl_texturenum = image->gl_texturenum; + g_ripple.texturescale = RIPPLES_CACHEWIDTH / image->width; + + pixels = (uint32_t *)glt->original->buffer; + v = MostSignificantBit( image->width ); + wmask = image->width - 1; + hmask = image->height - 1; + + for( int i = 0; i < RIPPLES_TEXSIZE; i++ ) + { + int val = g_ripple.curbuf[i]; + int x = ( val >> 4 ) + i; + int y = ( i >> 7 ) - ( val >> 4 ); + int pixel = ( x & wmask ) + (( y & hmask ) << ( v & 0x1f )); // ??? + + g_ripple.texture[i] = pixels[pixel]; + } + + pglTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, RIPPLES_CACHEWIDTH, RIPPLES_CACHEWIDTH, 0, + GL_RGBA, GL_UNSIGNED_BYTE, g_ripple.texture ); +}