diff --git a/src/client/cl_entities.c b/src/client/cl_entities.c index d87fa9d7..1fd6eb79 100644 --- a/src/client/cl_entities.c +++ b/src/client/cl_entities.c @@ -758,7 +758,13 @@ CL_CalcViewValues(void) ops = ps; /* don't interpolate */ } - lerp = cl.lerpfrac; + if(cl_paused->value){ + lerp = 1.0f; + } + else + { + lerp = cl.lerpfrac; + } /* calculate the origin */ if ((cl_predict->value) && !(cl.frame.playerstate.pmove.pm_flags & PMF_NO_PREDICTION)) diff --git a/src/client/cl_main.c b/src/client/cl_main.c index a6869e6f..89976792 100644 --- a/src/client/cl_main.c +++ b/src/client/cl_main.c @@ -95,6 +95,11 @@ cvar_t *hand; cvar_t *gender; cvar_t *gender_auto; + +cvar_t *cl_stereo; +cvar_t *cl_stereo_separation; +cvar_t *cl_stereo_convergence; + cvar_t *cl_vwep; client_static_t cls; @@ -529,6 +534,10 @@ CL_InitLocal(void) cl_paused = Cvar_Get("paused", "0", 0); cl_timedemo = Cvar_Get("timedemo", "0", 0); + cl_stereo = Cvar_Get( "cl_stereo", "0", CVAR_ARCHIVE ); + cl_stereo_separation = Cvar_Get( "cl_stereo_separation", "-0.4", CVAR_ARCHIVE ); + cl_stereo_convergence = Cvar_Get( "cl_stereo_convergence", "1", CVAR_ARCHIVE ); + rcon_client_password = Cvar_Get("rcon_password", "", 0); rcon_address = Cvar_Get("rcon_address", "", 0); diff --git a/src/client/cl_screen.c b/src/client/cl_screen.c index 5f1bc6a3..650a38a5 100644 --- a/src/client/cl_screen.c +++ b/src/client/cl_screen.c @@ -492,7 +492,6 @@ SCR_DrawLoading(void) return; } - scr_draw_loading = false; Draw_GetPicSize(&w, &h, "loading"); Draw_PicScaled((viddef.width - w * scale) / 2, (viddef.height - h * scale) / 2, "loading", scale); } @@ -609,6 +608,9 @@ SCR_BeginLoadingPlaque(void) } SCR_UpdateScreen(); + + scr_draw_loading = false; + SCR_StopCinematic(); cls.disable_screen = Sys_Milliseconds(); cls.disable_servercount = cl.servercount; @@ -1441,9 +1443,18 @@ SCR_UpdateScreen(void) return; /* not initialized yet */ } - separation[0] = 0; - separation[1] = 0; - numframes = 1; + if ( cl_stereo->value ) + { + numframes = 2; + separation[0] = -cl_stereo_separation->value / 2; + separation[1] = +cl_stereo_separation->value / 2; + } + else + { + separation[0] = 0; + separation[1] = 0; + numframes = 1; + } for (i = 0; i < numframes; i++) { @@ -1454,8 +1465,14 @@ SCR_UpdateScreen(void) /* loading plaque over black screen */ int w, h; - R_SetPalette(NULL); - scr_draw_loading = false; + if(i == 0){ + R_SetPalette(NULL); + } + + if(i == numframes - 1){ + scr_draw_loading = false; + } + Draw_GetPicSize(&w, &h, "loading"); Draw_PicScaled((viddef.width - w * scale) / 2, (viddef.height - h * scale) / 2, "loading", scale); } diff --git a/src/client/cl_view.c b/src/client/cl_view.c index 7a1581d4..6c8d3c0b 100644 --- a/src/client/cl_view.c +++ b/src/client/cl_view.c @@ -31,11 +31,18 @@ int gun_frame; struct model_s *gun_model; cvar_t *crosshair; +cvar_t *crosshair_3d; +cvar_t *crosshair_3d_glow; +cvar_t *crosshair_3d_color; + cvar_t *crosshair_scale; cvar_t *cl_testparticles; cvar_t *cl_testentities; cvar_t *cl_testlights; cvar_t *cl_testblend; +cvar_t *crosshair_3d_glow_r; +cvar_t *crosshair_3d_glow_g; +cvar_t *crosshair_3d_glow_b; cvar_t *cl_stats; @@ -53,6 +60,8 @@ lightstyle_t r_lightstyles[MAX_LIGHTSTYLES]; char cl_weaponmodels[MAX_CLIENTWEAPONMODELS][MAX_QPATH]; int num_cl_weaponmodels; +void V_Render3dCrosshair(void); + /* * Specifies the model that will be used as the world */ @@ -494,6 +503,9 @@ V_RenderView(float stereo_separation) v_forward, etc. */ CL_AddEntities(); + // before changing viewport we should trace the crosshair position + V_Render3dCrosshair(); + if (cl_testparticles->value) { V_TestParticles(); @@ -519,6 +531,7 @@ V_RenderView(float stereo_separation) /* offset vieworg appropriately if we're doing stereo separation */ + if (stereo_separation != 0) { vec3_t tmp; @@ -572,7 +585,19 @@ V_RenderView(float stereo_separation) qsort(cl.refdef.entities, cl.refdef.num_entities, sizeof(cl.refdef.entities[0]), (int (*)(const void *, const void *)) entitycmpfnc); - } + } else if (cl.frame.valid && cl_paused->value && cl_stereo->value) { + // We need to adjust the refdef in stereo mode when paused. + vec3_t tmp; + CL_CalcViewValues(); + VectorScale( cl.v_right, stereo_separation, tmp ); + VectorAdd( cl.refdef.vieworg, tmp, cl.refdef.vieworg ); + + cl.refdef.vieworg[0] += 1.0/16; + cl.refdef.vieworg[1] += 1.0/16; + cl.refdef.vieworg[2] += 1.0/16; + + cl.refdef.time = cl.time*0.001; + } cl.refdef.x = scr_vrect.x; cl.refdef.y = scr_vrect.y; @@ -602,6 +627,53 @@ V_RenderView(float stereo_separation) SCR_DrawCrosshair(); } +void +V_Render3dCrosshair(void) +{ + trace_t crosshair_trace; + vec3_t end; + vec_t *crosshair_pos; + + crosshair_3d = Cvar_Get("crosshair_3d", "0", CVAR_ARCHIVE); + crosshair_3d_glow = Cvar_Get("crosshair_3d_glow", "0", CVAR_ARCHIVE); + + + if(crosshair_3d->value || crosshair_3d_glow->value){ + VectorMA(cl.refdef.vieworg,8192,cl.v_forward,end); + crosshair_trace = CL_PMTrace(cl.refdef.vieworg, vec3_origin, vec3_origin, end); + + if(crosshair_3d_glow->value){ + crosshair_3d_glow_r = Cvar_Get("crosshair_3d_glow_r", "5", CVAR_ARCHIVE); + crosshair_3d_glow_g = Cvar_Get("crosshair_3d_glow_g", "1", CVAR_ARCHIVE); + crosshair_3d_glow_b = Cvar_Get("crosshair_3d_glow_b", "4", CVAR_ARCHIVE); + + V_AddLight( + crosshair_trace.endpos, + crosshair_3d_glow->value, + crosshair_3d_glow_r->value, + crosshair_3d_glow_g->value, + crosshair_3d_glow_b->value + ); + } + + if(crosshair_3d->value){ + entity_t crosshair_ent = {0}; + + crosshair_ent.origin[0] = crosshair_trace.endpos[0]; + crosshair_ent.origin[1] = crosshair_trace.endpos[1]; + crosshair_ent.origin[2] = crosshair_trace.endpos[2]; + + crosshair_ent.model = R_RegisterModel("models/crosshair/tris.md2"); + //crosshair_ent.skin = R_RegisterSkin("models/crosshair/skin.pcx"); + + AngleVectors2(crosshair_trace.plane.normal, crosshair_ent.angles); + crosshair_ent.flags = RF_DEPTHHACK | RF_FULLBRIGHT | RF_NOSHADOW; + + V_AddEntity(&crosshair_ent); + } + } +} + void V_Viewpos_f(void) { diff --git a/src/client/header/client.h b/src/client/header/client.h index 9a3195d0..b2092a86 100644 --- a/src/client/header/client.h +++ b/src/client/header/client.h @@ -248,6 +248,7 @@ extern client_static_t cls; /* cvars */ extern cvar_t *cl_stereo_separation; +extern cvar_t *cl_stereo_convergence; extern cvar_t *cl_stereo; extern cvar_t *cl_gun; extern cvar_t *cl_add_blend; @@ -506,5 +507,6 @@ void CL_KeyInventory (int key); void CL_DrawInventory (void); void CL_PredictMovement (void); +trace_t CL_PMTrace(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end); #endif diff --git a/src/client/refresh/header/local.h b/src/client/refresh/header/local.h index af213fe8..a5befd11 100644 --- a/src/client/refresh/header/local.h +++ b/src/client/refresh/header/local.h @@ -117,6 +117,23 @@ typedef enum it_sky } imagetype_t; +enum stereo_modes { + STEREO_MODE_NONE, + STEREO_MODE_OPENGL, + STEREO_MODE_ANAGLYPH, + STEREO_MODE_ROW_INTERLEAVED, + STEREO_MODE_COLUMN_INTERLEAVED, + STEREO_MODE_PIXEL_INTERLEAVED, + STEREO_SPLIT_HORIZONTAL, + STEREO_SPLIT_VERTICAL, +}; + +enum opengl_special_buffer_modes { + OPENGL_SPECIAL_BUFFER_MODE_NONE, + OPENGL_SPECIAL_BUFFER_MODE_STEREO, + OPENGL_SPECIAL_BUFFER_MODE_STENCIL, +}; + typedef struct image_s { char name[MAX_QPATH]; /* game path, including extension */ @@ -382,7 +399,7 @@ typedef struct int currenttmu; float camera_separation; - qboolean stereo_enabled; + enum stereo_modes stereo_mode; qboolean hwgamma; diff --git a/src/client/refresh/r_main.c b/src/client/refresh/r_main.c index a421ec0d..2def20a3 100644 --- a/src/client/refresh/r_main.c +++ b/src/client/refresh/r_main.c @@ -142,6 +142,11 @@ cvar_t *gl_msaa_samples; cvar_t *vid_fullscreen; cvar_t *vid_gamma; +cvar_t *cl_stereo; +cvar_t *cl_stereo_separation; +cvar_t *cl_stereo_anaglyph_colors; +cvar_t *cl_stereo_convergence; + /* * Returns true if the box is completely outside the frustom */ @@ -467,7 +472,10 @@ R_DrawParticles2(int num_particles, const particle_t particles[], void R_DrawParticles(void) { - if (gl_ext_pointparameters->value && qglPointParameterfEXT) + qboolean stereo_split_tb = ((gl_state.stereo_mode == STEREO_SPLIT_VERTICAL) && gl_state.camera_separation); + qboolean stereo_split_lr = ((gl_state.stereo_mode == STEREO_SPLIT_HORIZONTAL) && gl_state.camera_separation); + + if (gl_ext_pointparameters->value && qglPointParameterfEXT && !(stereo_split_tb || stereo_split_lr)) { int i; unsigned char color[4]; @@ -678,8 +686,8 @@ R_MYgluPerspective(GLdouble fovy, GLdouble aspect, xmin = ymin * aspect; xmax = ymax * aspect; - xmin += -(2 * gl_state.camera_separation) / zNear; - xmax += -(2 * gl_state.camera_separation) / zNear; + xmin += - cl_stereo_convergence->value * (2 * gl_state.camera_separation) / zNear; + xmax += - cl_stereo_convergence->value * (2 * gl_state.camera_separation) / zNear; glFrustum(xmin, xmax, ymin, ymax, zNear, zFar); } @@ -700,6 +708,20 @@ R_SetupGL(void) w = x2 - x; h = y - y2; + qboolean drawing_left_eye = gl_state.camera_separation < 0; + qboolean stereo_split_tb = ((gl_state.stereo_mode == STEREO_SPLIT_VERTICAL) && gl_state.camera_separation); + qboolean stereo_split_lr = ((gl_state.stereo_mode == STEREO_SPLIT_HORIZONTAL) && gl_state.camera_separation); + + if(stereo_split_lr) { + w = w / 2; + x = drawing_left_eye ? (x / 2) : (x + vid.width) / 2; + } + + if(stereo_split_tb) { + h = h / 2; + y2 = drawing_left_eye ? (y2 + vid.height) / 2 : (y2 / 2); + } + glViewport(x, y2, w, h); /* set up projection matrix */ @@ -749,13 +771,20 @@ R_SetupGL(void) void R_Clear(void) { + // Check whether the stencil buffer needs clearing, and do so if need be. + GLbitfield stencilFlags = 0; + if (gl_state.stereo_mode >= STEREO_MODE_ROW_INTERLEAVED && gl_state.stereo_mode <= STEREO_MODE_PIXEL_INTERLEAVED) { + glClearStencil(0); + stencilFlags |= GL_STENCIL_BUFFER_BIT; + } + if (gl_ztrick->value) { static int trickframe; if (gl_clear->value) { - glClear(GL_COLOR_BUFFER_BIT); + glClear(GL_COLOR_BUFFER_BIT | stencilFlags); } trickframe++; @@ -777,11 +806,11 @@ R_Clear(void) { if (gl_clear->value) { - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glClear(GL_COLOR_BUFFER_BIT | stencilFlags | GL_DEPTH_BUFFER_BIT); } else { - glClear(GL_DEPTH_BUFFER_BIT); + glClear(GL_DEPTH_BUFFER_BIT | stencilFlags); } gldepthmin = 0; @@ -823,6 +852,113 @@ R_Flash(void) void R_RenderView(refdef_t *fd) { + if ((gl_state.stereo_mode != STEREO_MODE_NONE) && gl_state.camera_separation) { + + qboolean drawing_left_eye = gl_state.camera_separation < 0; + switch (gl_state.stereo_mode) { + case STEREO_MODE_ANAGLYPH: + { + + // Work out the colour for each eye. + int anaglyph_colours[] = { 0x4, 0x3 }; // Left = red, right = cyan. + + if (strlen(cl_stereo_anaglyph_colors->string) == 2) { + int eye, colour, missing_bits; + // Decode the colour name from its character. + for (eye = 0; eye < 2; ++eye) { + colour = 0; + switch (toupper(cl_stereo_anaglyph_colors->string[eye])) { + case 'B': ++colour; // 001 Blue + case 'G': ++colour; // 010 Green + case 'C': ++colour; // 011 Cyan + case 'R': ++colour; // 100 Red + case 'M': ++colour; // 101 Magenta + case 'Y': ++colour; // 110 Yellow + anaglyph_colours[eye] = colour; + break; + } + } + // Fill in any missing bits. + missing_bits = ~(anaglyph_colours[0] | anaglyph_colours[1]) & 0x3; + for (eye = 0; eye < 2; ++eye) { + anaglyph_colours[eye] |= missing_bits; + } + } + + // Set the current colour. + glColorMask( + !!(anaglyph_colours[drawing_left_eye] & 0x4), + !!(anaglyph_colours[drawing_left_eye] & 0x2), + !!(anaglyph_colours[drawing_left_eye] & 0x1), + GL_TRUE + ); + } + break; + case STEREO_MODE_ROW_INTERLEAVED: + case STEREO_MODE_COLUMN_INTERLEAVED: + case STEREO_MODE_PIXEL_INTERLEAVED: + { + qboolean flip_eyes = true; + int client_x, client_y; + + //GLimp_GetClientAreaOffset(&client_x, &client_y); + client_x = 0; + client_y = 0; + + R_SetGL2D(); + + glEnable(GL_STENCIL_TEST); + glStencilMask(GL_TRUE); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + + glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP); + glStencilFunc(GL_NEVER, 0, 1); + + glBegin(GL_QUADS); + { + glVertex2i(0, 0); + glVertex2i(vid.width, 0); + glVertex2i(vid.width, vid.height); + glVertex2i(0, vid.height); + } + glEnd(); + + glStencilOp(GL_INVERT, GL_KEEP, GL_KEEP); + glStencilFunc(GL_NEVER, 1, 1); + + glBegin(GL_LINES); + { + if (gl_state.stereo_mode == STEREO_MODE_ROW_INTERLEAVED || gl_state.stereo_mode == STEREO_MODE_PIXEL_INTERLEAVED) { + int y; + for (y = 0; y <= vid.height; y += 2) { + glVertex2f(0, y - 0.5f); + glVertex2f(vid.width, y - 0.5f); + } + flip_eyes ^= (client_y & 1); + } + + if (gl_state.stereo_mode == STEREO_MODE_COLUMN_INTERLEAVED || gl_state.stereo_mode == STEREO_MODE_PIXEL_INTERLEAVED) { + int x; + for (x = 0; x <= vid.width; x += 2) { + glVertex2f(x - 0.5f, 0); + glVertex2f(x - 0.5f, vid.height); + } + flip_eyes ^= (client_x & 1); + } + } + glEnd(); + + glStencilMask(GL_FALSE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + glStencilFunc(GL_EQUAL, drawing_left_eye ^ flip_eyes, 1); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + } + break; + } + } + + if (gl_norefresh->value) { return; @@ -874,13 +1010,44 @@ R_RenderView(refdef_t *fd) c_brush_polys, c_alias_polys, c_visible_textures, c_visible_lightmaps); } + + switch (gl_state.stereo_mode) { + case STEREO_MODE_ANAGLYPH: + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + break; + case STEREO_MODE_ROW_INTERLEAVED: + case STEREO_MODE_COLUMN_INTERLEAVED: + case STEREO_MODE_PIXEL_INTERLEAVED: + glDisable(GL_STENCIL_TEST); + break; + } } void R_SetGL2D(void) { + int x, w, y, h; /* set 2D virtual screen size */ - glViewport(0, 0, vid.width, vid.height); + qboolean drawing_left_eye = gl_state.camera_separation < 0; + qboolean stereo_split_tb = ((gl_state.stereo_mode == STEREO_SPLIT_VERTICAL) && gl_state.camera_separation); + qboolean stereo_split_lr = ((gl_state.stereo_mode == STEREO_SPLIT_HORIZONTAL) && gl_state.camera_separation); + + x = 0; + w = vid.width; + y = 0; + h = vid.height; + + if(stereo_split_lr) { + w = w / 2; + x = drawing_left_eye ? 0 : w; + } + + if(stereo_split_tb) { + h = h / 2; + y = drawing_left_eye ? h : 0; + } + + glViewport(x, y, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, vid.width, vid.height, 0, -99999, 99999); @@ -893,6 +1060,23 @@ R_SetGL2D(void) glColor4f(1, 1, 1, 1); } +enum opengl_special_buffer_modes GL_GetSpecialBufferModeForStereoMode(enum stereo_modes stereo_mode) { + switch (stereo_mode) { + case STEREO_MODE_NONE: + case STEREO_SPLIT_HORIZONTAL: + case STEREO_SPLIT_VERTICAL: + case STEREO_MODE_ANAGLYPH: + return OPENGL_SPECIAL_BUFFER_MODE_NONE; + case STEREO_MODE_OPENGL: + return OPENGL_SPECIAL_BUFFER_MODE_STEREO; + case STEREO_MODE_ROW_INTERLEAVED: + case STEREO_MODE_COLUMN_INTERLEAVED: + case STEREO_MODE_PIXEL_INTERLEAVED: + return OPENGL_SPECIAL_BUFFER_MODE_STENCIL; + } + return OPENGL_SPECIAL_BUFFER_MODE_NONE; +} + void R_SetLightLevel(void) { @@ -1018,6 +1202,12 @@ R_Register(void) gl_retexturing = Cvar_Get("gl_retexturing", "1", CVAR_ARCHIVE); + + cl_stereo = Cvar_Get( "cl_stereo", "0", CVAR_ARCHIVE ); + cl_stereo_separation = Cvar_Get( "cl_stereo_separation", "-0.4", CVAR_ARCHIVE ); + cl_stereo_anaglyph_colors = Cvar_Get( "cl_stereo_anaglyph_colors", "rc", CVAR_ARCHIVE ); + cl_stereo_convergence = Cvar_Get( "cl_stereo_convergence", "1", CVAR_ARCHIVE ); + Cmd_AddCommand("imagelist", R_ImageList_f); Cmd_AddCommand("screenshot", R_ScreenShot); Cmd_AddCommand("modellist", Mod_Modellist_f); @@ -1128,6 +1318,7 @@ R_Init(void *hinstance, void *hWnd) /* set our "safe" mode */ gl_state.prev_mode = 4; + gl_state.stereo_mode = cl_stereo->value; /* create the window and set up the context */ if (!R_SetMode()) @@ -1344,6 +1535,21 @@ R_BeginFrame(float camera_separation) vid_fullscreen->modified = true; } + // force a vid_restart if cl_stereo has been modified. + if ( gl_state.stereo_mode != cl_stereo->value ) { + // If we've gone from one mode to another with the same special buffer requirements there's no need to restart. + if ( GL_GetSpecialBufferModeForStereoMode( gl_state.stereo_mode ) == GL_GetSpecialBufferModeForStereoMode( cl_stereo->value ) ) { + gl_state.stereo_mode = cl_stereo->value; + } + else + { + VID_Printf(PRINT_ALL, "stereo supermode changed, restarting video!\n"); + cvar_t *ref; + ref = Cvar_Get("vid_fullscreen", "0", CVAR_ARCHIVE); + ref->modified = true; + } + } + if (vid_gamma->modified) { vid_gamma->modified = false; @@ -1355,7 +1561,28 @@ R_BeginFrame(float camera_separation) } /* go into 2D mode */ - glViewport(0, 0, vid.width, vid.height); + + int x, w, y, h; + qboolean drawing_left_eye = gl_state.camera_separation < 0; + qboolean stereo_split_tb = ((gl_state.stereo_mode == STEREO_SPLIT_VERTICAL) && gl_state.camera_separation); + qboolean stereo_split_lr = ((gl_state.stereo_mode == STEREO_SPLIT_HORIZONTAL) && gl_state.camera_separation); + + x = 0; + w = vid.width; + y = 0; + h = vid.height; + + if(stereo_split_lr) { + w = w / 2; + x = drawing_left_eye ? 0 : w; + } + + if(stereo_split_tb) { + h = h / 2; + y = drawing_left_eye ? h : 0; + } + + glViewport(x, y, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, vid.width, vid.height, 0, -99999, 99999); @@ -1372,7 +1599,7 @@ R_BeginFrame(float camera_separation) { gl_drawbuffer->modified = false; - if ((gl_state.camera_separation == 0) || !gl_state.stereo_enabled) + if ((gl_state.camera_separation == 0) || gl_state.stereo_mode != STEREO_MODE_OPENGL) { if (Q_stricmp(gl_drawbuffer->string, "GL_FRONT") == 0) {