// Emacs style mode select -*- C++ -*- //----------------------------------------------------------------------------- // // $Id:$ // // Copyright (C) 1993-1996 by id Software, Inc. // // This source is available for distribution and/or modification // only under the terms of the DOOM Source Code License as // published by id Software. All rights reserved. // // The source is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License // for more details. // // $Log:$ // // DESCRIPTION: // Rendering main loop and setup functions, // utility functions (BSP, geometry, trigonometry). // See tables.c, too. // //----------------------------------------------------------------------------- // HEADER FILES ------------------------------------------------------------ #include #include #include "templates.h" #include "m_alloc.h" #include "doomdef.h" #include "d_net.h" #include "doomstat.h" #include "m_random.h" #include "m_bbox.h" #include "p_local.h" #include "r_local.h" #include "r_sky.h" #include "st_stuff.h" #include "c_cvars.h" #include "v_video.h" #include "stats.h" #include "i_video.h" #include "i_system.h" #include "vectors.h" #include "a_sharedglobal.h" #include "r_translate.h" #include "p_3dmidtex.h" // MACROS ------------------------------------------------------------------ #if 0 #define TEST_X 32343794 #define TEST_Y 111387517 #define TEST_Z 2164524 #define TEST_ANGLE 2468347904 #endif // TYPES ------------------------------------------------------------------- struct InterpolationViewer { AActor *ViewActor; int otic; fixed_t oviewx, oviewy, oviewz; fixed_t nviewx, nviewy, nviewz; int oviewpitch, nviewpitch; angle_t oviewangle, nviewangle; }; // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- void R_SpanInitData (); void RP_RenderBSPNode (void *node); bool RP_SetupFrame (bool backside); // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- // PRIVATE FUNCTION PROTOTYPES --------------------------------------------- static void R_Shutdown(); // EXTERNAL DATA DECLARATIONS ---------------------------------------------- extern bool DrawFSHUD; // [RH] Defined in d_main.cpp extern short *openings; extern bool r_fakingunderwater; extern "C" int fuzzviewheight; EXTERN_CVAR (Bool, r_particles) EXTERN_CVAR (Bool, cl_capfps) // PRIVATE DATA DECLARATIONS ----------------------------------------------- static float CurrentVisibility = 8.f; static fixed_t MaxVisForWall; static fixed_t MaxVisForFloor; static FRandom pr_torchflicker ("TorchFlicker"); static TArray PastViewers; static int centerxwide; static bool polyclipped; static bool r_showviewer; bool r_dontmaplines; // PUBLIC DATA DEFINITIONS ------------------------------------------------- CVAR (String, r_viewsize, "", CVAR_NOSET) CVAR (Int, r_polymost, 0, 0) CVAR (Bool, r_deathcamera, false, CVAR_ARCHIVE) fixed_t r_BaseVisibility; fixed_t r_WallVisibility; fixed_t r_FloorVisibility; float r_TiltVisibility; fixed_t r_SpriteVisibility; fixed_t r_ParticleVisibility; fixed_t r_SkyVisibility; fixed_t r_TicFrac; // [RH] Fractional tic to render DWORD r_FrameTime; // [RH] Time this frame started drawing (in ms) bool r_NoInterpolate; angle_t LocalViewAngle; int LocalViewPitch; bool LocalKeyboardTurner; float LastFOV; int WidescreenRatio; fixed_t GlobVis; fixed_t FocalTangent; fixed_t FocalLengthX; fixed_t FocalLengthY; float FocalLengthXfloat; int viewangleoffset; int validcount = 1; // increment every time a check is made FDynamicColormap*basecolormap; // [RH] colormap currently drawing with int fixedlightlev; lighttable_t *fixedcolormap; float WallTMapScale; float WallTMapScale2; extern "C" { int centerx; int centery; } DCanvas *RenderTarget; // [RH] canvas to render to bool bRenderingToCanvas; // [RH] True if rendering to a special canvas fixed_t globaluclip, globaldclip; fixed_t centerxfrac; fixed_t centeryfrac; fixed_t yaspectmul; float iyaspectmulfloat; fixed_t InvZtoScale; // just for profiling purposes int framecount; int linecount; int loopcount; fixed_t viewx; fixed_t viewy; fixed_t viewz; int viewpitch; int otic; angle_t viewangle; sector_t *viewsector; fixed_t viewcos, viewtancos; fixed_t viewsin, viewtansin; AActor *camera; // [RH] camera to draw from. doesn't have to be a player int r_Yaspect = 200; // Why did I make this a variable? It's never set anywhere. // // precalculated math tables // int FieldOfView = 2048; // Fineangles in the SCREENWIDTH wide window // The xtoviewangleangle[] table maps a screen pixel // to the lowest viewangle that maps back to x ranges // from clipangle to -clipangle. angle_t xtoviewangle[MAXWIDTH+1]; int extralight; // bumped light from gun blasts bool foggy; // [RH] ignore extralight and fullbright? int r_actualextralight; bool setsizeneeded; int setblocks, setdetail = -1; fixed_t freelookviewheight; unsigned int R_OldBlend = ~0; void (*colfunc) (void); void (*basecolfunc) (void); void (*fuzzcolfunc) (void); void (*transcolfunc) (void); void (*spanfunc) (void); void (*hcolfunc_pre) (void); void (*hcolfunc_post1) (int hx, int sx, int yl, int yh); void (*hcolfunc_post2) (int hx, int sx, int yl, int yh); void (STACK_ARGS *hcolfunc_post4) (int sx, int yl, int yh); cycle_t WallCycles, PlaneCycles, MaskedCycles, WallScanCycles; FCanvasTextureInfo *FCanvasTextureInfo::List; // PRIVATE DATA DEFINITIONS ------------------------------------------------ static int lastcenteryfrac; static bool NoInterpolateView; // CODE -------------------------------------------------------------------- //========================================================================== // // 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. // //========================================================================== angle_t R_PointToAngle2 (fixed_t x1, fixed_t y1, fixed_t x, fixed_t y) { x -= x1; y -= y1; if ((x | y) == 0) { return 0; } fixed_t ax = abs (x); fixed_t ay = abs (y); int div; angle_t angle; if (ax > ay) { swap (ax, ay); } div = SlopeDiv (ax, ay); angle = tantoangle[div]; if (x >= 0) { if (y >= 0) { if (x > y) { // octant 0 return angle; } else { // octant 1 return ANG90 - 1 - angle; } } else // y < 0 { if (x > -y) { // octant 8 return (angle_t)-(SDWORD)angle; } else { // octant 7 return ANG270 + angle; } } } else // x < 0 { if (y >= 0) { if (-x > y) { // octant 3 return ANG180 - 1 - angle; } else { // octant 2 return ANG90 + angle; } } else // y < 0 { if (x < y) { // octant 4 return ANG180 + angle; } else { // octant 5 return ANG270 - 1 - angle; } } } } //========================================================================== // // R_InitPointToAngle // //========================================================================== void R_InitPointToAngle (void) { double f; int i; // // slope (tangent) to angle lookup // for (i = 0; i <= SLOPERANGE; i++) { f = atan2 ((double)i, (double)SLOPERANGE) / (6.28318530718 /* 2*pi */); tantoangle[i] = (angle_t)(0xffffffff*f); } } //========================================================================== // // R_PointToDist2 // // Returns the distance from (0,0) to some other point. In a // floating point environment, we'd probably be better off using the // Pythagorean Theorem to determine the result. // // killough 5/2/98: simplified // [RH] Simplified further [sin (t + 90 deg) == cos (t)] // Not used. Should it go away? // //========================================================================== fixed_t R_PointToDist2 (fixed_t dx, fixed_t dy) { dx = abs (dx); dy = abs (dy); if ((dx | dy) == 0) { return 0; } if (dy > dx) { swap (dx, dy); } return FixedDiv (dx, finecosine[tantoangle[FixedDiv (dy, dx) >> DBITS] >> ANGLETOFINESHIFT]); } //========================================================================== // // R_InitTables // //========================================================================== void R_InitTables (void) { int i; const double pimul = PI*2/FINEANGLES; // viewangle tangent table finetangent[0] = (angle_t)(FRACUNIT*tan ((0.5-FINEANGLES/4)*pimul)+0.5); for (i = 1; i < FINEANGLES/2; i++) { finetangent[i] = (angle_t)(FRACUNIT*tan ((i-FINEANGLES/4)*pimul)+0.5); } // finesine table for (i = 0; i < FINEANGLES/4; i++) { finesine[i] = (angle_t)(FRACUNIT * sin (i*pimul)); } for (i = 0; i < FINEANGLES/4; i++) { finesine[i+FINEANGLES/4] = finesine[FINEANGLES/4-1-i]; } for (i = 0; i < FINEANGLES/2; i++) { finesine[i+FINEANGLES/2] = -finesine[i]; } finesine[FINEANGLES/4] = FRACUNIT; finesine[FINEANGLES*3/4] = -FRACUNIT; memcpy (&finesine[FINEANGLES], &finesine[0], sizeof(angle_t)*FINEANGLES/4); } //========================================================================== // // R_InitTextureMapping // //========================================================================== void R_InitTextureMapping () { int i; fixed_t slope; int fov = FieldOfView; // For widescreen displays, increase the FOV so that the middle part of the // screen that would be visible on a 4:3 display has the requested FOV. if (centerxwide != centerx) { // centerxwide is what centerx would be if the display was not widescreen fov = int(atan(double(centerx)*tan(double(fov)*M_PI/(FINEANGLES))/double(centerxwide))*(FINEANGLES)/M_PI); if (fov > 170*FINEANGLES/360) fov = 170*FINEANGLES/360; } /* default: break; case 1: fov = MIN (fov * 512/433, 170 * FINEANGLES / 360); break; // 16:9 case 2: fov = MIN (fov * 512/459, 170 * FINEANGLES / 360); break; // 16:10 } */ const int hitan = finetangent[FINEANGLES/4+fov/2]; // Calc focallength so FieldOfView fineangles covers viewwidth. FocalTangent = hitan; FocalLengthX = FixedDiv (centerxfrac, hitan); FocalLengthY = Scale (centerxfrac, yaspectmul, hitan); FocalLengthXfloat = (float)FocalLengthX / 65536.f; // Now generate xtoviewangle for sky texture mapping. // [RH] Do not generate viewangletox, because texture mapping is no // longer done with trig, so it's not needed. const int t = MIN ((FocalLengthX >> FRACBITS) + centerx, viewwidth); const fixed_t slopestep = hitan / centerx; const fixed_t dfocus = FocalLengthX >> DBITS; for (i = centerx, slope = 0; i <= t; i++, slope += slopestep) { xtoviewangle[i] = (angle_t)-(signed)tantoangle[slope >> DBITS]; } for (; i <= viewwidth; i++) { xtoviewangle[i] = ANG270+tantoangle[dfocus / (i - centerx)]; } for (i = 0; i < centerx; i++) { xtoviewangle[i] = (angle_t)(-(signed)xtoviewangle[viewwidth-i-1]); } } //========================================================================== // // R_SetFOV // // Changes the field of view in degrees // //========================================================================== void R_SetFOV (float fov) { if (fov < 5.f) fov = 5.f; else if (fov > 170.f) fov = 170.f; if (fov != LastFOV) { LastFOV = fov; FieldOfView = (int)(fov * (float)FINEANGLES / 360.f); setsizeneeded = true; } } //========================================================================== // // R_GetFOV // // Returns the current field of view in degrees // //========================================================================== float R_GetFOV () { return LastFOV; } //========================================================================== // // R_SetVisibility // // Changes how rapidly things get dark with distance // //========================================================================== void R_SetVisibility (float vis) { // Allow negative visibilities, just for novelty's sake //vis = clamp (vis, -204.7f, 204.7f); CurrentVisibility = vis; if (FocalTangent == 0) { // If r_visibility is called before the renderer is all set up, don't // divide by zero. This will be called again later, and the proper // values can be initialized then. return; } r_BaseVisibility = toint (vis * 65536.f); // Prevent overflow on walls if (r_BaseVisibility < 0 && r_BaseVisibility < -MaxVisForWall) r_WallVisibility = -MaxVisForWall; else if (r_BaseVisibility > 0 && r_BaseVisibility > MaxVisForWall) r_WallVisibility = MaxVisForWall; else r_WallVisibility = r_BaseVisibility; r_WallVisibility = FixedMul (Scale (InvZtoScale, SCREENWIDTH*(BaseRatioSizes[WidescreenRatio][1]< 0 && r_BaseVisibility > MaxVisForFloor) r_FloorVisibility = MaxVisForFloor; else r_FloorVisibility = r_BaseVisibility; r_FloorVisibility = Scale (160*FRACUNIT, r_FloorVisibility, FocalLengthY); r_TiltVisibility = vis * (float)FocalTangent * (16.f * 320.f) / (float)viewwidth; r_SpriteVisibility = r_WallVisibility; } //========================================================================== // // R_GetVisibility // //========================================================================== float R_GetVisibility () { return CurrentVisibility; } //========================================================================== // // 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. // //========================================================================== void R_SetViewSize (int blocks) { setsizeneeded = true; setblocks = blocks; } //========================================================================== // // CVAR r_detail // // Selects a pixel doubling mode // //========================================================================== CUSTOM_CVAR (Int, r_detail, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { static bool badrecovery = false; if (badrecovery) { badrecovery = false; return; } if (self < 0 || self > 3) { Printf ("Bad detail mode. (Use 0-3)\n"); badrecovery = true; self = (detailyshift << 1) | detailxshift; return; } setdetail = self; setsizeneeded = true; } //========================================================================== // // R_SetDetail // //========================================================================== void R_SetDetail (int detail) { detailxshift = detail & 1; detailyshift = (detail >> 1) & 1; } //========================================================================== // // R_SetWindow // //========================================================================== void R_SetWindow (int windowSize, int fullWidth, int fullHeight, int stHeight) { int virtheight, virtwidth; if (windowSize >= 11) { realviewwidth = fullWidth; freelookviewheight = realviewheight = fullHeight; } else if (windowSize == 10) { realviewwidth = fullWidth; realviewheight = stHeight; freelookviewheight = fullHeight; } else { realviewwidth = ((setblocks*fullWidth)/10) & (~15); realviewheight = ((setblocks*stHeight)/10)&~7; freelookviewheight = ((setblocks*fullHeight)/10)&~7; } // If the screen is approximately 16:9 or 16:10, consider it widescreen. WidescreenRatio = CheckRatio (fullWidth, fullHeight); DrawFSHUD = (windowSize == 11); viewwidth = realviewwidth >> detailxshift; viewheight = realviewheight >> detailyshift; fuzzviewheight = viewheight - 2; // Maximum row the fuzzer can draw to freelookviewheight >>= detailyshift; halfviewwidth = (viewwidth >> 1) - 1; if (!bRenderingToCanvas) { // Set r_viewsize cvar to reflect the current view size UCVarValue value; char temp[16]; sprintf (temp, "%d x %d", viewwidth, viewheight); value.String = temp; r_viewsize.ForceSet (value, CVAR_String); } lastcenteryfrac = 1<<30; centery = viewheight/2; centerx = viewwidth/2; centerxfrac = centerx<> detailxshift; virtheight = fullHeight >> detailyshift; if (WidescreenRatio & 4) { virtheight = virtheight * BaseRatioSizes[WidescreenRatio][3] / 48; centerxwide = centerx; } else { virtwidth = virtwidth * BaseRatioSizes[WidescreenRatio][3] / 48; centerxwide = centerx * BaseRatioSizes[WidescreenRatio][3] / 48; } yaspectmul = Scale ((320<GetPageCount (); if (setdetail >= 0) { R_SetDetail (setdetail); setdetail = -1; } R_SetWindow (setblocks, SCREENWIDTH, SCREENHEIGHT, ST_Y); // Handle resize, e.g. smaller view windows with border and/or status bar. viewwindowx = (screen->GetWidth() - (viewwidth<>1; // Same with base row offset. viewwindowy = ((viewwidth<GetWidth()) ? 0 : (ST_Y-(viewheight<> 1; } //========================================================================== // // CVAR screenblocks // // Selects the size of the visible window // //========================================================================== CUSTOM_CVAR (Int, screenblocks, 10, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { if (self > 12) self = 12; else if (self < 3) self = 3; else R_SetViewSize (self); } //========================================================================== // // CVAR r_columnmethod // // Selects which version of the seg renderers to use. // //========================================================================== CUSTOM_CVAR (Int, r_columnmethod, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { if (self != 0 && self != 1) { self = 1; } else { // Trigger the change r_detail.Callback (); } } //========================================================================== // // R_Init // //========================================================================== void R_Init () { atterm (R_Shutdown); R_InitData (); R_InitPointToAngle (); R_InitTables (); // viewwidth / viewheight are set by the defaults R_SetViewSize (screenblocks); R_InitPlanes (); R_InitTranslationTables (); R_InitParticles (); // [RH] Setup particle engine R_InitColumnDrawers (); colfunc = basecolfunc = R_DrawColumn; fuzzcolfunc = R_DrawFuzzColumn; transcolfunc = R_DrawTranslatedColumn; spanfunc = R_DrawSpan; // [RH] Horizontal column drawers hcolfunc_pre = R_DrawColumnHoriz; hcolfunc_post1 = rt_map1col; hcolfunc_post4 = rt_map4cols; framecount = 0; } //========================================================================== // // R_Shutdown // //========================================================================== static void R_Shutdown () { R_DeinitParticles(); R_DeinitTranslationTables(); R_DeinitPlanes(); R_DeinitData(); } //========================================================================== // // R_PointInSubsector // //========================================================================== subsector_t *R_PointInSubsector (fixed_t x, fixed_t y) { node_t *node; int side; // single subsector is a special case if (numnodes == 0) return subsectors; node = nodes + numnodes - 1; do { side = R_PointOnSide (x, y, node); node = (node_t *)node->children[side]; } while (!((size_t)node & 1)); return (subsector_t *)((BYTE *)node - 1); } //========================================================================== // // R_InterpolateView // //========================================================================== //CVAR (Int, tf, 0, 0) EXTERN_CVAR (Bool, cl_noprediction) void R_InterpolateView (player_t *player, fixed_t frac, InterpolationViewer *iview) { // frac = tf; if (NoInterpolateView) { NoInterpolateView = false; iview->oviewx = iview->nviewx; iview->oviewy = iview->nviewy; iview->oviewz = iview->nviewz; iview->oviewpitch = iview->nviewpitch; iview->oviewangle = iview->nviewangle; } viewx = iview->oviewx + FixedMul (frac, iview->nviewx - iview->oviewx); viewy = iview->oviewy + FixedMul (frac, iview->nviewy - iview->oviewy); viewz = iview->oviewz + FixedMul (frac, iview->nviewz - iview->oviewz); if (player != NULL && player - players == consoleplayer && camera == player->mo && !demoplayback && iview->nviewx == camera->x && iview->nviewy == camera->y && !(player->cheats & (CF_TOTALLYFROZEN|CF_FROZEN)) && player->playerstate == PST_LIVE && player->mo->reactiontime == 0 && !NoInterpolateView && !paused && (!netgame || !cl_noprediction) && !LocalKeyboardTurner) { viewangle = iview->nviewangle + (LocalViewAngle & 0xFFFF0000); fixed_t delta = -(signed)(LocalViewPitch & 0xFFFF0000); viewpitch = iview->nviewpitch; if (delta > 0) { // Avoid overflowing viewpitch (can happen when a netgame is stalled) if (viewpitch + delta <= viewpitch) { viewpitch = +ANGLE_1*MAX_DN_ANGLE; } else { viewpitch = MIN(viewpitch + delta, +ANGLE_1*MAX_DN_ANGLE); } } else if (delta < 0) { // Avoid overflowing viewpitch (can happen when a netgame is stalled) if (viewpitch + delta >= viewpitch) { viewpitch = -ANGLE_1*MAX_UP_ANGLE; } else { viewpitch = MAX(viewpitch + delta, -ANGLE_1*MAX_UP_ANGLE); } } } else { viewpitch = iview->oviewpitch + FixedMul (frac, iview->nviewpitch - iview->oviewpitch); viewangle = iview->oviewangle + FixedMul (frac, iview->nviewangle - iview->oviewangle); } // Due to interpolation this is not necessarily the same as the sector the camera is in. viewsector = R_PointInSubsector(viewx, viewy)->sector; } //========================================================================== // // R_ResetViewInterpolation // //========================================================================== void R_ResetViewInterpolation () { NoInterpolateView = true; } //========================================================================== // // R_SetViewAngle // //========================================================================== void R_SetViewAngle () { angle_t ang = viewangle >> ANGLETOFINESHIFT; viewsin = finesine[ang]; viewcos = finecosine[ang]; viewtansin = FixedMul (FocalTangent, viewsin); viewtancos = FixedMul (FocalTangent, viewcos); } //========================================================================== // // FindPastViewer // //========================================================================== static InterpolationViewer *FindPastViewer (AActor *actor) { for (unsigned int i = 0; i < PastViewers.Size(); ++i) { if (PastViewers[i].ViewActor == actor) { return &PastViewers[i]; } } // Not found, so make a new one InterpolationViewer iview = { 0 }; iview.ViewActor = actor; iview.otic = -1; return &PastViewers[PastViewers.Push (iview)]; } //========================================================================== // // R_FreePastViewers // //========================================================================== void R_FreePastViewers () { PastViewers.Clear (); } //========================================================================== // // R_ClearPastViewer // // If the actor changed in a non-interpolatable way, remove it. // //========================================================================== void R_ClearPastViewer (AActor *actor) { for (unsigned int i = 0; i < PastViewers.Size(); ++i) { if (PastViewers[i].ViewActor == actor) { // Found it, so remove it. if (i == PastViewers.Size()) { PastViewers.Delete (i); } else { PastViewers.Pop (PastViewers[i]); } } } } //========================================================================== // // R_CopyStackedViewParameters // //========================================================================== void R_CopyStackedViewParameters() { stacked_viewx = viewx; stacked_viewy = viewy; stacked_viewz = viewz; stacked_angle = viewangle; stacked_extralight = extralight; stacked_visibility = R_GetVisibility(); } //========================================================================== // // R_SetupFrame // //========================================================================== void R_SetupFrame (AActor *actor) { if (actor == NULL) { I_Error ("Tried to render from a NULL actor."); } player_t *player = actor->player; unsigned int newblend; InterpolationViewer *iview; if (player != NULL && player->mo == actor) { // [RH] Use camera instead of viewplayer camera = player->camera; if (camera == NULL) { camera = player->camera = player->mo; } if (camera == actor) { P_PredictPlayer (player); } } else { camera = actor; } if (camera == NULL) { I_Error ("You lost your body. Bad dehacked work is likely to blame."); } iview = FindPastViewer (camera); int nowtic = I_GetTime (false); if (iview->otic != -1 && nowtic > iview->otic) { iview->otic = nowtic; iview->oviewx = iview->nviewx; iview->oviewy = iview->nviewy; iview->oviewz = iview->nviewz; iview->oviewpitch = iview->nviewpitch; iview->oviewangle = iview->nviewangle; } if (player != NULL && gamestate != GS_TITLELEVEL && ((player->cheats & CF_CHASECAM) || (r_deathcamera && camera->health <= 0)) && (camera->RenderStyle.BlendOp != STYLEOP_None) && !(camera->renderflags & RF_INVISIBLE) && camera->sprite != 0) // Sprite 0 is always TNT1 { // [RH] Use chasecam view P_AimCamera (camera); iview->nviewx = CameraX; iview->nviewy = CameraY; iview->nviewz = CameraZ; viewsector = CameraSector; r_showviewer = true; } else { iview->nviewx = camera->x; iview->nviewy = camera->y; iview->nviewz = camera->player ? camera->player->viewz : camera->z + camera->GetClass()->Meta.GetMetaFixed(AMETA_CameraHeight); viewsector = camera->Sector; r_showviewer = false; } iview->nviewpitch = camera->pitch; if (camera->player != 0) { player = camera->player; } iview->nviewangle = camera->angle + viewangleoffset; if (iview->otic == -1 || r_NoInterpolate) { R_ResetViewInterpolation (); iview->otic = nowtic; } R_UpdateAnimations (I_MSTime()); r_TicFrac = I_GetTimeFrac (&r_FrameTime); if (cl_capfps || r_NoInterpolate) { r_TicFrac = FRACUNIT; } R_InterpolateView (player, r_TicFrac, iview); #ifdef TEST_X viewx = TEST_X; viewy = TEST_Y; viewz = TEST_Z; viewangle = TEST_ANGLE; #endif R_CopyStackedViewParameters(); R_SetViewAngle (); dointerpolations (r_TicFrac); // Keep the view within the sector's floor and ceiling fixed_t theZ = viewsector->ceilingplane.ZatPoint (viewx, viewy) - 4*FRACUNIT; if (viewz > theZ) { viewz = theZ; } theZ = viewsector->floorplane.ZatPoint (viewx, viewy) + 4*FRACUNIT; if (viewz < theZ) { viewz = theZ; } if (!paused) { int intensity = DEarthquake::StaticGetQuakeIntensity (camera); if (intensity != 0) { viewx += ((pr_torchflicker() % (intensity<<2)) -(intensity<<1))<player ? camera->player->extralight : 0; // killough 3/20/98, 4/4/98: select colormap based on player status // [RH] Can also select a blend if (viewsector->heightsec && !(viewsector->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC)) { const sector_t *s = viewsector->heightsec; newblend = viewz < s->floorplane.ZatPoint (viewx, viewy) ? s->bottommap : viewz > s->ceilingplane.ZatPoint (viewx, viewy) ? s->topmap : s->midmap; if (APART(newblend) == 0 && newblend >= numfakecmaps) newblend = 0; } else { newblend = 0; } // [RH] Don't override testblend unless entering a sector with a // blend different from the previous sector's. Same goes with // NormalLight's maps pointer. if (R_OldBlend != newblend) { R_OldBlend = newblend; if (APART(newblend)) { BaseBlendR = RPART(newblend); BaseBlendG = GPART(newblend); BaseBlendB = BPART(newblend); BaseBlendA = APART(newblend) / 255.f; NormalLight.Maps = realcolormaps; } else { NormalLight.Maps = realcolormaps + NUMCOLORMAPS*256*newblend; BaseBlendR = BaseBlendG = BaseBlendB = 0; BaseBlendA = 0.f; } } fixedcolormap = NULL; fixedlightlev = 0; if (player != NULL && camera == player->mo && player->fixedcolormap) { if (player->fixedcolormap < NUMCOLORMAPS) { fixedlightlev = player->fixedcolormap*256; fixedcolormap = NormalLight.Maps; } else switch (player->fixedcolormap) { case INVERSECOLORMAP: fixedcolormap = InverseColormap; break; case REDCOLORMAP: fixedcolormap = RedColormap; break; case GREENCOLORMAP: fixedcolormap = GreenColormap; break; case GOLDCOLORMAP: fixedcolormap = GoldColormap; break; default: break; } } // [RH] Inverse light for shooting the Sigil else if (extralight == INT_MIN) { fixedcolormap = InverseColormap; extralight = 0; } // [RH] freelook stuff { fixed_t dy; if (camera != NULL) { dy = FixedMul (FocalLengthY, finetangent[(ANGLE_90-viewpitch)>>ANGLETOFINESHIFT]); } else { dy = 0; } centeryfrac = (viewheight << (FRACBITS-1)) + dy; centery = centeryfrac >> FRACBITS; globaluclip = FixedDiv (-centeryfrac, InvZtoScale); globaldclip = FixedDiv ((viewheight<curline->v1; // Reflect the current view behind the mirror. if (ds->curline->linedef->dx == 0) { // vertical mirror viewx = v1->x - startx + v1->x; } else if (ds->curline->linedef->dy == 0) { // horizontal mirror viewy = v1->y - starty + v1->y; } else { // any mirror--use floats to avoid integer overflow vertex_t *v2 = ds->curline->v2; float dx = FIXED2FLOAT(v2->x - v1->x); float dy = FIXED2FLOAT(v2->y - v1->y); float x1 = FIXED2FLOAT(v1->x); float y1 = FIXED2FLOAT(v1->y); float x = FIXED2FLOAT(startx); float y = FIXED2FLOAT(starty); // the above two cases catch len == 0 float r = ((x - x1)*dx + (y - y1)*dy) / (dx*dx + dy*dy); viewx = FLOAT2FIXED((x1 + r * dx)*2 - x); viewy = FLOAT2FIXED((y1 + r * dy)*2 - y); } viewangle = 2*R_PointToAngle2 (ds->curline->v1->x, ds->curline->v1->y, ds->curline->v2->x, ds->curline->v2->y) - startang; viewsin = finesine[viewangle>>ANGLETOFINESHIFT]; viewcos = finecosine[viewangle>>ANGLETOFINESHIFT]; viewtansin = FixedMul (FocalTangent, viewsin); viewtancos = FixedMul (FocalTangent, viewcos); R_CopyStackedViewParameters(); validcount++; ActiveWallMirror = ds->curline; R_ClearPlanes (false); R_ClearClipSegs (ds->x1, ds->x2 + 1); memcpy (ceilingclip + ds->x1, openings + ds->sprtopclip, (ds->x2 - ds->x1 + 1)*sizeof(*ceilingclip)); memcpy (floorclip + ds->x1, openings + ds->sprbottomclip, (ds->x2 - ds->x1 + 1)*sizeof(*floorclip)); WindowLeft = ds->x1; WindowRight = ds->x2; MirrorFlags = (depth + 1) & 1; R_RenderBSPNode (nodes + numnodes - 1); R_DrawPlanes (); R_DrawSkyBoxes (); // Allow up to 4 recursions through a mirror if (depth < 4) { unsigned int mirrorsAtEnd = WallMirrors.Size (); for (; mirrorsAtStart < mirrorsAtEnd; mirrorsAtStart++) { R_EnterMirror (drawsegs + WallMirrors[mirrorsAtStart], depth + 1); } } else { depth = depth; } viewangle = startang; viewx = startx; viewy = starty; } //========================================================================== // // R_SetupBuffer // // Precalculate all row offsets and fuzz table. // //========================================================================== void R_SetupBuffer (bool inview) { static BYTE *lastbuff = NULL; int pitch = RenderTarget->GetPitch(); BYTE *lineptr = RenderTarget->GetBuffer() + viewwindowy*pitch + viewwindowx; if (inview) { pitch <<= detailyshift; } if (detailxshift) { lineptr += viewwidth; } if (dc_pitch != pitch || lineptr != lastbuff) { if (dc_pitch != pitch) { dc_pitch = pitch; R_InitFuzzTable (pitch); #ifdef USEASM ASM_PatchPitch (); #endif } dc_destorg = lineptr; for (int i = 0; i < RenderTarget->GetHeight(); i++) { ylookup[i] = i * pitch; } } } //========================================================================== // // R_RenderActorView // //========================================================================== void R_RenderActorView (AActor *actor, bool dontmaplines) { WallCycles = PlaneCycles = MaskedCycles = WallScanCycles = 0; R_SetupBuffer (true); R_SetupFrame (actor); // Clear buffers. R_ClearClipSegs (0, viewwidth); R_ClearDrawSegs (); R_ClearPlanes (true); R_ClearSprites (); NetUpdate (); // [RH] Show off segs if r_drawflat is 1 if (r_drawflat) { hcolfunc_pre = R_FillColumnHorizP; hcolfunc_post1 = rt_copy1col; hcolfunc_post4 = rt_copy4cols; colfunc = R_FillColumnP; spanfunc = R_FillSpan; } else { hcolfunc_pre = R_DrawColumnHoriz; hcolfunc_post1 = rt_map1col; hcolfunc_post4 = rt_map4cols; colfunc = basecolfunc; spanfunc = R_DrawSpan; } WindowLeft = 0; WindowRight = viewwidth - 1; MirrorFlags = 0; ActiveWallMirror = NULL; r_dontmaplines = dontmaplines; // [RH] Hack to make windows into underwater areas possible r_fakingunderwater = false; // [RH] Setup particles for this frame R_FindParticleSubsectors (); clock (WallCycles); DWORD savedflags = camera->renderflags; // Never draw the player unless in chasecam mode if (!r_showviewer) { camera->renderflags |= RF_INVISIBLE; } if (r_polymost < 2) { R_RenderBSPNode (nodes + numnodes - 1); // The head node is the last node output. } camera->renderflags = savedflags; unclock (WallCycles); NetUpdate (); if (viewactive) { clock (PlaneCycles); R_DrawPlanes (); R_DrawSkyBoxes (); unclock (PlaneCycles); // [RH] Walk through mirrors size_t lastmirror = WallMirrors.Size (); for (unsigned int i = 0; i < lastmirror; i++) { R_EnterMirror (drawsegs + WallMirrors[i], 0); } NetUpdate (); clock (MaskedCycles); R_DrawMasked (); unclock (MaskedCycles); NetUpdate (); if (r_polymost) { RP_RenderBSPNode (nodes + numnodes - 1); if (polyclipped) { RP_SetupFrame (true); RP_RenderBSPNode (nodes + numnodes - 1); } } } WallMirrors.Clear (); restoreinterpolations (); // If there is vertical doubling, and the view window is not an even height, // draw a black line at the bottom of the view window. if (detailyshift && viewwindowy == 0 && (realviewheight & 1)) { screen->Clear (0, realviewheight-1, realviewwidth, realviewheight, 0, 0); } R_SetupBuffer (false); } //========================================================================== // // R_RenderViewToCanvas // // Pre: Canvas is already locked. // //========================================================================== void R_RenderViewToCanvas (AActor *actor, DCanvas *canvas, int x, int y, int width, int height, bool dontmaplines) { const int saveddetail = detailxshift | (detailyshift << 1); const bool savedviewactive = viewactive; detailxshift = detailyshift = 0; realviewwidth = viewwidth = width; RenderTarget = canvas; bRenderingToCanvas = true; R_SetDetail (0); R_SetWindow (12, width, height, height); viewwindowx = x; viewwindowy = y; viewactive = true; R_RenderActorView (actor, dontmaplines); RenderTarget = screen; bRenderingToCanvas = false; R_SetDetail (saveddetail); R_ExecuteSetViewSize (); screen->Lock (true); R_SetupBuffer (false); screen->Unlock (); viewactive = savedviewactive; } //========================================================================== // // FCanvasTextureInfo :: Add // // Assigns a camera to a canvas texture. // //========================================================================== void FCanvasTextureInfo::Add (AActor *viewpoint, int picnum, int fov) { FCanvasTextureInfo *probe; FCanvasTexture *texture; if (picnum < 0) { return; } texture = static_cast(TexMan[picnum]); if (!texture->bHasCanvas) { Printf ("%s is not a valid target for a camera\n", texture->Name); return; } // Is this texture already assigned to a camera? for (probe = List; probe != NULL; probe = probe->Next) { if (probe->Texture == texture) { // Yes, change its assignment to this new camera if (probe->Viewpoint != viewpoint || probe->FOV != fov) { texture->bFirstUpdate = true; } probe->Viewpoint = viewpoint; probe->FOV = fov; return; } } // No, create a new assignment probe = new FCanvasTextureInfo; probe->Viewpoint = viewpoint; probe->Texture = texture; probe->PicNum = picnum; probe->FOV = fov; probe->Next = List; texture->bFirstUpdate = true; List = probe; } //========================================================================== // // FCanvasTextureInfo :: UpdateAll // // Updates all canvas textures that were visible in the last frame. // //========================================================================== void FCanvasTextureInfo::UpdateAll () { FCanvasTextureInfo *probe; for (probe = List; probe != NULL; probe = probe->Next) { if (probe->Viewpoint != NULL && probe->Texture->bNeedsUpdate) { probe->Texture->RenderView (probe->Viewpoint, probe->FOV); } } } //========================================================================== // // FCanvasTextureInfo :: EmptyList // // Removes all camera->texture assignments. // //========================================================================== void FCanvasTextureInfo::EmptyList () { FCanvasTextureInfo *probe, *next; for (probe = List; probe != NULL; probe = next) { next = probe->Next; delete probe; } List = NULL; } //========================================================================== // // FCanvasTextureInfo :: Serialize // // Reads or writes the current set of mappings in an archive. // //========================================================================== void FCanvasTextureInfo::Serialize (FArchive &arc) { if (arc.IsStoring ()) { FCanvasTextureInfo *probe; for (probe = List; probe != NULL; probe = probe->Next) { if (probe->Texture != NULL && probe->Viewpoint != NULL) { arc << probe->Viewpoint << probe->FOV; TexMan.WriteTexture (arc, probe->PicNum); } } AActor *nullactor = NULL; arc << nullactor; } else { AActor *viewpoint; int picnum, fov; EmptyList (); arc << viewpoint; while (viewpoint != NULL) { arc << fov; picnum = TexMan.ReadTexture (arc); Add (viewpoint, picnum, fov); arc << viewpoint; } } } //========================================================================== // // FCanvasTextureInfo :: Mark // // Marks all viewpoints in the list for the collector. // //========================================================================== void FCanvasTextureInfo::Mark() { for (FCanvasTextureInfo *probe = List; probe != NULL; probe = probe->Next) { GC::Mark(probe->Viewpoint); } } //========================================================================== // // R_MultiresInit // // Called from V_SetResolution() // //========================================================================== void R_MultiresInit () { R_PlaneInitData (); R_OldBlend = ~0; } // Stuff from BUILD to interpolate floors and ceilings // // "Build Engine & Tools" Copyright (c) 1993-1997 Ken Silverman // Ken Silverman's official web site: "http://www.advsys.net/ken" // See the included license file "BUILDLIC.TXT" for license info. // This code has been modified from its original form. static bool didInterp; FActiveInterpolation *FActiveInterpolation::curiposhash[INTERPOLATION_BUCKETS]; void FActiveInterpolation::CopyInterpToOld () { switch (Type) { case INTERP_SectorFloor: oldipos[0] = ((sector_t*)Address)->floorplane.d; oldipos[1] = ((sector_t*)Address)->floortexz; break; case INTERP_SectorCeiling: oldipos[0] = ((sector_t*)Address)->ceilingplane.d; oldipos[1] = ((sector_t*)Address)->ceilingtexz; break; case INTERP_Vertex: oldipos[0] = ((vertex_t*)Address)->x; oldipos[1] = ((vertex_t*)Address)->y; break; case INTERP_FloorPanning: oldipos[0] = ((sector_t*)Address)->floor_xoffs; oldipos[1] = ((sector_t*)Address)->floor_yoffs; break; case INTERP_CeilingPanning: oldipos[0] = ((sector_t*)Address)->ceiling_xoffs; oldipos[1] = ((sector_t*)Address)->ceiling_yoffs; break; case INTERP_WallPanning: oldipos[0] = ((side_t*)Address)->rowoffset; oldipos[1] = ((side_t*)Address)->textureoffset; break; } } void FActiveInterpolation::CopyBakToInterp () { switch (Type) { case INTERP_SectorFloor: ((sector_t*)Address)->floorplane.d = bakipos[0]; ((sector_t*)Address)->floortexz = bakipos[1]; break; case INTERP_SectorCeiling: ((sector_t*)Address)->ceilingplane.d = bakipos[0]; ((sector_t*)Address)->ceilingtexz = bakipos[1]; break; case INTERP_Vertex: ((vertex_t*)Address)->x = bakipos[0]; ((vertex_t*)Address)->y = bakipos[1]; break; case INTERP_FloorPanning: ((sector_t*)Address)->floor_xoffs = bakipos[0]; ((sector_t*)Address)->floor_yoffs = bakipos[1]; break; case INTERP_CeilingPanning: ((sector_t*)Address)->ceiling_xoffs = bakipos[0]; ((sector_t*)Address)->ceiling_yoffs = bakipos[1]; break; case INTERP_WallPanning: ((side_t*)Address)->rowoffset = bakipos[0]; ((side_t*)Address)->textureoffset = bakipos[1]; break; } } void FActiveInterpolation::DoAnInterpolation (fixed_t smoothratio) { fixed_t *adr1, *adr2, pos; switch (Type) { case INTERP_SectorFloor: adr1 = &((sector_t*)Address)->floorplane.d; adr2 = &((sector_t*)Address)->floortexz; break; case INTERP_SectorCeiling: adr1 = &((sector_t*)Address)->ceilingplane.d; adr2 = &((sector_t*)Address)->ceilingtexz; break; case INTERP_Vertex: adr1 = &((vertex_t*)Address)->x; adr2 = &((vertex_t*)Address)->y; break; case INTERP_FloorPanning: adr1 = &((sector_t*)Address)->floor_xoffs; adr2 = &((sector_t*)Address)->floor_yoffs; break; case INTERP_CeilingPanning: adr1 = &((sector_t*)Address)->ceiling_xoffs; adr2 = &((sector_t*)Address)->ceiling_yoffs; break; case INTERP_WallPanning: adr1 = &((side_t*)Address)->rowoffset; adr2 = &((side_t*)Address)->textureoffset; break; default: return; } pos = bakipos[0] = *adr1; *adr1 = oldipos[0] + FixedMul (pos - oldipos[0], smoothratio); pos = bakipos[1] = *adr2; *adr2 = oldipos[1] + FixedMul (pos - oldipos[1], smoothratio); } size_t FActiveInterpolation::HashKey (EInterpType type, void *interptr) { return (size_t)type * ((size_t)interptr>>5); } int FActiveInterpolation::CountInterpolations () { int d1, d2, d3; return CountInterpolations (&d1, &d2, &d3); } int FActiveInterpolation::CountInterpolations (int *usedbuckets, int *minbucketfill, int *maxbucketfill) { int count = 0; int inuse = 0; int minuse = INT_MAX; int maxuse = INT_MIN; for (int i = INTERPOLATION_BUCKETS-1; i >= 0; --i) { int use = 0; FActiveInterpolation *probe = FActiveInterpolation::curiposhash[i]; if (probe != NULL) { inuse++; } while (probe != NULL) { count++; use++; probe = probe->Next; } if (use > 0 && use < minuse) { minuse = use; } if (use > maxuse) { maxuse = use; } } *usedbuckets = inuse; *minbucketfill = minuse == INT_MAX ? 0 : minuse; *maxbucketfill = maxuse; return count; } FActiveInterpolation *FActiveInterpolation::FindInterpolation (EInterpType type, void *interptr, FActiveInterpolation **&interp_p) { size_t hash = HashKey (type, interptr) % INTERPOLATION_BUCKETS; FActiveInterpolation *probe, **probe_p; for (probe_p = &curiposhash[hash], probe = *probe_p; probe != NULL; probe_p = &probe->Next, probe = *probe_p) { if (probe->Address > interptr) { // We passed the place it would have been, so it must not be here. probe = NULL; break; } if (probe->Address == interptr && probe->Type == type) { // Found it. break; } } interp_p = probe_p; return probe; } void clearinterpolations() { for (int i = INTERPOLATION_BUCKETS-1; i >= 0; --i) { for (FActiveInterpolation *probe = FActiveInterpolation::curiposhash[i]; probe != NULL; ) { FActiveInterpolation *next = probe->Next; delete probe; probe = next; } FActiveInterpolation::curiposhash[i] = NULL; } } void updateinterpolations() //Stick at beginning of domovethings { for (int i = INTERPOLATION_BUCKETS-1; i >= 0; --i) { for (FActiveInterpolation *probe = FActiveInterpolation::curiposhash[i]; probe != NULL; probe = probe->Next) { probe->CopyInterpToOld (); } } } void setinterpolation(EInterpType type, void *posptr) { FActiveInterpolation **interp_p; FActiveInterpolation *interp = FActiveInterpolation::FindInterpolation (type, posptr, interp_p); if (interp != NULL) return; // It's already active interp = new FActiveInterpolation; interp->Type = type; interp->Address = posptr; interp->Next = *interp_p; *interp_p = interp; interp->CopyInterpToOld (); if (type == INTERP_SectorFloor) P_Start3dMidtexInterpolations((sector_t*)posptr, false); else if (type == INTERP_SectorCeiling) P_Start3dMidtexInterpolations((sector_t*)posptr, true); } void stopinterpolation(EInterpType type, void *posptr) { FActiveInterpolation **interp_p; FActiveInterpolation *interp = FActiveInterpolation::FindInterpolation (type, posptr, interp_p); if (interp != NULL) { if (type == INTERP_SectorFloor) P_Start3dMidtexInterpolations((sector_t*)posptr, false); else if (type == INTERP_SectorCeiling) P_Start3dMidtexInterpolations((sector_t*)posptr, true); *interp_p = interp->Next; delete interp; } } void dointerpolations(fixed_t smoothratio) //Stick at beginning of drawscreen { if (smoothratio == FRACUNIT) { didInterp = false; return; } didInterp = true; for (int i = INTERPOLATION_BUCKETS-1; i >= 0; --i) { for (FActiveInterpolation *probe = FActiveInterpolation::curiposhash[i]; probe != NULL; probe = probe->Next) { probe->DoAnInterpolation (smoothratio); } } } void restoreinterpolations() //Stick at end of drawscreen { if (didInterp) { didInterp = false; for (int i = INTERPOLATION_BUCKETS-1; i >= 0; --i) { for (FActiveInterpolation *probe = FActiveInterpolation::curiposhash[i]; probe != NULL; probe = probe->Next) { probe->CopyBakToInterp (); } } } } void SerializeInterpolations (FArchive &arc) { FActiveInterpolation *interp; int numinterpolations; int i; if (arc.IsStoring ()) { numinterpolations = FActiveInterpolation::CountInterpolations(); arc.WriteCount (numinterpolations); for (i = INTERPOLATION_BUCKETS-1; i >= 0; --i) { for (interp = FActiveInterpolation::curiposhash[i]; interp != NULL; interp = interp->Next) { arc << interp; } } } else { clearinterpolations (); numinterpolations = arc.ReadCount (); for (i = numinterpolations; i > 0; --i) { FActiveInterpolation **interp_p; arc << interp; if (FActiveInterpolation::FindInterpolation (interp->Type, interp->Address, interp_p) == NULL) { // Should always return NULL, because there should never be any duplicates stored. interp->Next = *interp_p; *interp_p = interp; } } } } FArchive &operator << (FArchive &arc, FActiveInterpolation *&interp) { BYTE type; union { vertex_t *vert; sector_t *sect; side_t *side; void *ptr; } ptr; if (arc.IsStoring ()) { type = interp->Type; ptr.ptr = interp->Address; arc << type; switch (type) { case INTERP_Vertex: arc << ptr.vert; break; case INTERP_WallPanning: arc << ptr.side; break; default: arc << ptr.sect; break; } } else { interp = new FActiveInterpolation; arc << type; interp->Type = (EInterpType)type; switch (type) { case INTERP_Vertex: arc << ptr.vert; break; case INTERP_WallPanning: arc << ptr.side; break; default: arc << ptr.sect; break; } interp->Address = ptr.ptr; } return arc; } ADD_STAT (interpolations) { int inuse, minuse, maxuse, total; FString out; total = FActiveInterpolation::CountInterpolations (&inuse, &minuse, &maxuse); out.Format ("%d interpolations buckets:%3d min:%3d max:%3d avg:%3d %d%% full %d%% buckfull", total, inuse, minuse, maxuse, inuse?total/inuse:0, total*100/INTERPOLATION_BUCKETS, inuse*100/INTERPOLATION_BUCKETS); return out; }