// SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1999-2018 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- /// \file r_main.c /// \brief Rendering main loop and setup functions, /// utility functions (BSP, geometry, trigonometry). /// See tables.c, too. #include #include "doomdef.h" #include "g_game.h" #include "g_input.h" #include "g_state.h" #include "r_local.h" #include "r_splats.h" // faB(21jan): testing #include "r_sky.h" #include "st_stuff.h" #include "p_local.h" #include "keys.h" #include "i_video.h" #include "m_menu.h" #include "am_map.h" #include "d_main.h" #include "v_video.h" #include "p_spec.h" // skyboxmo #include "p_setup.h" #include "z_zone.h" #include "m_random.h" // quake camera shake #include "r_portal.h" #include "doomstat.h" // MAXSPLITSCREENPLAYERS #include "r_fps.h" // Frame interpolation/uncapped #include "tables.h" #ifdef HWRENDER #include "hardware/hw_main.h" #endif // Fineangles in the SCREENWIDTH wide window. #define FIELDOFVIEW 2048 // increment every time a check is made size_t validcount = 1; INT32 centerx = 0, centery = 0; fixed_t centerxfrac = 0, centeryfrac = 0; fixed_t projection = 0; fixed_t projectiony = 0; // aspect ratio fixed_t fovtan = 0; // field of view // just for profiling purposes size_t framecount = 0; size_t loopcount = 0; fixed_t viewx = 0, viewy = 0, viewz = 0; angle_t viewangle = 0, aimingangle = 0, viewroll = 0; UINT8 viewssnum = 0; fixed_t viewcos = 0, viewsin = 0; boolean skyVisible = 0; boolean skyVisiblePerPlayer[MAXSPLITSCREENPLAYERS] = {}; // saved values of skyVisible for each splitscreen player sector_t *viewsector = NULL; player_t *viewplayer = NULL; fixed_t renderdeltatics = 0; boolean renderisnewtic = true; // // precalculated math tables // angle_t clipangle = 0; angle_t doubleclipangle = 0; // The viewangletox[viewangle + FINEANGLES/4] lookup // maps the visible view angles to screen X coordinates, // flattening the arc to a flat projection plane. // There will be many angles mapped to the same X. INT32 viewangletox[FINEANGLES/2] = {}; // The xtoviewangleangle[] table maps a screen pixel // to the lowest viewangle that maps back to x ranges // from clipangle to -clipangle. angle_t *xtoviewangle = NULL; lighttable_t *scalelight[LIGHTLEVELS][MAXLIGHTSCALE] = {}; lighttable_t *scalelightfixed[MAXLIGHTSCALE] = {}; lighttable_t *zlight[LIGHTLEVELS][MAXLIGHTZ] = {}; // Hack to support extra boom colormaps. size_t num_extra_colormaps = 0; extracolormap_t extra_colormaps[MAXCOLORMAPS] = {}; // Performance stats precise_t ps_prevframetime = 0; ps_metric_t ps_rendercalltime = {}; ps_metric_t ps_otherrendertime = {}; ps_metric_t ps_uitime = {}; ps_metric_t ps_swaptime = {}; ps_metric_t ps_skyboxtime = {}; ps_metric_t ps_bsptime = {}; ps_metric_t ps_sw_spritecliptime = {}; ps_metric_t ps_sw_portaltime = {}; ps_metric_t ps_sw_planetime = {}; ps_metric_t ps_sw_maskedtime = {}; ps_metric_t ps_numbspcalls = {}; ps_metric_t ps_numsprites = {}; ps_metric_t ps_numdrawnodes = {}; ps_metric_t ps_numpolyobjects = {}; static CV_PossibleValue_t drawdist_cons_t[] = { /*{256, "256"},*/ {512, "512"}, {768, "768"}, {1024, "1024"}, {1536, "1536"}, {2048, "2048"}, {3072, "3072"}, {4096, "4096"}, {6144, "6144"}, {8192, "8192"}, {12288, "12288"}, {20480, "20480"}, {24576, "24576"},{0, "Infinite"}, {0, NULL}}; #if __SIZEOF_POINTER__ < 8 // only for 64bit static CV_PossibleValue_t drawdist_precip_cons_t[] = { {256, "256"}, {512, "512"}, {768, "768"}, {1024, "1024"}, {1536, "1536"}, {2048, "2048"}, {0, "None"}, {0, NULL}}; #else static CV_PossibleValue_t drawdist_precip_cons_t[] = { {256, "256"}, {512, "512"}, {768, "768"}, {1024, "1024"}, {1536, "1536"}, {2048, "2048"}, {3072, "3072"}, {4096, "4096"}, {0, "None"}, {0, NULL}}; #endif static CV_PossibleValue_t maxinterpdist_cons_t[] = { /*{256, "256"},*/ {512, "512"}, {768, "768"}, {1024, "1024"}, {1536, "1536"}, {2048, "2048"}, {3072, "3072"}, {4096, "4096"}, {6144, "6144"}, {8192, "8192"}, {0, "Infinite"}, {0, NULL}}; static CV_PossibleValue_t fov_cons_t[] = {{MINFOV*FRACUNIT, "MIN"}, {MAXFOV*FRACUNIT, "MAX"}, {0, NULL}}; static CV_PossibleValue_t translucenthud_cons_t[] = {{0, "MIN"}, {10, "MAX"}, {0, NULL}}; static CV_PossibleValue_t maxportals_cons_t[] = {{0, "MIN"}, {12, "MAX"}, {0, NULL}}; // lmao rendering 32 portals, you're a card static CV_PossibleValue_t homremoval_cons_t[] = {{0, "No"}, {1, "Yes"}, {2, "Flash"}, {0, NULL}}; static CV_PossibleValue_t secbright_cons_t[] = {{0, "MIN"}, {255, "MAX"}, {0, NULL}}; static void R_SetFov(fixed_t playerfov); static void Fov_OnChange(void); static void FlipCam_OnChange(void); static void FlipCam2_OnChange(void); static void FlipCam3_OnChange(void); static void FlipCam4_OnChange(void); static void DirLight_OnChange(void); static void Precipstuff_OnChange(void); consvar_t cv_tailspickup = {"tailspickup", "On", CV_NETVAR|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; // if enabled, load all graphics at level load consvar_t cv_precachetextures = {"precachetextures", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_chasecam[MAXSPLITSCREENPLAYERS] = { {"chasecam", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}, {"chasecam2", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}, {"chasecam3", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}, {"chasecam4", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL} }; consvar_t cv_flipcam[MAXSPLITSCREENPLAYERS] = { {"flipcam", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, FlipCam_OnChange, 0, NULL, NULL, 0, 0, NULL}, {"flipcam2", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, FlipCam2_OnChange, 0, NULL, NULL, 0, 0, NULL}, {"flipcam3", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, FlipCam3_OnChange, 0, NULL, NULL, 0, 0, NULL}, {"flipcam4", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, FlipCam4_OnChange, 0, NULL, NULL, 0, 0, NULL} }; static CV_PossibleValue_t flipcammode_cons_t[] = {{0, "Displayplayer"}, {1, "Local"}, {0, NULL}}; consvar_t cv_flipcammode = {"flipcammode", "Displayplayer", CV_SAVE, flipcammode_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_shadow = {"shadow", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_shadowoffs = {"offsetshadows", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_skybox = {"skybox", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_skydome = {"skydome", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_ffloorclip = {"r_ffloorclip", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_spriteclip = {"r_spriteclip", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_softcyancut = {"softwarecyancut", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_translucency = {"translucency", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_drawdist = {"drawdist", "Infinite", CV_SAVE, drawdist_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_drawdist_precip = {"drawdist_precip", "1024", CV_SAVE|CV_CALL|CV_NOINIT, drawdist_precip_cons_t, Precipstuff_OnChange, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_lessprecip = {"lessweathereffects", "Off", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, Precipstuff_OnChange, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_maxinterpdist = {"maxinterpdist", "Infinite", CV_SAVE, maxinterpdist_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_playerfade = {"playerfade", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_ripplewater = {"waterripples", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; // Okay, whoever said homremoval causes a performance hit should be shot. consvar_t cv_homremoval = {"homremoval", "Yes", CV_SAVE, homremoval_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_secbright = {"r_secbright", "0", CV_SAVE, secbright_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_maxportals = {"maxportals", "2", CV_SAVE, maxportals_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; // cap fov, fov too high tears software apart. consvar_t cv_fov = {"fov", "90", CV_FLOAT|CV_CALL|CV_SAVE, fov_cons_t, Fov_OnChange, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_fovchange = {"fovchange", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; // randomized directional lightning consvar_t cv_randomdirlight = {"randomdirectionallight", "Off", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, DirLight_OnChange, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_showhud = {"showhud", "Yes", CV_CALL, CV_YesNo, R_SetViewSize, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_translucenthud = {"translucenthud", "10", CV_SAVE, translucenthud_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_uncappedhud = {"uncappedhud", "Yes", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_soniccd = {"soniccd", "Off", CV_NETVAR|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_allowmlook = {"allowmlook", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL}; // tells to reset the maplight at next map change static void DirLight_OnChange(void) { reinitmaplight = true; } void SplitScreen_OnChange(void) { UINT8 i; // recompute screen size R_ExecuteSetViewSize(); if (!demo.playback && !botingame) { for (i = 1; i < MAXSPLITSCREENPLAYERS; i++) { if (i > splitscreen) CL_RemoveSplitscreenPlayer(displayplayers[i]); else CL_AddSplitscreenPlayer(); } if (server && !netgame) multiplayer = splitscreen; } else { for (i = 1; i < MAXSPLITSCREENPLAYERS; i++) displayplayers[i] = consoleplayer; for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && i != consoleplayer) { UINT8 j; for (j = 1; j < MAXSPLITSCREENPLAYERS; j++) { if (displayplayers[j] == consoleplayer) { displayplayers[j] = i; break; } } if (j == MAXSPLITSCREENPLAYERS) break; } } } } static void Fov_OnChange(void) { R_SetViewSize(); } static void FlipCam_OnChange(void) { SendWeaponPref(0); } static void FlipCam2_OnChange(void) { SendWeaponPref(1); } static void FlipCam3_OnChange(void) { SendWeaponPref(2); } static void FlipCam4_OnChange(void) { SendWeaponPref(3); } static void Precipstuff_OnChange(void) { if (gamestate == GS_LEVEL) { P_PurgePrecipitation(); P_SpawnPrecipitation(); } } // // R_PointToAngle // To get a global angle from cartesian coordinates, // the coordinates are flipped until they are in // the first octant of the coordinate system, then // the y (<=x) is scaled and divided by x to get a // tangent (slope) value which is looked up in the // tantoangle[] table. The +1 size of tantoangle[] // is to handle the case when x==y without additional // checking. // // killough 5/2/98: reformatted, cleaned up angle_t R_PointToAngle(fixed_t x, fixed_t y) { return (y -= viewy, (x -= viewx) || y) ? x >= 0 ? y >= 0 ? (x > y) ? tantoangle[SlopeDiv(y,x)] : // octant 0 ANGLE_90-tantoangle[SlopeDiv(x,y)] : // octant 1 x > (y = -y) ? 0-tantoangle[SlopeDiv(y,x)] : // octant 8 ANGLE_270+tantoangle[SlopeDiv(x,y)] : // octant 7 y >= 0 ? (x = -x) > y ? ANGLE_180-tantoangle[SlopeDiv(y,x)] : // octant 3 ANGLE_90 + tantoangle[SlopeDiv(x,y)] : // octant 2 (x = -x) > (y = -y) ? ANGLE_180+tantoangle[SlopeDiv(y,x)] : // octant 4 ANGLE_270-tantoangle[SlopeDiv(x,y)] : // octant 5 0; } // This version uses 64-bit variables to avoid overflows with large values. angle_t R_PointToAngle64(INT64 x, INT64 y) { return (y -= viewy, (x -= viewx) || y) ? x >= 0 ? y >= 0 ? (x > y) ? tantoangle[SlopeDivEx(y,x)] : // octant 0 ANGLE_90-tantoangle[SlopeDivEx(x,y)] : // octant 1 x > (y = -y) ? 0-tantoangle[SlopeDivEx(y,x)] : // octant 8 ANGLE_270+tantoangle[SlopeDivEx(x,y)] : // octant 7 y >= 0 ? (x = -x) > y ? ANGLE_180-tantoangle[SlopeDivEx(y,x)] : // octant 3 ANGLE_90 + tantoangle[SlopeDivEx(x,y)] : // octant 2 (x = -x) > (y = -y) ? ANGLE_180+tantoangle[SlopeDivEx(y,x)] : // octant 4 ANGLE_270-tantoangle[SlopeDivEx(x,y)] : // octant 5 0; } angle_t R_PointToAngle2(fixed_t pviewx, fixed_t pviewy, fixed_t x, fixed_t y) { return (y -= pviewy, (x -= pviewx) || y) ? x >= 0 ? y >= 0 ? (x > y) ? tantoangle[SlopeDiv(y,x)] : // octant 0 ANGLE_90-tantoangle[SlopeDiv(x,y)] : // octant 1 x > (y = -y) ? 0-tantoangle[SlopeDiv(y,x)] : // octant 8 ANGLE_270+tantoangle[SlopeDiv(x,y)] : // octant 7 y >= 0 ? (x = -x) > y ? ANGLE_180-tantoangle[SlopeDiv(y,x)] : // octant 3 ANGLE_90 + tantoangle[SlopeDiv(x,y)] : // octant 2 (x = -x) > (y = -y) ? ANGLE_180+tantoangle[SlopeDiv(y,x)] : // octant 4 ANGLE_270-tantoangle[SlopeDiv(x,y)] : // octant 5 0; } angle_t R_PlayerSliptideAngle(player_t *player) { mobj_t *mo; spritedef_t *sprdef; spriteframe_t *sprframe; angle_t ang = 0; if (!cv_sliptideroll.value || !player || P_MobjWasRemoved(player->mo)) return 0; mo = player->mo; if (mo->player->sliproll == 0 || mo->player->kartstuff[k_aizdriftstrat] == 0) return 0; size_t rot = (mo->frame & FF_FRAMEMASK); if (mo->skin && mo->sprite == SPR_PLAY) { sprdef = &((skin_t *)mo->skin)->spritedef; if (rot >= sprdef->numframes) sprdef = &sprites[mo->sprite]; } else { sprdef = &sprites[mo->sprite]; } if (rot >= sprdef->numframes) { sprdef = &sprites[states[S_UNKNOWN].sprite]; rot = (states[S_UNKNOWN].frame & FF_FRAMEMASK); } sprframe = &sprdef->spriteframes[rot]; // No sprite frame? I guess it is possible if (!sprframe) return 0; if (sprframe->rotate != SRF_SINGLE || (mo->frame & FF_PAPERSPRITE)) ang = R_PointToAngle(mo->x, mo->y) - mo->angle; return FixedMul(FINECOSINE((ang) >> ANGLETOFINESHIFT), mo->player->sliproll * mo->player->kartstuff[k_aizdriftstrat]); } // // R_ScaleFromGlobalAngle // Returns the texture mapping scale for the current line (horizontal span) // at the given angle. // rw_distance must be calculated first. // // killough 5/2/98: reformatted, cleaned up // // note: THIS IS USED ONLY FOR WALLS! fixed_t R_ScaleFromGlobalAngle(angle_t visangle) { angle_t anglea = ANGLE_90 + (visangle-viewangle); angle_t angleb = ANGLE_90 + (visangle-rw_normalangle); fixed_t den = FixedMul(rw_distance, FINESINE(anglea>>ANGLETOFINESHIFT)); // proff 11/06/98: Changed for high-res fixed_t num = FixedMul(projectiony, FINESINE(angleb>>ANGLETOFINESHIFT)); if (den > num>>16) { num = FixedDiv(num, den); num = CLAMP(num, 256, 64*FRACUNIT); return num; } return 64*FRACUNIT; } line_t *R_GetFFloorLine(const line_t *line, const ffloor_t *pfloor, const sector_t *sector) { if (pfloor->master->flags & ML_TFERLINE) { size_t linenum = std::min((size_t)(line - sector->lines[0]), pfloor->master->frontsector->linecount); return pfloor->master->frontsector->lines[0] + linenum; } return pfloor->master; } side_t *R_GetFFloorSide(const line_t *line, const ffloor_t *pfloor, const sector_t *sector) { if (pfloor->master->flags & ML_TFERLINE) { line_t *newline = R_GetFFloorLine(line, pfloor, sector); return &sides[newline->sidenum[0]]; } return &sides[pfloor->master->sidenum[0]]; } // // R_DoCulling // Checks viewz and top/bottom heights of an item against culling planes // Returns true if the item is to be culled, i.e it shouldn't be drawn! // if ML_NOCLIMB is set, the camera view is required to be in the same area for culling to occur boolean R_DoCulling(line_t *cullheight, line_t *viewcullheight, fixed_t vz, fixed_t bottomh, fixed_t toph) { fixed_t cullplane; if (!cullheight) return false; cullplane = cullheight->frontsector->floorheight; if (cullheight->flags & ML_NOCLIMB) // Group culling { if (!viewcullheight) return false; // Make sure this is part of the same group if (viewcullheight->frontsector == cullheight->frontsector) { // OK, we can cull if (vz > cullplane && toph < cullplane) // Cull if below plane return true; if (bottomh > cullplane && vz <= cullplane) // Cull if above plane return true; } } else // Quick culling { if (vz > cullplane && toph < cullplane) // Cull if below plane return true; if (bottomh > cullplane && vz <= cullplane) // Cull if above plane return true; } return false; } // Returns search dimensions within a blockmap, in the direction of viewangle and out to a certain distance. void R_GetRenderBlockMapDimensions(fixed_t drawdist, INT32 *xl, INT32 *xh, INT32 *yl, INT32 *yh) { const angle_t left = viewangle - clipangle; const angle_t right = viewangle + clipangle; const fixed_t vxleft = viewx + FixedMul(drawdist, FCOS(left)); const fixed_t vyleft = viewy + FixedMul(drawdist, FSIN(left)); const fixed_t vxright = viewx + FixedMul(drawdist, FCOS(right)); const fixed_t vyright = viewy + FixedMul(drawdist, FSIN(right)); // Try to narrow the search to within only the field of view *xl = (unsigned)(std::min(viewx, std::min(vxleft, vxright)) - bmaporgx)>>MAPBLOCKSHIFT; *xh = (unsigned)(std::max(viewx, std::max(vxleft, vxright)) - bmaporgx)>>MAPBLOCKSHIFT; *yl = (unsigned)(std::min(viewy, std::min(vyleft, vyright)) - bmaporgy)>>MAPBLOCKSHIFT; *yh = (unsigned)(std::max(viewy, std::max(vyleft, vyright)) - bmaporgy)>>MAPBLOCKSHIFT; if (*xh >= bmapwidth) *xh = bmapwidth - 1; if (*yh >= bmapheight) *yh = bmapheight - 1; BMBOUNDFIX(*xl, *xh, *yl, *yh); } // // R_InitTextureMapping // static void R_InitTextureMapping(void) { INT32 i; INT32 x; INT32 t; fixed_t focallength; // Use tangent table to generate viewangletox: // viewangletox will give the next greatest x // after the view angle. // // Calc focallength // so FIELDOFVIEW angles covers SCREENWIDTH. focallength = FixedDiv(projection, FINETANGENT(FINEANGLES/4+/*cv_fov.value*/ FIELDOFVIEW/2)); focallengthf = FIXED_TO_FLOAT(focallength); for (i = 0; i < FINEANGLES/2; i++) { if (FINETANGENT(i) > fovtan*2) t = -1; else if (FINETANGENT(i) < -fovtan*2) t = viewwidth+1; else { t = FixedMul(FINETANGENT(i), focallength); t = (centerxfrac - t+FRACUNIT-1)>>FRACBITS; if (t < -1) t = -1; else if (t > viewwidth+1) t = viewwidth+1; } viewangletox[i] = t; } // Scan viewangletox[] to generate xtoviewangle[]: // xtoviewangle will give the smallest view angle // that maps to x. for (x = 0; x <= viewwidth;x++) { i = 0; while (viewangletox[i] > x) i++; xtoviewangle[x] = (i<>= LIGHTSCALESHIFT; level = startmapl - scale/DISTMAP; if (level < 0) level = 0; if (level >= NUMCOLORMAPS) level = NUMCOLORMAPS-1; zlight[i][j] = colormaps + level*256; } } } static struct { angle_t rollangle; // pre-shifted by fineshift fixed_t zoomneeded; INT32 *scrmap; INT32 scrmapsize; INT32 x1; // clip rendering horizontally for efficiency INT16 *ceilingclip, *floorclip; boolean use; } viewmorph = { 0, FRACUNIT, NULL, 0, 0, NULL, NULL, false }; void R_CheckViewMorph(void) { float zoomfactor, rollcos, rollsin; float x1, y1, x2, y2; fixed_t temp; INT32 end, vx, vy, pos, usedpos; INT32 usedx, usedy, halfwidth = vid.width/2, halfheight = vid.height/2; angle_t rollangle = viewroll; rollangle >>= ANGLETOFINESHIFT; rollangle = ((rollangle+2) & ~3) & FINEMASK; // Limit the distinct number of angles to reduce recalcs from angles changing a lot. if (rollangle == viewmorph.rollangle && viewmorph.scrmapsize == vid.width*vid.height) return; // No change viewmorph.rollangle = rollangle; if (viewmorph.rollangle == 0) { viewmorph.use = false; viewmorph.x1 = 0; if (viewmorph.zoomneeded != FRACUNIT) R_SetViewSize(); viewmorph.zoomneeded = FRACUNIT; return; } if (viewmorph.scrmapsize != vid.width*vid.height) { viewmorph.scrmapsize = vid.width*vid.height; viewmorph.scrmap = static_cast(realloc(viewmorph.scrmap, vid.width*vid.height * sizeof(INT32))); viewmorph.ceilingclip = static_cast(realloc(viewmorph.ceilingclip, vid.width * sizeof(INT16))); viewmorph.floorclip = static_cast(realloc(viewmorph.floorclip, vid.width * sizeof(INT16))); } temp = FINECOSINE(rollangle); rollcos = FIXED_TO_FLOAT(temp); temp = FINESINE(rollangle); rollsin = FIXED_TO_FLOAT(temp); // Calculate maximum zoom needed x1 = (vid.width*fabsf(rollcos) + vid.height*fabsf(rollsin)) / vid.width; y1 = (vid.height*fabsf(rollcos) + vid.width*fabsf(rollsin)) / vid.height; temp = std::max(x1, y1)*FRACUNIT; if (temp < FRACUNIT) temp = FRACUNIT; else temp |= 0x3FFF; // Limit how many times the viewport needs to be recalculated //CONS_Printf("Setting zoom to %f\n", FIXED_TO_FLOAT(temp)); if (temp != viewmorph.zoomneeded) { viewmorph.zoomneeded = temp; R_SetViewSize(); } zoomfactor = FIXED_TO_FLOAT(viewmorph.zoomneeded); end = vid.width * vid.height - 1; pos = 0; // Pre-multiply rollcos and rollsin to use for positional stuff rollcos /= zoomfactor; rollsin /= zoomfactor; x1 = -(halfwidth * rollcos - halfheight * rollsin); y1 = -(halfheight * rollcos + halfwidth * rollsin); viewmorph.x1 = (INT32)(halfwidth - (halfwidth * fabsf(rollcos) + halfheight * fabsf(rollsin))); //CONS_Printf("saving %d cols\n", viewmorph.x1); // Set ceilingclip and floorclip for (vx = 0; vx < vid.width; vx++) { viewmorph.ceilingclip[vx] = vid.height; viewmorph.floorclip[vx] = -1; } x2 = x1; y2 = y1; for (vx = 0; vx < vid.width; vx++) { INT16 xa, ya, xb, yb; xa = x2+halfwidth; ya = y2+halfheight-1; xb = vid.width-1-xa; yb = vid.height-1-ya; viewmorph.ceilingclip[xa] = std::min(viewmorph.ceilingclip[xa], ya); viewmorph.floorclip[xa] = std::max(viewmorph.floorclip[xa], ya); viewmorph.ceilingclip[xb] = std::min(viewmorph.ceilingclip[xb], yb); viewmorph.floorclip[xb] = std::max(viewmorph.floorclip[xb], yb); x2 += rollcos; y2 += rollsin; } x2 = x1; y2 = y1; for (vy = 0; vy < vid.height; vy++) { INT16 xa, ya, xb, yb; xa = x2+halfwidth; ya = y2+halfheight; xb = vid.width-1-xa; yb = vid.height-1-ya; viewmorph.ceilingclip[xa] = std::min(viewmorph.ceilingclip[xa], ya); viewmorph.floorclip[xa] = std::max(viewmorph.floorclip[xa], ya); viewmorph.ceilingclip[xb] = std::min(viewmorph.ceilingclip[xb], yb); viewmorph.floorclip[xb] = std::max(viewmorph.floorclip[xb], yb); x2 -= rollsin; y2 += rollcos; } //CONS_Printf("Top left corner is %f %f\n", x1, y1); x1 += halfwidth; y1 += halfheight; for (vy = 0; vy < halfheight; vy++) { x2 = x1; y2 = y1; x1 -= rollsin; y1 += rollcos; for (vx = 0; vx < vid.width; vx++) { usedx = x2; usedy = y2; usedpos = usedx + usedy*vid.width; viewmorph.scrmap[pos] = usedpos; viewmorph.scrmap[end-pos] = end-usedpos; x2 += rollcos; y2 += rollsin; pos++; } } viewmorph.use = true; } void R_ApplyViewMorph(void) { if (!viewmorph.use) return; UINT8 *tmpscr = vid.screens[4]; UINT8 *srcscr = vid.screens[0]; INT32 p, end = vid.width * vid.height; if (cv_debug & DBG_VIEWMORPH) { UINT8 border = 32; UINT8 grid = 160; INT32 ws = vid.width / 4; INT32 hs = vid.width * (vid.height / 4); memcpy(tmpscr, srcscr, vid.width*vid.height); for (p = 0; p < vid.width; p++) { tmpscr[viewmorph.scrmap[p]] = border; tmpscr[viewmorph.scrmap[p + hs]] = grid; tmpscr[viewmorph.scrmap[p + hs*2]] = grid; tmpscr[viewmorph.scrmap[p + hs*3]] = grid; tmpscr[viewmorph.scrmap[end - 1 - p]] = border; } for (p = vid.width; p < end; p += vid.width) { tmpscr[viewmorph.scrmap[p]] = border; tmpscr[viewmorph.scrmap[p + ws]] = grid; tmpscr[viewmorph.scrmap[p + ws*2]] = grid; tmpscr[viewmorph.scrmap[p + ws*3]] = grid; tmpscr[viewmorph.scrmap[end - 1 - p]] = border; } } else for (p = 0; p < end; p++) tmpscr[p] = srcscr[viewmorph.scrmap[p]]; VID_BlitLinearScreen(tmpscr, vid.screens[0], vid.width, vid.height, vid.width, vid.width); } angle_t R_ViewRollAngle(const player_t *player) { angle_t roll = 0; const UINT8 viewnum = R_GetViewNumber(); if (gamestate != GS_LEVEL) { // FIXME: The way this is implemented is totally // incompatible with cameras that aren't directly // tied to the player. (podium, titlemap, // MT_ALTVIEWMAN in general) // All of these player variables should affect their // camera_t in P_MoveChaseCamera, and then this // just returns that variable instead. return 0; } roll = player->viewrollangle; if (cv_tilting.value) { if (!player->spectator && !camera[viewnum].freecam) roll += player->tilt; if (cv_actionmovie.value) { int xs = intsign(quake.x), ys = intsign(quake.y), zs = intsign(quake.z); roll += (xs ^ ys ^ zs) * ANG1; } } return roll; } // // R_SetViewSize // Do not really change anything here, // because it might be in the middle of a refresh. // The change will take effect next refresh. // boolean setsizeneeded; void R_SetViewSize(void) { setsizeneeded = true; } static void R_SetupViewBuffers(void); // // R_ExecuteSetViewSize // void R_ExecuteSetViewSize(void) { INT32 i; INT32 j; INT32 level; INT32 startmapl; setsizeneeded = false; if (rendermode == render_none) return; // status bar overlay st_overlay = cv_showhud.value; viewwidth = vid.width; viewheight = vid.height; if (splitscreen) { viewheight >>= 1; if (splitscreen > 1) viewwidth >>= 1; } centerx = viewwidth/2; centery = viewheight/2; centerxfrac = centerx<= NUMCOLORMAPS) level = NUMCOLORMAPS - 1; scalelight[i][j] = colormaps + level*256; } } R_SetupFreelook(); am_recalc = true; } fixed_t R_GetPlayerFov(player_t *player) { fixed_t fov = cv_fov.value + player->fovadd; return std::max(MINFOV*FRACUNIT, std::min(fov, MAXFOV*FRACUNIT)); } static void R_CalcFov(fixed_t playerfov) { angle_t fov = FixedAngle(playerfov/2) + ANGLE_90; fovtan = FixedMul(FINETANGENT(fov >> ANGLETOFINESHIFT), viewmorph.zoomneeded); if (splitscreen == 1) // Splitscreen FOV should be adjusted to maintain expected vertical view fovtan = 17*fovtan/10; projection = projectiony = FixedDiv(centerxfrac, fovtan); } static void R_InitViewMapping(void) { R_InitTextureMapping(); if (rendermode == render_soft) { // this is only used for planes rendering in software mode INT32 j = viewheight*16; for (INT32 i = 0; i < j; i++) { fixed_t dy = abs(i - viewheight*8) << FRACBITS; dy = FixedMul(dy, fovtan); yslopetab[i] = FixedDiv(centerx*FRACUNIT, dy); } } // setup sky scaling R_SetSkyScale(); } static void R_SetupViewBuffers(void) { R_CalcFov(cv_fov.value); R_InitViewBuffer(); R_InitViewMapping(); } static void R_SetFov(fixed_t playerfov) { R_CalcFov(playerfov); R_InitViewMapping(); } // // R_Init // void R_Init(void) { // screensize independent //I_OutputMsg("\nR_InitData"); R_InitData(); R_SetViewSize(); // setsizeneeded is set true // this is now done by SCR_Recalc() at the first mode set //I_OutputMsg("\nR_InitLightTables"); R_InitLightTables(); //I_OutputMsg("\nR_InitTranslucencyTables\n"); //R_InitTranslucencyTables(); R_InitDrawNodes(); framecount = 0; } // // R_IsPointInSubsector, same as above but returns 0 if not in subsector // subsector_t *R_IsPointInSubsector(fixed_t x, fixed_t y) { node_t *node; INT32 side, i; size_t nodenum; subsector_t *ret; nodenum = numnodes - 1; while (!(nodenum & NF_SUBSECTOR)) { node = &nodes[nodenum]; side = R_PointOnSide(x, y, node); nodenum = node->children[side]; } ret = &subsectors[nodenum & ~NF_SUBSECTOR]; for (i = 0; i < ret->numlines; i++) if (P_PointOnLineSide(x, y, segs[ret->firstline + i].linedef) != segs[ret->firstline + i].side) return NULL; return ret; } // // R_SetupFrame // mobj_t *viewmobj = NULL; static void R_SetupCommonFrame(player_t * player) { newview->player = player; newview->x += quake.x; newview->y += quake.y; newview->z += quake.z; newview->roll = R_ViewRollAngle(player); R_InterpolateView(R_GetTimeFrac(RTF_CAMERA), false); } static void R_SetupAimingFrame(player_t *player, camera_t *thiscam) { if (player->awayviewtics && player->awayviewmobj && !(thiscam && thiscam->freecam)) // dont force this if we wanna freecam! { newview->aim = player->awayviewaiming; newview->angle = player->awayviewmobj->angle; } else if (thiscam && thiscam->chase) { newview->aim = thiscam->aiming; newview->angle = thiscam->angle; } else if (P_IsLocalPlayer(player) && !demo.playback && player->playerstate != PST_DEAD) { newview->aim = localaiming[viewssnum]; newview->angle = localangle[viewssnum]; } else { newview->aim = player->aiming; newview->angle = player->mo->angle; } } static void R_SetupSkyScale(player_t *player, camera_t *thiscam, mapheader_t *mh) { if (!mh->skybox_scalex && !mh->skybox_scaley && !mh->skybox_scalez) return; auto setup_skyscale = [&](fixed_t mox, fixed_t moy, fixed_t zscale) { if (skyboxmo[1]) { fixed_t x = 0, y = 0; if (mh->skybox_scalex > 0) x = (mox - skyboxmo[1]->x) / mh->skybox_scalex; else if (mh->skybox_scalex < 0) x = (mox - skyboxmo[1]->x) * -mh->skybox_scalex; if (mh->skybox_scaley > 0) y = (moy - skyboxmo[1]->y) / mh->skybox_scaley; else if (mh->skybox_scaley < 0) y = (moy - skyboxmo[1]->y) * -mh->skybox_scaley; switch (viewmobj->angle) { case 0: newview->x += x; newview->y += y; break; case ANGLE_90: newview->x -= y; newview->y += x; break; case ANGLE_180: newview->x -= x; newview->y -= y; break; case ANGLE_270: newview->x += y; newview->y -= x; break; default: angle_t ang = viewmobj->angle>>ANGLETOFINESHIFT; newview->x += FixedMul(x,FINECOSINE(ang)) - FixedMul(y, FINESINE(ang)); newview->y += FixedMul(x, FINESINE(ang)) + FixedMul(y,FINECOSINE(ang)); break; } } if (mh->skybox_scalez > 0) newview->z += zscale / mh->skybox_scalez; else if (mh->skybox_scalez < 0) newview->z += zscale * -mh->skybox_scalez; }; if (player->awayviewtics && player->awayviewmobj) { setup_skyscale(player->awayviewmobj->x, player->awayviewmobj->y, (player->awayviewmobj->z + 20*FRACUNIT)); } else if (thiscam && thiscam->chase) { setup_skyscale(thiscam->x, thiscam->y, (thiscam->z + (thiscam->height>>1))); } else { setup_skyscale(player->mo->x, player->mo->y, player->viewz); } } void R_SkyboxFrame(UINT8 pnum) { player_t *player = &players[displayplayers[pnum]]; camera_t *thiscam = &camera[pnum]; mapheader_t *mh = mapheaderinfo[gamemap-1]; R_SetViewContext(static_cast(VIEWCONTEXT_SKY1 + pnum)); // cut-away view stuff newview->sky = true; viewmobj = skyboxmo[0]; #ifdef PARANOIA if (P_MobjWasRemoved(viewmobj)) { const size_t playeri = (size_t)(player - players); I_Error("R_SkyboxFrame: viewmobj null (player %s)", sizeu1(playeri)); } #endif R_SetupAimingFrame(player, thiscam); if (!P_MobjWasRemoved(viewmobj)) { newview->angle += viewmobj->angle; newview->x = viewmobj->x; newview->y = viewmobj->y; newview->z = (viewmobj->spawnpoint) ? (((fixed_t)viewmobj->spawnpoint->angle) << FRACBITS) : 0; } if (mh) { R_SetupSkyScale(player, thiscam, mh); } R_SetupCommonFrame(player); } void R_SetupFrame(UINT8 pnum, boolean skybox) { player_t *player = &players[displayplayers[pnum]]; camera_t *thiscam = &camera[pnum]; boolean chasecam = (cv_chasecam[pnum].value); R_SetViewContext(static_cast(VIEWCONTEXT_PLAYER1 + pnum)); if (player->spectator || (thiscam && thiscam->freecam)) { // Free flying spectator uses demo freecam. This // requires chasecam to be enabled. chasecam = true; } if (player->playerstate == PST_DEAD || player->exiting) chasecam = true; // force chasecam on if (thiscam) { if (thiscam->reset) { R_ResetViewInterpolation(pnum); thiscam->reset = false; } if (chasecam && !thiscam->chase) { P_ResetCamera(player, thiscam); thiscam->chase = true; } else if (!chasecam) thiscam->chase = false; } newview->sky = !skybox; R_SetupAimingFrame(player, thiscam); if (player->awayviewtics && player->awayviewmobj // cut-away view stuff && !(thiscam && thiscam->freecam)) // dont force this when we wanna freecam! { viewmobj = player->awayviewmobj; // should be a MT_ALTVIEWMAN whos altview man? I_Assert(viewmobj != NULL); newview->x = viewmobj->x; newview->y = viewmobj->y; newview->z = viewmobj->z + 20*FRACUNIT; R_SetupCommonFrame(player); } else if (thiscam && chasecam) // use outside cam view { viewmobj = NULL; I_Assert(thiscam != NULL); newview->x = thiscam->x; newview->y = thiscam->y; newview->z = thiscam->z + (thiscam->height>>1); R_SetupCommonFrame(player); } else if (player->mo) // use the player's eyes view { viewmobj = player->mo; I_Assert(viewmobj != NULL); newview->x = viewmobj->x; newview->y = viewmobj->y; newview->z = player->viewz; R_SetupCommonFrame(player); } } static void R_PortalFrame(portal_t *portal) { viewx = portal->viewx; viewy = portal->viewy; viewz = portal->viewz; viewangle = portal->viewangle; viewsin = FINESINE(viewangle>>ANGLETOFINESHIFT); viewcos = FINECOSINE(viewangle>>ANGLETOFINESHIFT); portalclipstart = portal->start; portalclipend = portal->end; if (portal->clipline != -1) { portalclipline = &lines[portal->clipline]; viewsector = portalcullsector = portalclipline->frontsector; } else { portalclipline = NULL; portalcullsector = NULL; viewsector = R_PointInSubsectorFast(viewx, viewy)->sector; } } static void Mask_Pre(maskcount_t* m) { m->drawsegs[0] = ds_p - drawsegs; m->vissprites[0] = visspritecount; m->viewx = viewx; m->viewy = viewy; m->viewz = viewz; m->viewsector = viewsector; } static void Mask_Post(maskcount_t* m) { m->drawsegs[1] = ds_p - drawsegs; m->vissprites[1] = visspritecount; } // ================ // R_RenderView // ================ // viewx, viewy, viewangle, all that good stuff must be set static void R_RenderViewpoint(maskcount_t* mask, boolean drawprecip) { Mask_Pre(mask); curdrawsegs = ds_p; R_RenderBSPNode((INT32)numnodes - 1); if (drawprecip) R_AddPrecipitationSprites(); Mask_Post(mask); } // FAB NOTE FOR WIN32 PORT !! I'm not finished already, // but I suspect network may have problems with the video buffer being locked // for all duration of rendering, and being released only once at the end.. // I mean, there is a win16lock() or something that lasts all the rendering, // so maybe we should release screen lock before each netupdate below..? static fixed_t viewfov[MAXSPLITSCREENPLAYERS]; void R_RenderPlayerView(player_t *player) { INT32 nummasks = 1; maskcount_t* masks = static_cast(malloc(sizeof(maskcount_t))); const boolean skybox = (skyboxmo[0] && cv_skybox.value); // load previous saved value of skyVisible for the player skyVisible = skyVisiblePerPlayer[viewssnum]; const fixed_t fov = R_GetPlayerFov(player); if (viewfov[viewssnum] != fov) { viewfov[viewssnum] = fov; R_SetFov(fov); } Portal_InitList(); PS_START_TIMING(ps_skyboxtime); if (skybox && skyVisible) { R_SkyboxFrame(viewssnum); R_ClearClipSegs(); R_ClearDrawSegs(); R_ClearSegTables(); R_ClearPlanes(); R_ClearSprites(); R_SetColumnContext(COLUMNCONTEXT_FLUSH); R_RenderViewpoint(&masks[nummasks - 1], false); R_ClipSprites(drawsegs, NULL); R_ResetColumnBuffer(); R_SetColumnContext(COLUMNCONTEXT_DIRECT); R_DrawSkyPlanes(); // draw the fucker again to prevent some artifacts R_DrawPlanes(); R_DrawMasked(masks, nummasks); } PS_STOP_TIMING(ps_skyboxtime); R_SetupFrame(viewssnum, skybox); skyVisible = false; framecount++; validcount++; // Clear buffers. R_ClearPlanes(); if (viewmorph.use) { portalclipstart = viewmorph.x1; portalclipend = viewwidth-viewmorph.x1-1; R_PortalClearClipSegs(portalclipstart, portalclipend); memcpy(ceilingclip, viewmorph.ceilingclip, sizeof(INT16)*vid.width); memcpy(floorclip, viewmorph.floorclip, sizeof(INT16)*vid.width); } else { portalclipstart = 0; portalclipend = viewwidth; R_ClearClipSegs(); } R_ClearDrawSegs(); R_ClearSegTables(); R_ClearSprites(); // check for new console commands. NetUpdate(); R_SetColumnContext(COLUMNCONTEXT_FLUSH); ps_numbspcalls.value.i = ps_numpolyobjects.value.i = ps_numdrawnodes.value.i = 0; PS_START_TIMING(ps_bsptime); R_RenderViewpoint(&masks[nummasks - 1], true); PS_STOP_TIMING(ps_bsptime); PS_START_TIMING(ps_sw_spritecliptime); R_ClipSprites(drawsegs, NULL); PS_STOP_TIMING(ps_sw_spritecliptime); R_ResetColumnBuffer(); ps_numsprites.value.i = numvisiblesprites; PS_START_TIMING(ps_sw_portaltime); // Portal rendering. Hijacks the BSP traversal. if (portal_base) { portal_t *portal; for (portal = portal_base; portal; portal = portal_base) { portalrender = portal->pass; // Recursiveness depth. R_ClearFFloorClips(); // Apply the viewpoint stored for the portal. R_PortalFrame(portal); // Hack in the clipsegs to delimit the starting // clipping for sprites and possibly other similar // future items. R_PortalClearClipSegs(portal->start, portal->end); // Hack in the top/bottom clip values for the window // that were previously stored. Portal_ClipApply(portal); validcount++; masks = static_cast(realloc(masks, (++nummasks)*sizeof(maskcount_t))); // Render the BSP from the new viewpoint, and clip // any sprites with the new clipsegs and window. R_RenderViewpoint(&masks[nummasks - 1], true); R_ClipSprites(ds_p - (masks[nummasks - 1].drawsegs[1] - masks[nummasks - 1].drawsegs[0]), portal); R_ResetColumnBuffer(); Portal_Remove(portal); } } PS_STOP_TIMING(ps_sw_portaltime); R_SetColumnContext(COLUMNCONTEXT_DIRECT); PS_START_TIMING(ps_sw_planetime); if (!skybox) R_DrawSkyPlanes(); R_DrawPlanes(); PS_STOP_TIMING(ps_sw_planetime); // draw mid texture and sprite // And now 3D floors/sides! PS_START_TIMING(ps_sw_maskedtime); R_DrawMasked(masks, nummasks); PS_STOP_TIMING(ps_sw_maskedtime); free(masks); // Check for new console commands. NetUpdate(); // save value to skyVisiblePerPlayer // this is so that P1 can't affect whether P2 can see a skybox or not, or vice versa skyVisiblePerPlayer[viewssnum] = skyVisible; } // ========================================================================= // ENGINE COMMANDS & VARS // ========================================================================= void R_RegisterEngineStuff(void) { INT32 i; CV_RegisterVar(&cv_gravity); CV_RegisterVar(&cv_tailspickup); CV_RegisterVar(&cv_soniccd); CV_RegisterVar(&cv_allowmlook); CV_RegisterVar(&cv_homremoval); for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { CV_RegisterVar(&cv_flipcam[i]); } CV_RegisterVar(&cv_flipcammode); // Enough for dedicated server if (dedicated) return; CV_RegisterVar(&cv_precachetextures); CV_RegisterVar(&cv_translucency); CV_RegisterVar(&cv_drawdist); CV_RegisterVar(&cv_drawdist_precip); CV_RegisterVar(&cv_lessprecip); CV_RegisterVar(&cv_fovchange); CV_RegisterVar(&cv_fov); CV_RegisterVar(&cv_shadow); CV_RegisterVar(&cv_shadowoffs); CV_RegisterVar(&cv_skybox); CV_RegisterVar(&cv_skydome); CV_RegisterVar(&cv_ffloorclip); CV_RegisterVar(&cv_spriteclip); CV_RegisterVar(&cv_secbright); CV_RegisterVar(&cv_softcyancut); for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { CV_RegisterVar(&cv_chasecam[i]); CV_RegisterVar(&cv_cam_dist[i]); CV_RegisterVar(&cv_cam_height[i]); CV_RegisterVar(&cv_cam_pitch[i]); CV_RegisterVar(&cv_cam_speed[i]); CV_RegisterVar(&cv_cam_timeover[i]); CV_RegisterVar(&cv_cam_lookbackmom[i]); CV_RegisterVar(&cv_cam_still[i]); CV_RegisterVar(&cv_cam_rotate[i]); CV_RegisterVar(&cv_freecam_speed[i]); } CV_RegisterVar(&cv_tilting); CV_RegisterVar(&cv_quaketilt); CV_RegisterVar(&cv_tiltsmoothing); CV_RegisterVar(&cv_actionmovie); CV_RegisterVar(&cv_screenquake); CV_RegisterVar(&cv_driftsparkpulse); CV_RegisterVar(&cv_gravstretch); CV_RegisterVar(&cv_sloperoll); CV_RegisterVar(&cv_sliptideroll); CV_RegisterVar(&cv_sloperolldist); CV_RegisterVar(&cv_sparkroll); CV_RegisterVar(&cv_stairjank); CV_RegisterVar(&cv_showhud); CV_RegisterVar(&cv_translucenthud); CV_RegisterVar(&cv_uncappedhud); CV_RegisterVar(&cv_maxportals); CV_RegisterVar(&cv_randomdirlight); CV_RegisterVar(&cv_maxinterpdist); CV_RegisterVar(&cv_playerfade); CV_RegisterVar(&cv_ripplewater); // Frame interpolation/uncapped CV_RegisterVar(&cv_fpscap); CV_RegisterVar(&cv_fpscapbg); }