From 855163296c2f4bb332f2faa29976ea63962f8138 Mon Sep 17 00:00:00 2001 From: Alan Date: Mon, 30 Oct 2023 13:32:43 -0500 Subject: [PATCH] Implemented software water rippling effect thanks to A1batross. Original pull request for FWGS here: https://github.com/FWGS/xash3d-fwgs/pull/1475 --- src/engine/client/gl_image.c | 5 + src/engine/client/gl_local.h | 12 ++ src/engine/client/gl_rmain.c | 4 + src/engine/client/gl_rmisc.c | 2 + src/engine/client/gl_rsurf.c | 12 +- src/engine/client/gl_vidnt.c | 8 ++ src/engine/client/gl_warp.c | 228 ++++++++++++++++++++++++++++++++- src/engine/common/mod_bmodel.c | 34 ++++- 8 files changed, 289 insertions(+), 16 deletions(-) diff --git a/src/engine/client/gl_image.c b/src/engine/client/gl_image.c index 14e4e50..bee2cca 100644 --- a/src/engine/client/gl_image.c +++ b/src/engine/client/gl_image.c @@ -353,6 +353,8 @@ void R_SetTextureParameters( void ) // change all the existing mipmapped texture objects for( i = 0; i < gl_numTextures; i++ ) GL_UpdateTextureParams( i ); + + R_UpdateRippleTexParams(); } /* @@ -2288,9 +2290,12 @@ void R_InitImages( void ) gl_texturesHashTable[gl_textures->hashValue] = gl_textures; gl_numTextures = 1; + R_InitRipples(); //placed this before texture parameters to prevent *default texture upload error in game. + // validate cvars R_SetTextureParameters(); GL_CreateInternalTextures(); + Cmd_AddCommand( "texturelist", R_TextureList_f, "display loaded textures list" ); } diff --git a/src/engine/client/gl_local.h b/src/engine/client/gl_local.h index c6835c1..1142c6b 100644 --- a/src/engine/client/gl_local.h +++ b/src/engine/client/gl_local.h @@ -733,4 +733,16 @@ extern convar_t* r_overbright; //magic nipples - overbright extern convar_t* r_lighting_lambert; extern convar_t* gammaboost; + +//software water +void R_InitRipples(void); +void R_ResetRipples(void); +void R_AnimateRipples(void); +void R_UpdateRippleTexParams(void); +void R_UploadRipples(texture_t* image); + +extern convar_t* r_ripple; +extern convar_t* r_ripple_updatetime; +extern convar_t* r_ripple_spawntime; + #endif//GL_LOCAL_H \ No newline at end of file diff --git a/src/engine/client/gl_rmain.c b/src/engine/client/gl_rmain.c index 88958f5..1dad5c0 100644 --- a/src/engine/client/gl_rmain.c +++ b/src/engine/client/gl_rmain.c @@ -1000,6 +1000,10 @@ void R_RenderScene( void ) R_CheckGLFog(); R_DrawWorld(); + + if (RI.drawWorld) + R_AnimateRipples(); + R_CheckFog(); CL_ExtraUpdate (); // don't let sound get messed up if going slow diff --git a/src/engine/client/gl_rmisc.c b/src/engine/client/gl_rmisc.c index b8331d7..6085162 100644 --- a/src/engine/client/gl_rmisc.c +++ b/src/engine/client/gl_rmisc.c @@ -200,4 +200,6 @@ void R_NewMap( void ) R_SetupSky( clgame.movevars.skyName ); GL_BuildLightmaps (); + + R_ResetRipples(); } \ No newline at end of file diff --git a/src/engine/client/gl_rsurf.c b/src/engine/client/gl_rsurf.c index a6fd89f..528449a 100644 --- a/src/engine/client/gl_rsurf.c +++ b/src/engine/client/gl_rsurf.c @@ -208,7 +208,7 @@ void GL_SetupFogColorForSurfaces( void ) return; if( RI.currententity && RI.currententity->curstate.rendermode == kRenderTransTexture ) - { + { pglFogfv( GL_FOG_COLOR, RI.fogColor ); return; } @@ -1095,14 +1095,17 @@ void R_RenderBrushPoly( msurface_t *fa, int cull_type ) // DEBUG: reset the mirror texture after drawing fa->info->mirrortexturenum = 0; } - else GL_Bind(GL_TEXTURE0, t->gl_texturenum); //if mirrors removed. leave just this line. + //else GL_Bind(GL_TEXTURE0, t->gl_texturenum); //if mirrors removed. leave just this line. - if( FBitSet( fa->flags, SURF_DRAWTURB )) + else if( FBitSet( fa->flags, SURF_DRAWTURB )) //might not need else { + R_UploadRipples(t); + // warp texture, no lightmaps EmitWaterPolys( fa, (cull_type == CULL_BACKSIDE)); return; } + else GL_Bind(GL_TEXTURE0, t->gl_texturenum); //if mirrors removed. leave just this line. if( t->fb_texturenum ) { @@ -1377,7 +1380,8 @@ void R_DrawWaterSurfaces( void ) continue; // set modulate mode explicitly - GL_Bind( GL_TEXTURE0, t->gl_texturenum ); + //GL_Bind( GL_TEXTURE0, t->gl_texturenum ); + R_UploadRipples(t); for( ; s; s = s->texturechain ) EmitWaterPolys( s, false ); diff --git a/src/engine/client/gl_vidnt.c b/src/engine/client/gl_vidnt.c index 92e6c7a..c6054d8 100644 --- a/src/engine/client/gl_vidnt.c +++ b/src/engine/client/gl_vidnt.c @@ -85,6 +85,10 @@ convar_t* vid_mode; convar_t* r_downsample; //magic nipples - down sampling +convar_t* r_ripple; +convar_t* r_ripple_updatetime; +convar_t* r_ripple_spawntime; + byte* r_temppool; ref_globals_t tr; @@ -1654,6 +1658,10 @@ void GL_InitCommands( void ) gl_round_down = Cvar_Get( "gl_round_down", "2", FCVAR_RENDERINFO, "round texture sizes to nearest POT value" ); gl_msaa = Cvar_Get( "gl_msaa", "1", FCVAR_ARCHIVE, "enable multi sample anti-aliasing" ); + r_ripple = Cvar_Get("r_ripple", "1", FCVAR_ARCHIVE, "enable software - like water texture ripple simulation"); + r_ripple_updatetime = Cvar_Get("r_ripple_updatetime", "0.05", FCVAR_ARCHIVE, "how fast ripple simulation is"); + r_ripple_spawntime = Cvar_Get("r_ripple_spawntime", "0.1", FCVAR_ARCHIVE, "how fast new ripples spawn"); + // these cvar not used by engine but some mods requires this gl_polyoffset = Cvar_Get( "gl_polyoffset", "2.0", FCVAR_ARCHIVE, "polygon offset for decals" ); diff --git a/src/engine/client/gl_warp.c b/src/engine/client/gl_warp.c index 8dc2705..2287262 100644 --- a/src/engine/client/gl_warp.c +++ b/src/engine/client/gl_warp.c @@ -62,6 +62,29 @@ 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 struct +{ + double time; + double oldtime; + + short* curbuf, * oldbuf; + short buf[2][RIPPLES_TEXSIZE]; + qboolean update; + + uint texture[RIPPLES_TEXSIZE]; //might be a gl_texture_t + int gl_texturenum; + int rippletexturenum; + int texturescale; // not all textures are 128x128, scale the texcoords down +} g_ripple; + + + #define SKYBOX_MISSED 0 #define SKYBOX_HLSTYLE 1 #define SKYBOX_Q1STYLE 2 @@ -769,7 +792,12 @@ 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( warp->polys->verts[0][2] >= RI.vieworg[2] ) + // waveHeight = -RI.currententity->curstate.scale; + //else waveHeight = RI.currententity->curstate.scale; + 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; @@ -817,11 +845,23 @@ void EmitWaterPolys( msurface_t *warp, qboolean reverse ) os = v[3]; ot = v[4]; - s = os + r_turbsin[(int)((ot * 0.125f + cl.time) * TURBSCALE) & 255]; - s *= ( 1.0f / SUBDIVIDE_SIZE ); + //s = os + r_turbsin[(int)((ot * 0.125f + cl.time) * TURBSCALE) & 255]; + //s *= ( 1.0f / SUBDIVIDE_SIZE ); - t = ot + r_turbsin[(int)((os * 0.125f + cl.time) * TURBSCALE) & 255]; - t *= ( 1.0f / SUBDIVIDE_SIZE ); + //t = ot + r_turbsin[(int)((os * 0.125f + cl.time) * TURBSCALE) & 255]; + //t *= ( 1.0f / SUBDIVIDE_SIZE ); + if (!r_ripple->value) + { + s = os + r_turbsin[(int)((ot * 0.125f + cl.time) * TURBSCALE) & 255]; + t = ot + r_turbsin[(int)((os * 0.125f + cl.time) * TURBSCALE) & 255]; + } + else + { + s = os / g_ripple.texturescale; + t = ot / g_ripple.texturescale; + } + s *= (1.0f / SUBDIVIDE_SIZE); + t *= (1.0f / SUBDIVIDE_SIZE); pglTexCoord2f( s, t ); pglVertex3f( v[0], v[1], nv ); @@ -840,4 +880,182 @@ void EmitWaterPolys( msurface_t *warp, qboolean reverse ) //GL_SetupFogColorForSurfaces(); GL_ResetFogColor(); //MAGIC NIPPLES - func_water fix!! +} + + +/* +============================================================ + 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 = cl.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 = cl.time - g_ripple.time; + + g_ripple.update = r_ripple->value && frametime >= r_ripple_updatetime->value; + + if (!g_ripple.update) + return; + + g_ripple.time = cl.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 = rand() & 0x7fff;//COM_RandomLong(0, 0x7fff); + y = rand() & 0x7fff; //COM_RandomLong(0, 0x7fff); + val = rand() & 0x3ff; //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(GL_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; + uint* pixels; + int v, wmask, hmask; + + // discard unuseful textures + if (!r_ripple->value || image->width > RIPPLES_CACHEWIDTH || image->width != image->height) + { + GL_Bind(GL_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(GL_TEXTURE0, image->gl_texturenum); + return; + } + + GL_Bind(GL_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; + + // TODO: original sw.dll always draws at 64x64 + g_ripple.texturescale = Q_max(2, RIPPLES_CACHEWIDTH / image->width); //g_ripple.texturescale = RIPPLES_CACHEWIDTH / image->width; + + pixels = (uint*)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); } \ No newline at end of file diff --git a/src/engine/common/mod_bmodel.c b/src/engine/common/mod_bmodel.c index f2bdaef..8c37e90 100644 --- a/src/engine/common/mod_bmodel.c +++ b/src/engine/common/mod_bmodel.c @@ -1892,6 +1892,20 @@ static void Mod_LoadMarkSurfaces( dbspmodel_t *bmod ) } } +static qboolean Mod_LooksLikeWaterTexture(const char* name) +{ + if ((name[0] == '*' && Q__stricmp(name, "*default")) || name[0] == '!') + return true; + + if (!CL_IsQuakeCompatible()) + { + if (!Q_strncmp(name, "water", 5) || !Q__strnicmp(name, "laser", 5)) + return true; + } + + return false; +} + /* ================= Mod_LoadTextures @@ -1964,6 +1978,10 @@ static void Mod_LoadTextures( dbspmodel_t *bmod ) if( FBitSet( host.features, ENGINE_IMPROVED_LINETRACE ) && mt->name[0] == '{' ) SetBits( txFlags, TF_KEEP_SOURCE ); // Paranoia2 texture alpha-tracing + // check if this is water to keep the source texture and expand it to RGBA (so ripple effect works) + if (Mod_LooksLikeWaterTexture(mt->name)) + SetBits(txFlags, TF_KEEP_SOURCE | TF_EXPAND_SOURCE); + if( mt->offsets[0] > 0 ) { int size = (int)sizeof( mip_t ) + ((mt->width * mt->height * 85)>>6); @@ -2301,14 +2319,16 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) if( !Q_strncmp( tex->name, "sky", 3 )) SetBits( out->flags, SURF_DRAWSKY ); - if(( tex->name[0] == '*' && Q__stricmp( tex->name, "*default" )) || tex->name[0] == '!' ) - SetBits( out->flags, SURF_DRAWTURB ); + //if(( tex->name[0] == '*' && Q__stricmp( tex->name, "*default" )) || tex->name[0] == '!' ) + // SetBits( out->flags, SURF_DRAWTURB ); - if( !CL_IsQuakeCompatible( )) - { - if( !Q_strncmp( tex->name, "water", 5 ) || !Q__strnicmp( tex->name, "laser", 5 )) - SetBits( out->flags, SURF_DRAWTURB ); - } + //if( !CL_IsQuakeCompatible( )) + //{ + // if( !Q_strncmp( tex->name, "water", 5 ) || !Q__strnicmp( tex->name, "laser", 5 )) + // SetBits( out->flags, SURF_DRAWTURB ); + //} + if (Mod_LooksLikeWaterTexture(tex->name)) + SetBits(out->flags, SURF_DRAWTURB); if( !Q_strncmp( tex->name, "scroll", 6 )) SetBits( out->flags, SURF_CONVEYOR );