diff --git a/help_commands.json b/help_commands.json index f8736e61d..20c120142 100644 --- a/help_commands.json +++ b/help_commands.json @@ -1659,6 +1659,24 @@ "description": "In basics works same as mapgroup.", "syntax": "skyboxname map1 [map2] ..." }, + "skywind": { + "description": "Sets the animation parameters of the skybox. Requires a skybox with some level of transparency.", + "syntax": "skywind [distance] [yaw] [period] [pitch]" + }, + "skywind_save": { + "description": "Saves the current skywind configuration as gfx/env/$skybox_wind.cfg." + }, + "skywind_load": { + "description": "Loads the skywind config for the loaded skymap. The file is expected to contain: skywind [distance] [yaw] [period] [pitch]." + }, + "skywind_lookdir": { + "description": "Updates the skywind direction to current point of view with optional overrides of period and distance.", + "syntax": "skywind_lookdir [period] [distance]" + }, + "skywind_rotate": { + "description": "Updates the skywind direction with yaw (horizontal) and pitch (vertical) adjustment.", + "syntax": "skywind_rotate [yaw] [pitch]" + }, "snap": { "description": "Remote screenshot from a player.\n\nExample:\nsnap 1234\n - server requests remote screenshot from user 1234", "syntax": "" diff --git a/help_variables.json b/help_variables.json index a812651df..0f13bb95b 100644 --- a/help_variables.json +++ b/help_variables.json @@ -16590,6 +16590,13 @@ "remarks": "Specifying a value will disable loading of skyboxes specified by the map.\nSkyboxes should be placed in 'env' or 'gfx/env' folders", "type": "string" }, + "r_skywind": { + "default": "1", + "desc": "Sets the scale factor of skybox animation if above 0, and skybox has an animation configuration.", + "group-id": "51", + "remarks": "Specifying a value above 1.0 will increase the pace of the configured animation.", + "type": "float" + }, "r_slimecolor": { "default": "10 60 10", "desc": "Changes color of slime when r_fastturb set to 1.", diff --git a/src/gl_program.c b/src/gl_program.c index ee80d5d3f..9a37b705b 100644 --- a/src/gl_program.c +++ b/src/gl_program.c @@ -218,6 +218,8 @@ static r_program_uniform_t program_uniforms[] = { { r_program_sky_glc, "skySpeedscale2", 1, false }, // r_program_uniform_sky_glc_skyTex, { r_program_sky_glc, "skyTex", 1, false }, + // r_program_uniform_sky_glc_skyWind, + { r_program_sky_glc, "skyWind", 1, false }, // r_program_uniform_sky_glc_skyDomeTex, { r_program_sky_glc, "skyDomeTex", 1, false }, // r_program_uniform_sky_glc_skyDomeCloudTex, diff --git a/src/glc_sky.c b/src/glc_sky.c index 163aa63f0..2649bd609 100644 --- a/src/glc_sky.c +++ b/src/glc_sky.c @@ -282,10 +282,14 @@ static qbool R_DetermineSkyLimits(qbool *ignore_z) } #define PROGRAMFLAGS_SKYBOX 1 +#define PROGRAMFLAGS_SKYWIND 2 qbool GLC_SkyProgramCompile(void) { int flags = (r_skyboxloaded && R_UseCubeMapForSkyBox() ? PROGRAMFLAGS_SKYBOX : 0); + if (flags && Skywind_Active()) { + flags |= PROGRAMFLAGS_SKYWIND; + } if (R_ProgramRecompileNeeded(r_program_sky_glc, flags)) { static char included_definitions[512]; @@ -293,6 +297,9 @@ qbool GLC_SkyProgramCompile(void) memset(included_definitions, 0, sizeof(included_definitions)); if (flags & PROGRAMFLAGS_SKYBOX) { strlcat(included_definitions, "#define DRAW_SKYBOX\n", sizeof(included_definitions)); + if (flags & PROGRAMFLAGS_SKYWIND) { + strlcat(included_definitions, "#define DRAW_SKYWIND\n", sizeof(included_definitions)); + } } R_ProgramCompileWithInclude(r_program_sky_glc, included_definitions); @@ -316,6 +323,7 @@ static void GLC_DrawSky_Program(void) extern msurface_t *skychain; float skySpeedscale = r_refdef2.time * 8; float skySpeedscale2 = r_refdef2.time * 16; + float skyWind[4]; skySpeedscale -= (int)skySpeedscale & ~127; skySpeedscale2 -= (int)skySpeedscale2 & ~127; @@ -328,6 +336,9 @@ static void GLC_DrawSky_Program(void) R_ProgramUniform1f(r_program_uniform_sky_glc_speedscale, skySpeedscale); R_ProgramUniform1f(r_program_uniform_sky_glc_speedscale2, skySpeedscale2); R_ProgramUniform3fv(r_program_uniform_sky_glc_cameraPosition, r_refdef.vieworg); + if (Skywind_GetDirectionAndPhase(skyWind, &skyWind[3])) { + R_ProgramUniform4fv(r_program_uniform_sky_glc_skyWind, skyWind); + } if (r_skyboxloaded && R_UseCubeMapForSkyBox()) { extern texture_ref skybox_cubeMap; diff --git a/src/glm_local.h b/src/glm_local.h index ae01ed0cb..b8e1bfb33 100644 --- a/src/glm_local.h +++ b/src/glm_local.h @@ -38,6 +38,10 @@ typedef struct uniform_block_frame_constants_s { float r_farclip_unused; // NO LONGER USED, replace float waterAlpha; + // animated skybox + vec3_t windDir; + float windPhase; + // drawflat toggles (combine into bitfield?) int r_drawflat; int r_fastturb; diff --git a/src/glm_misc.c b/src/glm_misc.c index 2d7e395eb..f63dca2fc 100644 --- a/src/glm_misc.c +++ b/src/glm_misc.c @@ -31,6 +31,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "glm_local.h" #include "r_renderer.h" #include "gl_texture.h" +#include "r_brushmodel_sky.h" static uniform_block_frame_constants_t frameConstants; static qbool frameConstantsUploaded = false; @@ -162,6 +163,8 @@ void GLM_PreRenderView(void) frameConstants.skyFogMix = r_refdef2.fog_sky; frameConstants.fogDensity = r_refdef2.fog_density; // (r_refdef2.fog_calculation == fogcalc_exp2 ? r_refdef2.fog_density * r_refdef2.fog_density : r_refdef2.fog_density); + Skywind_GetDirectionAndPhase(frameConstants.windDir, &frameConstants.windPhase); + frameConstantsUploaded = false; } diff --git a/src/glm_rsurf.c b/src/glm_rsurf.c index 0e4b66a89..863c7504a 100644 --- a/src/glm_rsurf.c +++ b/src/glm_rsurf.c @@ -112,6 +112,7 @@ static void GLM_CheckDrawCallSize(void) #define DRAW_DRAWFLAT_TINTED (1 << 11) #define DRAW_DRAWFLAT_BRIGHT (1 << 12) #define DRAW_ALPHATESTED (1 << 13) +#define DRAW_SKYWIND (1 << 14) static int material_samplers_max; static int TEXTURE_UNIT_MATERIAL; // Must always be the first non-standard texture unit @@ -134,6 +135,7 @@ static qbool GLM_CompileDrawWorldProgramImpl(r_program_id program_id, qbool alph qbool luma_textures = gl_lumatextures.integer && r_refdef2.allow_lumas; qbool skybox = r_skyboxloaded && !r_fastsky.integer; qbool skydome = !skybox && !r_fastsky.integer && R_TextureReferenceIsValid(solidskytexture); + qbool skywind = skybox && Skywind_Active(); int drawworld_desiredOptions = (detail_textures ? DRAW_DETAIL_TEXTURES : 0) | @@ -148,7 +150,8 @@ static qbool GLM_CompileDrawWorldProgramImpl(r_program_id program_id, qbool alph (r_drawflat.integer == 1 || r_drawflat.integer == 3 ? DRAW_FLATWALLS : 0) | (gl_textureless.integer ? DRAW_TEXTURELESS : 0) | ((gl_outline.integer & 2) ? DRAW_GEOMETRY : 0) | - (alpha_test ? DRAW_ALPHATESTED : 0); + (alpha_test ? DRAW_ALPHATESTED : 0) | + (skywind ? DRAW_SKYWIND : 0); if (R_ProgramRecompileNeeded(program_id, drawworld_desiredOptions)) { static char included_definitions[2048]; @@ -192,7 +195,11 @@ static qbool GLM_CompileDrawWorldProgramImpl(r_program_id program_id, qbool alph TEXTURE_UNIT_SKYBOX = samplers++; strlcat(included_definitions, "#define DRAW_SKYBOX\n", sizeof(included_definitions)); + if (skywind) { + strlcat(included_definitions, "#define DRAW_SKYWIND\n", sizeof(included_definitions)); + } strlcat(included_definitions, va("#define SAMPLER_SKYBOX_TEXTURE %d\n", TEXTURE_UNIT_SKYBOX), sizeof(included_definitions)); + } else if (skydome) { TEXTURE_UNIT_SKYDOME_TEXTURE = samplers++; diff --git a/src/glsl/common.glsl b/src/glsl/common.glsl index 6b8e94495..194040c6b 100644 --- a/src/glsl/common.glsl +++ b/src/glsl/common.glsl @@ -34,6 +34,9 @@ layout(std140, binding=EZQ_GL_BINDINGPOINT_FRAMECONSTANTS) uniform GlobalState { float r_farclip_unused; // Replace float waterAlpha; + // animated skybox + vec4 skyWind; + // drawflat toggles int r_drawflat; int r_fastturb; diff --git a/src/glsl/draw_world.fragment.glsl b/src/glsl/draw_world.fragment.glsl index e3f551cf8..3460289bb 100644 --- a/src/glsl/draw_world.fragment.glsl +++ b/src/glsl/draw_world.fragment.glsl @@ -10,7 +10,7 @@ layout(binding=SAMPLER_DETAIL_TEXTURE) uniform sampler2D detailTex; #ifdef DRAW_CAUSTIC_TEXTURES layout(binding=SAMPLER_CAUSTIC_TEXTURE) uniform sampler2D causticsTex; #endif -#ifdef DRAW_SKYBOX +#if defined(DRAW_SKYBOX) layout(binding=SAMPLER_SKYBOX_TEXTURE) uniform samplerCube skyTex; #elif defined(DRAW_SKYDOME) layout(binding=SAMPLER_SKYDOME_TEXTURE) uniform sampler2D skyDomeTex; @@ -169,6 +169,20 @@ void main() else if (turbType == TEXTURE_TURB_SKY) { #if defined(DRAW_SKYBOX) frag_colour = texture(skyTex, Direction); +#if defined(DRAW_SKYWIND) + float t1 = skyWind.w; + float t2 = fract(t1) - 0.5; + float blend = abs(t1 * 2.0); + vec3 dir = normalize(Direction); + vec4 layer1 = texture(skyTex, dir + t1 * skyWind.xyz); + vec4 layer2 = texture(skyTex, dir + t2 * skyWind.xyz); + layer1.a *= 1.0 - blend; + layer2.a *= blend; + layer1.rgb *= layer1.a; + layer2.rgb *= layer2.a; + vec4 combined = layer1 + layer2; + frag_colour = vec4(frag_colour.rgb * (1.0 - combined.a) + combined.rgb, 1); +#endif #elif defined(DRAW_SKYDOME) const float len = 3.09375; // Flatten it out diff --git a/src/glsl/glc/glc_sky.fragment.glsl b/src/glsl/glc/glc_sky.fragment.glsl index d40180a7e..35ee843fb 100644 --- a/src/glsl/glc/glc_sky.fragment.glsl +++ b/src/glsl/glc/glc_sky.fragment.glsl @@ -4,6 +4,9 @@ #ifdef DRAW_SKYBOX uniform samplerCube skyTex; +#ifdef DRAW_SKYWIND +uniform vec4 skyWind; +#endif #else uniform sampler2D skyDomeTex; uniform sampler2D skyDomeCloudTex; @@ -22,6 +25,20 @@ void main() { #if defined(DRAW_SKYBOX) gl_FragColor = textureCube(skyTex, Direction); +#if defined(DRAW_SKYWIND) + float t1 = skyWind.w; + float t2 = fract(t1) - 0.5; + float blend = abs(t1 * 2.0); + vec3 dir = normalize(Direction); + vec4 layer1 = textureCube(skyTex, dir + t1 * skyWind.xyz); + vec4 layer2 = textureCube(skyTex, dir + t2 * skyWind.xyz); + layer1.a *= 1.0 - blend; + layer2.a *= blend; + layer1.rgb *= layer1.a; + layer2.rgb *= layer2.a; + vec4 combined = layer1 + layer2; + gl_FragColor = vec4(gl_FragColor.rgb * (1.0 - combined.a) + combined.rgb, 1); +#endif #else const float len = 3.09375; // Flatten it out diff --git a/src/r_brushmodel_sky.c b/src/r_brushmodel_sky.c index 003994857..c46d67e9b 100644 --- a/src/r_brushmodel_sky.c +++ b/src/r_brushmodel_sky.c @@ -37,6 +37,20 @@ texture_ref skybox_cubeMap; qbool r_skyboxloaded; +extern cvar_t r_skywind; + +static float skywind_dist; +static float skywind_yaw; +static float skywind_pitch; +static float skywind_period; + +static char active_skyname[MAX_QPATH]; + +#define SKYWIND_CFG "_wind.cfg" + +static void Skywind_Load_f(void); +static void Skywind_Clear(void); + //A sky texture is 256 * 128, with the right side being a masked overlay void R_InitSky (texture_t *mt) { int i, j, p, r, g, b; @@ -80,7 +94,9 @@ int R_SetSky(char *skyname) { char *groupname; + memset(active_skyname, 0, sizeof(active_skyname)); r_skyboxloaded = false; + Skywind_Clear(); // set skyname to groupname if any skyname = (groupname = TP_GetSkyGroupName(TP_MapName(), NULL)) ? groupname : skyname; @@ -98,12 +114,20 @@ int R_SetSky(char *skyname) // everything was OK r_skyboxloaded = true; + + strlcpy(active_skyname, skyname, sizeof(active_skyname)); + + if (r_skywind.integer) { + Skywind_Load_f(); + } + return 0; } void OnChange_r_skyname (cvar_t *v, char *skyname, qbool* cancel) { - if (!skyname[0]) { + if (!skyname[0]) { + memset(active_skyname, 0, sizeof(active_skyname)); r_skyboxloaded = false; return; } @@ -111,7 +135,7 @@ void OnChange_r_skyname (cvar_t *v, char *skyname, qbool* cancel) *cancel = R_SetSky(skyname); } -void R_LoadSky_f(void) +static void R_LoadSky_f(void) { switch (Cmd_Argc()) { case 1: @@ -208,7 +232,7 @@ static qbool Sky_LoadSkyboxTextures(const char* skyname) fixed_size = size; if (i == 0) { - skybox_cubeMap = R_CreateCubeMap("***skybox***", size, size, TEX_NOCOMPRESS | TEX_MIPMAP | TEX_NOSCALE); + skybox_cubeMap = R_CreateCubeMap("***skybox***", size, size, TEX_NOCOMPRESS | TEX_MIPMAP | TEX_NOSCALE | TEX_ALPHA); if (!R_TextureReferenceIsValid(skybox_cubeMap)) { Q_free(data); Com_Printf("Couldn't load skybox \"%s\"\n", skyname); @@ -258,3 +282,262 @@ void R_ClearSkyTextures(void) R_TextureReferenceInvalidate(solidskytexture); R_TextureReferenceInvalidate(alphaskytexture); } + +static void Skywind_Clear(void) +{ + if (!r_skyboxloaded) + return; + skywind_dist = 0.f; + skywind_yaw = 45.f; + skywind_pitch = 0.f; + skywind_period = 30.f; +} + +static void Skywind_Load_f(void) +{ + char relname[MAX_QPATH]; + char *buf; + const char *data; + + if (!r_skyboxloaded) + { + Con_Printf ("No skybox loaded\n"); + return; + } + + snprintf(relname, sizeof(relname), "gfx/env/%s" SKYWIND_CFG, active_skyname); + buf = (char *)FS_LoadTempFile(relname, NULL); + if (!buf) + { + Con_DPrintf ("Sky wind config not found '%s'.\n", relname); + return; + } + + data = COM_Parse(buf); + if (!data) + { + return; + } + + if (strcmp(com_token, "skywind") != 0) + { + Con_Printf("Skywind_Load_f: first token must be 'skywind'.\n"); + return; + } + + Skywind_Clear(); + + if ((data = COM_Parse(data)) != NULL) + { + skywind_dist = bound(-2.0f, atof(com_token), 2.0f); + } + + if ((data = COM_Parse(data)) != NULL) + { + skywind_yaw = fmodf(atof(com_token), 360.0f); + } + + if ((data = COM_Parse(data)) != NULL) + { + skywind_period = atof(com_token); + } + + if ((data = COM_Parse(data)) != NULL) + { + skywind_pitch = fmodf(atof(com_token) + 90.0f, 180.0f) - 90.0f; + } +} + +static void Skywind_Save_f (void) +{ + char relname[MAX_QPATH]; + char path[MAX_OSPATH]; + FILE *f; + + if (!r_skyboxloaded) + { + Con_Printf("No skybox loaded\n"); + return; + } + + snprintf(relname, sizeof(relname), "gfx/env/%s" SKYWIND_CFG, active_skyname); + snprintf(path, sizeof(path), "%s/%s", com_gamedir, relname); + f = fopen(path, "wt"); + if (!f) + { + Con_Printf("Couldn't write '%s'.\n", relname); + return; + } + + fprintf(f, + "// distance yaw period pitch\n" + "skywind %g %g %g %g\n", + skywind_dist, + skywind_yaw, + skywind_period, + skywind_pitch + ); + + fclose(f); + + Con_Printf("Wrote %s\n", relname); +} + +static void Skywind_LookDir_f (void) +{ + if (cls.state != ca_active) + { + return; + } + + if (!r_skyboxloaded) + { + Con_Printf("No skybox loaded\n"); + return; + } + + // invert view direction so that clouds move towards the player, not away from them + skywind_yaw = fmodf(cl.viewangles[YAW] + 180.0f, 360.0f); + skywind_pitch = -cl.viewangles[PITCH]; + + // first argument, if present, overrides the loop duration (default: 30 seconds) + if (Cmd_Argc() >= 2) + { + skywind_period = atof(Cmd_Argv(1)); + } + else if (!skywind_period) + { + skywind_period = 30.f; + } + + // second argument, if present, overrides the amplitude of the movement (default: 1.0) + if (Cmd_Argc() >= 3) + { + skywind_dist = bound(-2.0f, atof(Cmd_Argv(2)), 2.0f); + } + else if (!skywind_dist) + { + skywind_dist = 1.0f; + } +} + +static void Skywind_Rotate_f(void) +{ + if (cls.state != ca_active) + { + return; + } + + if (!r_skyboxloaded) + { + Con_Printf("No skybox loaded\n"); + return; + } + + if (Cmd_Argc() < 2) + { + Con_Printf( + "usage:\n" + " %s [pitchdelta]\n", + Cmd_Argv (0) + ); + return; + } + + skywind_yaw = fmodf(skywind_yaw + atof(Cmd_Argv(1)), 360.0f); + if (Cmd_Argc() >= 3) + { + skywind_pitch = fmodf(skywind_pitch + atof(Cmd_Argv(2)) + 90.0f, 180.0f) - 90.0f; + } +} + +static void Skywind_f (void) +{ + if (cls.state != ca_active) + { + return; + } + + if (!r_skyboxloaded) + { + Con_Printf("No skybox loaded\n"); + return; + } + + if (Cmd_Argc() < 2) + { + Con_Printf ( + "usage:\n" + " %s [distance] [yaw] [period] [pitch]\n" + "current values:\n" + " \"distance\" is \"%g\"\n" + " \"yaw\" is \"%g\"\n" + " \"period\" is \"%g\"\n" + " \"pitch\" is \"%g\"\n", + Cmd_Argv(0), + skywind_dist, + skywind_yaw, + skywind_period, + skywind_pitch + ); + return; + } + + skywind_dist = bound(-2.0f, atof(Cmd_Argv (1)), 2.0f); + if (Cmd_Argc () >= 3) + { + skywind_yaw = fmodf(atof(Cmd_Argv(2)), 360.0f); + } + if (Cmd_Argc () >= 4) + { + skywind_period = atof(Cmd_Argv(3)); + } + if (Cmd_Argc () >= 5) + { + skywind_pitch = fmodf(atof(Cmd_Argv(4)) + 90.0f, 180.0f) - 90.0f; + } +} + +qbool Skywind_Active(void) +{ + return r_skyboxloaded && skywind_dist > 0.0f; +} + +qbool Skywind_GetDirectionAndPhase(float wind_dir[3], float *wind_phase) +{ + float yaw, pitch, sy, sp, cy, cp, dist, period; + double phase; + + if (!Skywind_Active()) + { + return false; + } + + yaw = DEG2RAD(skywind_yaw); + pitch = DEG2RAD(skywind_pitch); + sy = sinf(yaw); + sp = sinf(pitch); + cy = cosf(yaw); + cp = cosf(pitch); + dist = bound(-2.f, skywind_dist, 2.f); + period = skywind_period / r_skywind.value; + phase = period ? cl.time * 0.5 / period : 0.5; + + phase -= floor(phase) + 0.5; // [-0.5, 0.5) + wind_dir[0] = dist * cp * sy; + wind_dir[1] = dist * sp; + wind_dir[2] = -dist * cp * cy; + *wind_phase = (float) phase; + + return true; +} + +void R_SkyRegisterCvars(void) +{ + Cmd_AddCommand("loadsky", R_LoadSky_f); + Cmd_AddCommand("skywind",Skywind_f); + Cmd_AddCommand("skywind_save",Skywind_Save_f); + Cmd_AddCommand("skywind_load",Skywind_Load_f); + Cmd_AddCommand("skywind_lookdir",Skywind_LookDir_f); + Cmd_AddCommand("skywind_rotate",Skywind_Rotate_f); +} \ No newline at end of file diff --git a/src/r_brushmodel_sky.h b/src/r_brushmodel_sky.h index 286c52d3d..f32467528 100644 --- a/src/r_brushmodel_sky.h +++ b/src/r_brushmodel_sky.h @@ -13,7 +13,10 @@ extern cvar_t r_skyname; extern texture_ref skyboxtextures[MAX_SKYBOXTEXTURES]; void R_ClearSkyTextures(void); -void R_LoadSky_f(void); +void R_SkyRegisterCvars(void); extern qbool r_skyboxloaded; +qbool Skywind_Active(void); +qbool Skywind_GetDirectionAndPhase(float wind_dir[3], float *wind_phase); + #endif // EZQUAKE_R_BRUSHMODEL_SKY_HEADER diff --git a/src/r_program.h b/src/r_program.h index 002cc0dc8..a1175a83c 100644 --- a/src/r_program.h +++ b/src/r_program.h @@ -54,6 +54,7 @@ typedef enum { r_program_uniform_sky_glc_speedscale, r_program_uniform_sky_glc_speedscale2, r_program_uniform_sky_glc_skyTex, + r_program_uniform_sky_glc_skyWind, r_program_uniform_sky_glc_skyDomeTex, r_program_uniform_sky_glc_skyDomeCloudTex, r_program_uniform_turb_glc_texSampler, diff --git a/src/r_rmain.c b/src/r_rmain.c index 428e798b6..71f6171bc 100644 --- a/src/r_rmain.c +++ b/src/r_rmain.c @@ -169,6 +169,7 @@ cvar_t r_floorcolor = {"r_floorcolor", "50 100 150", CVAR cvar_t gl_textureless = {"gl_textureless", "0", 0, OnChange_r_drawflat}; //Qrack cvar_t r_farclip = {"r_farclip", "8192", CVAR_RULESET_MAX | CVAR_RULESET_MIN, NULL, 8192.0f, R_MAXIMUM_FARCLIP, R_MINIMUM_FARCLIP }; // previous default was 4096. 8192 helps some TF players in big maps cvar_t r_skyname = {"r_skyname", "", 0, OnChange_r_skyname}; +cvar_t r_skywind = {"r_skywind", "1"}; cvar_t gl_detail = {"gl_detail","0"}; cvar_t gl_brush_polygonoffset = {"gl_brush_polygonoffset", "2.0"}; // This is the one to adjust if you notice flicker on lift @ e1m1 for instance, for z-fighting cvar_t gl_brush_polygonoffset_factor = {"gl_brush_polygonoffset_factor", "0.05"}; @@ -602,7 +603,7 @@ static void R_SetupGL(void) void R_Init(void) { - Cmd_AddCommand("loadsky", R_LoadSky_f); + R_SkyRegisterCvars(); Cmd_AddCommand("timerefresh", R_TimeRefresh_f); #ifndef CLIENTONLY Cmd_AddCommand("dev_pointfile", R_ReadPointFile_f); @@ -654,6 +655,7 @@ void R_Init(void) Cvar_SetCurrentGroup(CVAR_GROUP_TURB); Cvar_Register(&r_skyname); + Cvar_Register(&r_skywind); Cvar_Register(&r_fastsky); Cvar_Register(&r_skycolor); Cvar_Register(&r_fastturb);