// 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: // Refresh of things, i.e. objects represented by sprites. // //----------------------------------------------------------------------------- #include #include #include #include "p_lnspec.h" #include "templates.h" #include "doomdef.h" #include "m_swap.h" #include "i_system.h" #include "w_wad.h" #include "swrenderer/r_main.h" #include "swrenderer/scene/r_things.h" #include "c_console.h" #include "c_cvars.h" #include "c_dispatch.h" #include "doomstat.h" #include "v_video.h" #include "sc_man.h" #include "s_sound.h" #include "sbar.h" #include "gi.h" #include "r_sky.h" #include "cmdlib.h" #include "g_level.h" #include "d_net.h" #include "colormatcher.h" #include "d_netinf.h" #include "p_effect.h" #include "r_bsp.h" #include "r_3dfloors.h" #include "swrenderer/drawers/r_draw_rgba.h" #include "swrenderer/drawers/r_draw_pal.h" #include "v_palette.h" #include "r_data/r_translate.h" #include "r_data/colormaps.h" #include "r_data/voxels.h" #include "p_local.h" #include "p_maputl.h" #include "swrenderer/things/r_voxel.h" #include "swrenderer/segments/r_drawsegment.h" #include "r_portal.h" #include "swrenderer/things/r_particle.h" #include "swrenderer/things/r_playersprite.h" #include "swrenderer/things/r_wallsprite.h" #include "swrenderer/things/r_sprite.h" #include "swrenderer/r_memory.h" EXTERN_CVAR(Int, r_drawfuzz) EXTERN_CVAR(Bool, r_drawvoxels) EXTERN_CVAR(Bool, r_blendmethod) CVAR(Bool, r_fullbrightignoresectorcolor, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR(Bool, r_splitsprites, true, CVAR_ARCHIVE) namespace swrenderer { using namespace drawerargs; // // Sprite rotation 0 is facing the viewer, // rotation 1 is one angle turn CLOCKWISE around the axis. // This is not the same as the angle, // which increases counter clockwise (protractor). // fixed_t sky1scale; // [RH] Sky 1 scale factor fixed_t sky2scale; // [RH] Sky 2 scale factor FTexture *WallSpriteTile; // // INITIALIZATION FUNCTIONS // // // GAME FUNCTIONS // bool DrewAVoxel; static vissprite_t **spritesorter; static int spritesortersize = 0; static int vsprcount; void R_DeinitSprites() { R_DeinitVisSprites(); R_DeinitRenderVoxel(); // Free vissprites sorter if (spritesorter != NULL) { delete[] spritesorter; spritesortersize = 0; spritesorter = NULL; } } // // R_ClearSprites // Called at frame start. // void R_ClearSprites (void) { R_ClearVisSprites(); DrewAVoxel = false; } static TArray portaldrawsegs; static inline void R_CollectPortals() { // This function collects all drawsegs that may be of interest to R_ClipSpriteColumnWithPortals // Having that function over the entire list of drawsegs can break down performance quite drastically. // This is doing the costly stuff only once so that R_ClipSpriteColumnWithPortals can // a) exit early if no relevant info is found and // b) skip most of the collected drawsegs which have no portal attached. portaldrawsegs.Clear(); for (drawseg_t* seg = ds_p; seg-- > firstdrawseg; ) // copied code from killough below { // I don't know what makes this happen (some old top-down portal code or possibly skybox code? something adds null lines...) // crashes at the first frame of the first map of Action2.wad if (!seg->curline) continue; line_t* line = seg->curline->linedef; // ignore minisegs from GL nodes. if (!line) continue; // check if this line will clip sprites to itself if (!line->isVisualPortal() && line->special != Line_Mirror) continue; // don't clip sprites with portal's back side (it's transparent) if (seg->curline->sidedef != line->sidedef[0]) continue; portaldrawsegs.Push(seg); } } bool R_ClipSpriteColumnWithPortals(vissprite_t* spr) { RenderPortal *renderportal = RenderPortal::Instance(); // [ZZ] 10.01.2016: don't clip sprites from the root of a skybox. if (renderportal->CurrentPortalInSkybox) return false; for (drawseg_t *seg : portaldrawsegs) { // ignore segs from other portals if (seg->CurrentPortalUniq != renderportal->CurrentPortalUniq) continue; // (all checks that are already done in R_CollectPortals have been removed for performance reasons.) // don't clip if the sprite is in front of the portal if (!P_PointOnLineSidePrecise(spr->gpos.X, spr->gpos.Y, seg->curline->linedef)) continue; // now if current column is covered by this drawseg, we clip it away if ((dc_x >= seg->x1) && (dc_x < seg->x2)) return true; } return false; } // // R_ProjectSprite // Generates a vissprite for a thing if it might be visible. // void R_ProjectSprite (AActor *thing, WaterFakeSide fakeside, F3DFloor *fakefloor, F3DFloor *fakeceiling, sector_t *current_sector, int spriteshade) { double tr_x; double tr_y; double gzt; // killough 3/27/98 double gzb; // [RH] use bottom of sprite, not actor double tx;// , tx2; double tz; double xscale = 1, yscale = 1; int x1; int x2; FTextureID picnum; FTexture *tex; FVoxelDef *voxel; vissprite_t* vis; fixed_t iscale; sector_t* heightsec; // killough 3/27/98 // Don't waste time projecting sprites that are definitely not visible. if (thing == NULL || (thing->renderflags & RF_INVISIBLE) || !thing->RenderStyle.IsVisible(thing->Alpha) || !thing->IsVisibleToPlayer() || !thing->IsInsideVisibleAngles()) { return; } RenderPortal *renderportal = RenderPortal::Instance(); // [ZZ] Or less definitely not visible (hue) // [ZZ] 10.01.2016: don't try to clip stuff inside a skybox against the current portal. if (!renderportal->CurrentPortalInSkybox && renderportal->CurrentPortal && !!P_PointOnLineSidePrecise(thing->Pos(), renderportal->CurrentPortal->dst)) return; // [RH] Interpolate the sprite's position to make it look smooth DVector3 pos = thing->InterpolatedPosition(r_TicFracF); pos.Z += thing->GetBobOffset(r_TicFracF); tex = NULL; voxel = NULL; int spritenum = thing->sprite; DVector2 spriteScale = thing->Scale; int renderflags = thing->renderflags; if (spriteScale.Y < 0) { spriteScale.Y = -spriteScale.Y; renderflags ^= RF_YFLIP; } if (thing->player != NULL) { P_CheckPlayerSprite(thing, spritenum, spriteScale); } if (thing->picnum.isValid()) { picnum = thing->picnum; tex = TexMan(picnum); if (tex->UseType == FTexture::TEX_Null) { return; } if (tex->Rotations != 0xFFFF) { // choose a different rotation based on player view spriteframe_t *sprframe = &SpriteFrames[tex->Rotations]; DAngle ang = (pos - ViewPos).Angle(); angle_t rot; if (sprframe->Texture[0] == sprframe->Texture[1]) { if (thing->flags7 & MF7_SPRITEANGLE) rot = (thing->SpriteAngle + 45.0 / 2 * 9).BAMs() >> 28; else rot = (ang - (thing->Angles.Yaw + thing->SpriteRotation) + 45.0 / 2 * 9).BAMs() >> 28; } else { if (thing->flags7 & MF7_SPRITEANGLE) rot = (thing->SpriteAngle + (45.0 / 2 * 9 - 180.0 / 16)).BAMs() >> 28; else rot = (ang - (thing->Angles.Yaw + thing->SpriteRotation) + (45.0 / 2 * 9 - 180.0 / 16)).BAMs() >> 28; } picnum = sprframe->Texture[rot]; if (sprframe->Flip & (1 << rot)) { renderflags ^= RF_XFLIP; } tex = TexMan[picnum]; // Do not animate the rotation } } else { // decide which texture to use for the sprite if ((unsigned)spritenum >= sprites.Size ()) { DPrintf (DMSG_ERROR, "R_ProjectSprite: invalid sprite number %u\n", spritenum); return; } spritedef_t *sprdef = &sprites[spritenum]; if (thing->frame >= sprdef->numframes) { // If there are no frames at all for this sprite, don't draw it. return; } else { //picnum = SpriteFrames[sprdef->spriteframes + thing->frame].Texture[0]; // choose a different rotation based on player view spriteframe_t *sprframe = &SpriteFrames[sprdef->spriteframes + thing->frame]; DAngle ang = (pos - ViewPos).Angle(); angle_t rot; if (sprframe->Texture[0] == sprframe->Texture[1]) { if (thing->flags7 & MF7_SPRITEANGLE) rot = (thing->SpriteAngle + 45.0 / 2 * 9).BAMs() >> 28; else rot = (ang - (thing->Angles.Yaw + thing->SpriteRotation) + 45.0 / 2 * 9).BAMs() >> 28; } else { if (thing->flags7 & MF7_SPRITEANGLE) rot = (thing->SpriteAngle + (45.0 / 2 * 9 - 180.0 / 16)).BAMs() >> 28; else rot = (ang - (thing->Angles.Yaw + thing->SpriteRotation) + (45.0 / 2 * 9 - 180.0 / 16)).BAMs() >> 28; } picnum = sprframe->Texture[rot]; if (sprframe->Flip & (1 << rot)) { renderflags ^= RF_XFLIP; } tex = TexMan[picnum]; // Do not animate the rotation if (r_drawvoxels) { voxel = sprframe->Voxel; } } } if (spriteScale.X < 0) { spriteScale.X = -spriteScale.X; renderflags ^= RF_XFLIP; } if (voxel == NULL && (tex == NULL || tex->UseType == FTexture::TEX_Null)) { return; } if ((renderflags & RF_SPRITETYPEMASK) == RF_WALLSPRITE) { R_ProjectWallSprite(thing, pos, picnum, spriteScale, renderflags, spriteshade); return; } // transform the origin point tr_x = pos.X - ViewPos.X; tr_y = pos.Y - ViewPos.Y; tz = tr_x * ViewTanCos + tr_y * ViewTanSin; // thing is behind view plane? if (voxel == NULL && tz < MINZ) return; tx = tr_x * ViewSin - tr_y * ViewCos; // [RH] Flip for mirrors if (renderportal->MirrorFlags & RF_XFLIP) { tx = -tx; } //tx2 = tx >> 4; // too far off the side? // if it's a voxel, it can be further off the side if ((voxel == NULL && (fabs(tx / 64) > fabs(tz))) || (voxel != NULL && (fabs(tx / 128) > fabs(tz)))) { return; } if (voxel == NULL) { // [RH] Added scaling int scaled_to = tex->GetScaledTopOffset(); int scaled_bo = scaled_to - tex->GetScaledHeight(); gzt = pos.Z + spriteScale.Y * scaled_to; gzb = pos.Z + spriteScale.Y * scaled_bo; } else { xscale = spriteScale.X * voxel->Scale; yscale = spriteScale.Y * voxel->Scale; double piv = voxel->Voxel->Mips[0].Pivot.Z; gzt = pos.Z + yscale * piv - thing->Floorclip; gzb = pos.Z + yscale * (piv - voxel->Voxel->Mips[0].SizeZ); if (gzt <= gzb) return; } // killough 3/27/98: exclude things totally separated // from the viewer, by either water or fake ceilings // killough 4/11/98: improve sprite clipping for underwater/fake ceilings heightsec = thing->Sector->GetHeightSec(); if (heightsec != NULL) // only clip things which are in special sectors { if (fakeside == WaterFakeSide::AboveCeiling) { if (gzt < heightsec->ceilingplane.ZatPoint(pos)) return; } else if (fakeside == WaterFakeSide::BelowFloor) { if (gzb >= heightsec->floorplane.ZatPoint(pos)) return; } else { if (gzt < heightsec->floorplane.ZatPoint(pos)) return; if (!(heightsec->MoreFlags & SECF_FAKEFLOORONLY) && gzb >= heightsec->ceilingplane.ZatPoint(pos)) return; } } if (voxel == NULL) { xscale = CenterX / tz; // [RH] Reject sprites that are off the top or bottom of the screen if (globaluclip * tz > ViewPos.Z - gzb || globaldclip * tz < ViewPos.Z - gzt) { return; } // [RH] Flip for mirrors renderflags ^= renderportal->MirrorFlags & RF_XFLIP; // calculate edges of the shape const double thingxscalemul = spriteScale.X / tex->Scale.X; tx -= ((renderflags & RF_XFLIP) ? (tex->GetWidth() - tex->LeftOffset - 1) : tex->LeftOffset) * thingxscalemul; double dtx1 = tx * xscale; x1 = centerx + xs_RoundToInt(dtx1); // off the right side? if (x1 >= renderportal->WindowRight) return; tx += tex->GetWidth() * thingxscalemul; x2 = centerx + xs_RoundToInt(tx * xscale); // off the left side or too small? if ((x2 < renderportal->WindowLeft || x2 <= x1)) return; xscale = spriteScale.X * xscale / tex->Scale.X; iscale = (fixed_t)(FRACUNIT / xscale); // Round towards zero to avoid wrapping in edge cases double yscale = spriteScale.Y / tex->Scale.Y; // store information in a vissprite vis = R_NewVisSprite(); vis->CurrentPortalUniq = renderportal->CurrentPortalUniq; vis->xscale = FLOAT2FIXED(xscale); vis->yscale = float(InvZtoScale * yscale / tz); vis->idepth = float(1 / tz); vis->floorclip = thing->Floorclip / yscale; vis->texturemid = tex->TopOffset - (ViewPos.Z - pos.Z + thing->Floorclip) / yscale; vis->x1 = x1 < renderportal->WindowLeft ? renderportal->WindowLeft : x1; vis->x2 = x2 > renderportal->WindowRight ? renderportal->WindowRight : x2; vis->Angle = thing->Angles.Yaw; if (renderflags & RF_XFLIP) { vis->startfrac = (tex->GetWidth() << FRACBITS) - 1; vis->xiscale = -iscale; } else { vis->startfrac = 0; vis->xiscale = iscale; } vis->startfrac += (fixed_t)(vis->xiscale * (vis->x1 - centerx + 0.5 - dtx1)); } else { vis = R_NewVisSprite(); vis->CurrentPortalUniq = renderportal->CurrentPortalUniq; vis->xscale = FLOAT2FIXED(xscale); vis->yscale = (float)yscale; vis->x1 = renderportal->WindowLeft; vis->x2 = renderportal->WindowRight; vis->idepth = 1 / MINZ; vis->floorclip = thing->Floorclip; pos.Z -= thing->Floorclip; vis->Angle = thing->Angles.Yaw + voxel->AngleOffset; int voxelspin = (thing->flags & MF_DROPPED) ? voxel->DroppedSpin : voxel->PlacedSpin; if (voxelspin != 0) { DAngle ang = double(I_FPSTime()) * voxelspin / 1000; vis->Angle -= ang; } vis->pa.vpos = { (float)ViewPos.X, (float)ViewPos.Y, (float)ViewPos.Z }; vis->pa.vang = FAngle((float)ViewAngle.Degrees); } // killough 3/27/98: save sector for special clipping later vis->heightsec = heightsec; vis->sector = thing->Sector; vis->depth = (float)tz; vis->gpos = { (float)pos.X, (float)pos.Y, (float)pos.Z }; vis->gzb = (float)gzb; // [RH] use gzb, not thing->z vis->gzt = (float)gzt; // killough 3/27/98 vis->deltax = float(pos.X - ViewPos.X); vis->deltay = float(pos.Y - ViewPos.Y); vis->renderflags = renderflags; if(thing->flags5 & MF5_BRIGHT) vis->renderflags |= RF_FULLBRIGHT; // kg3D vis->Style.RenderStyle = thing->RenderStyle; vis->FillColor = thing->fillcolor; vis->Translation = thing->Translation; // [RH] thing translation table vis->FakeFlatStat = fakeside; vis->Style.Alpha = float(thing->Alpha); vis->fakefloor = fakefloor; vis->fakeceiling = fakeceiling; vis->Style.ColormapNum = 0; vis->bInMirror = renderportal->MirrorFlags & RF_XFLIP; vis->bSplitSprite = false; if (voxel != NULL) { vis->voxel = voxel->Voxel; vis->bIsVoxel = true; vis->bWallSprite = false; DrewAVoxel = true; } else { vis->pic = tex; vis->bIsVoxel = false; vis->bWallSprite = false; } // The software renderer cannot invert the source without inverting the overlay // too. That means if the source is inverted, we need to do the reverse of what // the invert overlay flag says to do. INTBOOL invertcolormap = (vis->Style.RenderStyle.Flags & STYLEF_InvertOverlay); if (vis->Style.RenderStyle.Flags & STYLEF_InvertSource) { invertcolormap = !invertcolormap; } FDynamicColormap *mybasecolormap = basecolormap; if (current_sector->sectornum != thing->Sector->sectornum) // compare sectornums to account for R_FakeFlat copies. { // Todo: The actor is from a different sector so we have to retrieve the proper basecolormap for that sector. } // Sprites that are added to the scene must fade to black. if (vis->Style.RenderStyle == LegacyRenderStyles[STYLE_Add] && mybasecolormap->Fade != 0) { mybasecolormap = GetSpecialLights(mybasecolormap->Color, 0, mybasecolormap->Desaturate); } if (vis->Style.RenderStyle.Flags & STYLEF_FadeToBlack) { if (invertcolormap) { // Fade to white mybasecolormap = GetSpecialLights(mybasecolormap->Color, MAKERGB(255,255,255), mybasecolormap->Desaturate); invertcolormap = false; } else { // Fade to black mybasecolormap = GetSpecialLights(mybasecolormap->Color, MAKERGB(0,0,0), mybasecolormap->Desaturate); } } // get light level if (fixedcolormap != NULL) { // fixed map vis->Style.BaseColormap = fixedcolormap; vis->Style.ColormapNum = 0; } else { if (invertcolormap) { mybasecolormap = GetSpecialLights(mybasecolormap->Color, mybasecolormap->Fade.InverseColor(), mybasecolormap->Desaturate); } if (fixedlightlev >= 0) { vis->Style.BaseColormap = mybasecolormap; vis->Style.ColormapNum = fixedlightlev >> COLORMAPSHIFT; } else if (!foggy && ((renderflags & RF_FULLBRIGHT) || (thing->flags5 & MF5_BRIGHT))) { // full bright vis->Style.BaseColormap = (r_fullbrightignoresectorcolor) ? &FullNormalLight : mybasecolormap; vis->Style.ColormapNum = 0; } else { // diminished light vis->Style.ColormapNum = GETPALOOKUP( r_SpriteVisibility / MAX(tz, MINZ), spriteshade); vis->Style.BaseColormap = mybasecolormap; } } } // // R_AddSprites // During BSP traversal, this adds sprites by sector. // // killough 9/18/98: add lightlevel as parameter, fixing underwater lighting // [RH] Save which side of heightsec sprite is on here. void R_AddSprites (sector_t *sec, int lightlevel, WaterFakeSide fakeside) { F3DFloor *fakeceiling = NULL; F3DFloor *fakefloor = NULL; // BSP is traversed by subsector. // A sector might have been split into several // subsectors during BSP building. // Thus we check whether it was already added. if (sec->touching_renderthings == nullptr || sec->validcount == validcount) return; // Well, now it will be done. sec->validcount = validcount; int spriteshade = LIGHT2SHADE(lightlevel + r_actualextralight); // Handle all things in sector. for(auto p = sec->touching_renderthings; p != nullptr; p = p->m_snext) { auto thing = p->m_thing; if (thing->validcount == validcount) continue; thing->validcount = validcount; FIntCVar *cvar = thing->GetClass()->distancecheck; if (cvar != NULL && *cvar >= 0) { double dist = (thing->Pos() - ViewPos).LengthSquared(); double check = (double)**cvar; if (dist >= check * check) { continue; } } // find fake level for(auto rover : thing->Sector->e->XFloor.ffloors) { if(!(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERPLANES)) continue; if(!(rover->flags & FF_SOLID) || rover->alpha != 255) continue; if(!fakefloor) { if(!rover->top.plane->isSlope()) { if(rover->top.plane->ZatPoint(0., 0.) <= thing->Z()) fakefloor = rover; } } if(!rover->bottom.plane->isSlope()) { if(rover->bottom.plane->ZatPoint(0., 0.) >= thing->Top()) fakeceiling = rover; } } R_ProjectSprite (thing, fakeside, fakefloor, fakeceiling, sec, spriteshade); fakeceiling = NULL; fakefloor = NULL; } } // // R_SortVisSprites // // [RH] The old code for this function used a bubble sort, which was far less // than optimal with large numbers of sprites. I changed it to use the // stdlib qsort() function instead, and now it is a *lot* faster; the // more vissprites that need to be sorted, the better the performance // gain compared to the old function. // // Sort vissprites by depth, far to near // This is the standard version, which does a simple test based on depth. static bool sv_compare(vissprite_t *a, vissprite_t *b) { return a->idepth > b->idepth; } // This is an alternate version, for when one or more voxel is in view. // It does a 2D distance test based on whichever one is furthest from // the viewpoint. static bool sv_compare2d(vissprite_t *a, vissprite_t *b) { return DVector2(a->deltax, a->deltay).LengthSquared() < DVector2(b->deltax, b->deltay).LengthSquared(); } #if 0 static drawseg_t **drawsegsorter; static int drawsegsortersize = 0; // Sort vissprites by leftmost column, left to right static int sv_comparex (const void *arg1, const void *arg2) { return (*(vissprite_t **)arg2)->x1 - (*(vissprite_t **)arg1)->x1; } // Sort drawsegs by rightmost column, left to right static int sd_comparex (const void *arg1, const void *arg2) { return (*(drawseg_t **)arg2)->x2 - (*(drawseg_t **)arg1)->x2; } // Split up vissprites that intersect drawsegs void R_SplitVisSprites () { size_t start, stop; size_t numdrawsegs = ds_p - firstdrawseg; size_t numsprites; size_t spr, dseg, dseg2; if (!r_splitsprites) return; if (numdrawsegs == 0 || vissprite_p - firstvissprite == 0) return; // Sort drawsegs from left to right if (numdrawsegs > drawsegsortersize) { if (drawsegsorter != NULL) delete[] drawsegsorter; drawsegsortersize = numdrawsegs * 2; drawsegsorter = new drawseg_t *[drawsegsortersize]; } for (dseg = dseg2 = 0; dseg < numdrawsegs; ++dseg) { // Drawsegs that don't clip any sprites don't need to be considered. if (firstdrawseg[dseg].silhouette) { drawsegsorter[dseg2++] = &firstdrawseg[dseg]; } } numdrawsegs = dseg2; if (numdrawsegs == 0) { return; } qsort (drawsegsorter, numdrawsegs, sizeof(drawseg_t *), sd_comparex); // Now sort vissprites from left to right, and walk them simultaneously // with the drawsegs, splitting any that intersect. start = firstvissprite - vissprites; int p = 0; do { p++; R_SortVisSprites (sv_comparex, start); stop = vissprite_p - vissprites; numsprites = stop - start; spr = dseg = 0; do { vissprite_t *vis = spritesorter[spr], *vis2; // Skip drawsegs until we get to one that doesn't end before the sprite // begins. while (dseg < numdrawsegs && drawsegsorter[dseg]->x2 <= vis->x1) { dseg++; } // Now split the sprite against any drawsegs it intersects for (dseg2 = dseg; dseg2 < numdrawsegs; dseg2++) { drawseg_t *ds = drawsegsorter[dseg2]; if (ds->x1 > vis->x2 || ds->x2 < vis->x1) continue; if ((vis->idepth < ds->siz1) != (vis->idepth < ds->siz2)) { // The drawseg is crossed; find the x where the intersection occurs int cross = Scale (vis->idepth - ds->siz1, ds->sx2 - ds->sx1, ds->siz2 - ds->siz1) + ds->sx1 + 1; /* if (cross < ds->x1 || cross > ds->x2) { // The original seg is crossed, but the drawseg is not continue; } */ if (cross <= vis->x1 || cross >= vis->x2) { // Don't create 0-sized sprites continue; } vis->bSplitSprite = true; // Create a new vissprite for the right part of the sprite vis2 = R_NewVisSprite (); *vis2 = *vis; vis2->startfrac += vis2->xiscale * (cross - vis2->x1); vis->x2 = cross-1; vis2->x1 = cross; //vis2->alpha /= 2; //vis2->RenderStyle = STYLE_Add; if (vis->idepth < ds->siz1) { // Left is in back, right is in front vis->sector = ds->curline->backsector; vis2->sector = ds->curline->frontsector; } else { // Right is in front, left is in back vis->sector = ds->curline->frontsector; vis2->sector = ds->curline->backsector; } } } } while (dseg < numdrawsegs && ++spr < numsprites); // Repeat for any new sprites that were added. } while (start = stop, stop != vissprite_p - vissprites); } #endif #ifdef __GNUC__ static void swap(vissprite_t *&a, vissprite_t *&b) { vissprite_t *t = a; a = b; b = t; } #endif void R_SortVisSprites (bool (*compare)(vissprite_t *, vissprite_t *), size_t first) { int i; vissprite_t **spr; vsprcount = int(vissprite_p - &vissprites[first]); if (vsprcount == 0) return; if (spritesortersize < MaxVisSprites) { if (spritesorter != NULL) delete[] spritesorter; spritesorter = new vissprite_t *[MaxVisSprites]; spritesortersize = MaxVisSprites; } if (!(i_compatflags & COMPATF_SPRITESORT)) { for (i = 0, spr = firstvissprite; i < vsprcount; i++, spr++) { spritesorter[i] = *spr; } } else { // If the compatibility option is on sprites of equal distance need to // be sorted in inverse order. This is most easily achieved by // filling the sort array backwards before the sort. for (i = 0, spr = firstvissprite + vsprcount-1; i < vsprcount; i++, spr--) { spritesorter[i] = *spr; } } std::stable_sort(&spritesorter[0], &spritesorter[vsprcount], compare); } // // R_DrawSprite // void R_DrawSprite (vissprite_t *spr) { static short clipbot[MAXWIDTH]; static short cliptop[MAXWIDTH]; drawseg_t *ds; int i; int x1, x2; int r1, r2; short topclip, botclip; short *clip1, *clip2; FSWColormap *colormap = spr->Style.BaseColormap; int colormapnum = spr->Style.ColormapNum; F3DFloor *rover; FDynamicColormap *mybasecolormap; Clip3DFloors *clip3d = Clip3DFloors::Instance(); // [RH] Check for particles if (!spr->bIsVoxel && spr->pic == NULL) { // kg3D - reject invisible parts if ((clip3d->fake3D & FAKE3D_CLIPBOTTOM) && spr->gpos.Z <= clip3d->sclipBottom) return; if ((clip3d->fake3D & FAKE3D_CLIPTOP) && spr->gpos.Z >= clip3d->sclipTop) return; R_DrawParticle (spr); return; } x1 = spr->x1; x2 = spr->x2; // [RH] Quickly reject sprites with bad x ranges. if (x1 >= x2) return; // [RH] Sprites split behind a one-sided line can also be discarded. if (spr->sector == NULL) return; // kg3D - reject invisible parts if ((clip3d->fake3D & FAKE3D_CLIPBOTTOM) && spr->gzt <= clip3d->sclipBottom) return; if ((clip3d->fake3D & FAKE3D_CLIPTOP) && spr->gzb >= clip3d->sclipTop) return; // kg3D - correct colors now if (!fixedcolormap && fixedlightlev < 0 && spr->sector->e && spr->sector->e->XFloor.lightlist.Size()) { if (!(clip3d->fake3D & FAKE3D_CLIPTOP)) { clip3d->sclipTop = spr->sector->ceilingplane.ZatPoint(ViewPos); } sector_t *sec = NULL; for (i = spr->sector->e->XFloor.lightlist.Size() - 1; i >= 0; i--) { if (clip3d->sclipTop <= spr->sector->e->XFloor.lightlist[i].plane.Zat0()) { rover = spr->sector->e->XFloor.lightlist[i].caster; if (rover) { if (rover->flags & FF_DOUBLESHADOW && clip3d->sclipTop <= rover->bottom.plane->Zat0()) { break; } sec = rover->model; if (rover->flags & FF_FADEWALLS) { mybasecolormap = sec->ColorMap; } else { mybasecolormap = spr->sector->e->XFloor.lightlist[i].extra_colormap; } } break; } } // found new values, recalculate if (sec) { INTBOOL invertcolormap = (spr->Style.RenderStyle.Flags & STYLEF_InvertOverlay); if (spr->Style.RenderStyle.Flags & STYLEF_InvertSource) { invertcolormap = !invertcolormap; } // Sprites that are added to the scene must fade to black. if (spr->Style.RenderStyle == LegacyRenderStyles[STYLE_Add] && mybasecolormap->Fade != 0) { mybasecolormap = GetSpecialLights(mybasecolormap->Color, 0, mybasecolormap->Desaturate); } if (spr->Style.RenderStyle.Flags & STYLEF_FadeToBlack) { if (invertcolormap) { // Fade to white mybasecolormap = GetSpecialLights(mybasecolormap->Color, MAKERGB(255,255,255), mybasecolormap->Desaturate); invertcolormap = false; } else { // Fade to black mybasecolormap = GetSpecialLights(mybasecolormap->Color, MAKERGB(0,0,0), mybasecolormap->Desaturate); } } // get light level if (invertcolormap) { mybasecolormap = GetSpecialLights(mybasecolormap->Color, mybasecolormap->Fade.InverseColor(), mybasecolormap->Desaturate); } if (fixedlightlev >= 0) { spr->Style.BaseColormap = mybasecolormap; spr->Style.ColormapNum = fixedlightlev >> COLORMAPSHIFT; } else if (!foggy && (spr->renderflags & RF_FULLBRIGHT)) { // full bright spr->Style.BaseColormap = (r_fullbrightignoresectorcolor) ? &FullNormalLight : mybasecolormap; spr->Style.ColormapNum = 0; } else { // diminished light int spriteshade = LIGHT2SHADE(sec->lightlevel + r_actualextralight); spr->Style.BaseColormap = mybasecolormap; spr->Style.ColormapNum = GETPALOOKUP(r_SpriteVisibility / MAX(MINZ, (double)spr->depth), spriteshade); } } } // [RH] Initialize the clipping arrays to their largest possible range // instead of using a special "not clipped" value. This eliminates // visual anomalies when looking down and should be faster, too. topclip = 0; botclip = viewheight; // killough 3/27/98: // Clip the sprite against deep water and/or fake ceilings. // [RH] rewrote this to be based on which part of the sector is really visible double scale = InvZtoScale * spr->idepth; double hzb = DBL_MIN, hzt = DBL_MAX; if (spr->bIsVoxel && spr->floorclip != 0) { hzb = spr->gzb; } if (spr->heightsec && !(spr->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC)) { // only things in specially marked sectors if (spr->FakeFlatStat != WaterFakeSide::AboveCeiling) { double hz = spr->heightsec->floorplane.ZatPoint(spr->gpos); int h = xs_RoundToInt(CenterY - (hz - ViewPos.Z) * scale); if (spr->FakeFlatStat == WaterFakeSide::BelowFloor) { // seen below floor: clip top if (!spr->bIsVoxel && h > topclip) { topclip = short(MIN(h, viewheight)); } hzt = MIN(hzt, hz); } else { // seen in the middle: clip bottom if (!spr->bIsVoxel && h < botclip) { botclip = MAX (0, h); } hzb = MAX(hzb, hz); } } if (spr->FakeFlatStat != WaterFakeSide::BelowFloor && !(spr->heightsec->MoreFlags & SECF_FAKEFLOORONLY)) { double hz = spr->heightsec->ceilingplane.ZatPoint(spr->gpos); int h = xs_RoundToInt(CenterY - (hz - ViewPos.Z) * scale); if (spr->FakeFlatStat == WaterFakeSide::AboveCeiling) { // seen above ceiling: clip bottom if (!spr->bIsVoxel && h < botclip) { botclip = MAX (0, h); } hzb = MAX(hzb, hz); } else { // seen in the middle: clip top if (!spr->bIsVoxel && h > topclip) { topclip = MIN(h, viewheight); } hzt = MIN(hzt, hz); } } } // killough 3/27/98: end special clipping for deep water / fake ceilings else if (!spr->bIsVoxel && spr->floorclip) { // [RH] Move floorclip stuff from R_DrawVisSprite to here //int clip = ((FLOAT2FIXED(CenterY) - FixedMul (spr->texturemid - (spr->pic->GetHeight() << FRACBITS) + spr->floorclip, spr->yscale)) >> FRACBITS); int clip = xs_RoundToInt(CenterY - (spr->texturemid - spr->pic->GetHeight() + spr->floorclip) * spr->yscale); if (clip < botclip) { botclip = MAX(0, clip); } } if (clip3d->fake3D & FAKE3D_CLIPBOTTOM) { if (!spr->bIsVoxel) { double hz = clip3d->sclipBottom; if (spr->fakefloor) { double floorz = spr->fakefloor->top.plane->Zat0(); if (ViewPos.Z > floorz && floorz == clip3d->sclipBottom ) { hz = spr->fakefloor->bottom.plane->Zat0(); } } int h = xs_RoundToInt(CenterY - (hz - ViewPos.Z) * scale); if (h < botclip) { botclip = MAX(0, h); } } hzb = MAX(hzb, clip3d->sclipBottom); } if (clip3d->fake3D & FAKE3D_CLIPTOP) { if (!spr->bIsVoxel) { double hz = clip3d->sclipTop; if (spr->fakeceiling != NULL) { double ceilingZ = spr->fakeceiling->bottom.plane->Zat0(); if (ViewPos.Z < ceilingZ && ceilingZ == clip3d->sclipTop) { hz = spr->fakeceiling->top.plane->Zat0(); } } int h = xs_RoundToInt(CenterY - (hz - ViewPos.Z) * scale); if (h > topclip) { topclip = short(MIN(h, viewheight)); } } hzt = MIN(hzt, clip3d->sclipTop); } if (topclip >= botclip) { spr->Style.BaseColormap = colormap; spr->Style.ColormapNum = colormapnum; return; } i = x2 - x1; clip1 = clipbot + x1; clip2 = cliptop + x1; do { *clip1++ = botclip; *clip2++ = topclip; } while (--i); // Scan drawsegs from end to start for obscuring segs. // The first drawseg that is closer than the sprite is the clip seg. // Modified by Lee Killough: // (pointer check was originally nonportable // and buggy, by going past LEFT end of array): // for (ds=ds_p-1 ; ds >= drawsegs ; ds--) old buggy code for (ds = ds_p; ds-- > firstdrawseg; ) // new -- killough { // [ZZ] portal handling here //if (ds->CurrentPortalUniq != spr->CurrentPortalUniq) // continue; // [ZZ] WARNING: uncommenting the two above lines, totally breaks sprite clipping // kg3D - no clipping on fake segs if (ds->fake) continue; // determine if the drawseg obscures the sprite if (ds->x1 >= x2 || ds->x2 <= x1 || (!(ds->silhouette & SIL_BOTH) && ds->maskedtexturecol == -1 && !ds->bFogBoundary) ) { // does not cover sprite continue; } r1 = MAX (ds->x1, x1); r2 = MIN (ds->x2, x2); float neardepth, fardepth; if (!spr->bWallSprite) { if (ds->sz1 < ds->sz2) { neardepth = ds->sz1, fardepth = ds->sz2; } else { neardepth = ds->sz2, fardepth = ds->sz1; } } // Check if sprite is in front of draw seg: if ((!spr->bWallSprite && neardepth > spr->depth) || ((spr->bWallSprite || fardepth > spr->depth) && (spr->gpos.Y - ds->curline->v1->fY()) * (ds->curline->v2->fX() - ds->curline->v1->fX()) - (spr->gpos.X - ds->curline->v1->fX()) * (ds->curline->v2->fY() - ds->curline->v1->fY()) <= 0)) { RenderPortal *renderportal = RenderPortal::Instance(); // seg is behind sprite, so draw the mid texture if it has one if (ds->CurrentPortalUniq == renderportal->CurrentPortalUniq && // [ZZ] instead, portal uniq check is made here (ds->maskedtexturecol != -1 || ds->bFogBoundary)) R_RenderMaskedSegRange (ds, r1, r2); continue; } // clip this piece of the sprite // killough 3/27/98: optimized and made much shorter // [RH] Optimized further (at least for VC++; // other compilers should be at least as good as before) if (ds->silhouette & SIL_BOTTOM) //bottom sil { clip1 = clipbot + r1; clip2 = openings + ds->sprbottomclip + r1 - ds->x1; i = r2 - r1; do { if (*clip1 > *clip2) *clip1 = *clip2; clip1++; clip2++; } while (--i); } if (ds->silhouette & SIL_TOP) // top sil { clip1 = cliptop + r1; clip2 = openings + ds->sprtopclip + r1 - ds->x1; i = r2 - r1; do { if (*clip1 < *clip2) *clip1 = *clip2; clip1++; clip2++; } while (--i); } } // all clipping has been performed, so draw the sprite if (!spr->bIsVoxel) { if (!spr->bWallSprite) { R_DrawVisSprite(spr, clipbot, cliptop); } else { R_DrawWallSprite(spr, clipbot, cliptop); } } else { // If it is completely clipped away, don't bother drawing it. if (cliptop[x2] >= clipbot[x2]) { for (i = x1; i < x2; ++i) { if (cliptop[i] < clipbot[i]) { break; } } if (i == x2) { spr->Style.BaseColormap = colormap; spr->Style.ColormapNum = colormapnum; return; } } // Add everything outside the left and right edges to the clipping array // for R_DrawVisVoxel(). if (x1 > 0) { fillshort(cliptop, x1, viewheight); } if (x2 < viewwidth - 1) { fillshort(cliptop + x2, viewwidth - x2, viewheight); } int minvoxely = spr->gzt <= hzt ? 0 : xs_RoundToInt((spr->gzt - hzt) / spr->yscale); int maxvoxely = spr->gzb > hzb ? INT_MAX : xs_RoundToInt((spr->gzt - hzb) / spr->yscale); R_DrawVisVoxel(spr, minvoxely, maxvoxely, cliptop, clipbot); } spr->Style.BaseColormap = colormap; spr->Style.ColormapNum = colormapnum; } // kg3D: // R_DrawMasked contains sorting // original renamed to R_DrawMaskedSingle void R_DrawMaskedSingle (bool renew) { drawseg_t *ds; int i; RenderPortal *renderportal = RenderPortal::Instance(); for (i = vsprcount; i > 0; i--) { if (spritesorter[i-1]->CurrentPortalUniq != renderportal->CurrentPortalUniq) continue; // probably another time R_DrawSprite (spritesorter[i-1]); } // render any remaining masked mid textures // Modified by Lee Killough: // (pointer check was originally nonportable // and buggy, by going past LEFT end of array): // for (ds=ds_p-1 ; ds >= drawsegs ; ds--) old buggy code if (renew) { Clip3DFloors::Instance()->fake3D |= FAKE3D_REFRESHCLIP; } for (ds = ds_p; ds-- > firstdrawseg; ) // new -- killough { // [ZZ] the same as above if (ds->CurrentPortalUniq != renderportal->CurrentPortalUniq) continue; // kg3D - no fake segs if (ds->fake) continue; if (ds->maskedtexturecol != -1 || ds->bFogBoundary) { R_RenderMaskedSegRange (ds, ds->x1, ds->x2); } } } void R_DrawHeightPlanes(double height); // kg3D - fake planes void R_DrawMasked () { R_CollectPortals(); R_SortVisSprites (DrewAVoxel ? sv_compare2d : sv_compare, firstvissprite - vissprites); Clip3DFloors *clip3d = Clip3DFloors::Instance(); if (clip3d->height_top == NULL) { // kg3D - no visible 3D floors, normal rendering R_DrawMaskedSingle(false); } else { // kg3D - correct sorting HeightLevel *hl; // ceilings for (hl = clip3d->height_cur; hl != NULL && hl->height >= ViewPos.Z; hl = hl->prev) { if (hl->next) { clip3d->fake3D = FAKE3D_CLIPBOTTOM | FAKE3D_CLIPTOP; clip3d->sclipTop = hl->next->height; } else { clip3d->fake3D = FAKE3D_CLIPBOTTOM; } clip3d->sclipBottom = hl->height; R_DrawMaskedSingle(true); R_DrawHeightPlanes(hl->height); } // floors clip3d->fake3D = FAKE3D_DOWN2UP | FAKE3D_CLIPTOP; clip3d->sclipTop = clip3d->height_top->height; R_DrawMaskedSingle(true); hl = clip3d->height_top; for (hl = clip3d->height_top; hl != NULL && hl->height < ViewPos.Z; hl = hl->next) { R_DrawHeightPlanes(hl->height); if (hl->next) { clip3d->fake3D = FAKE3D_DOWN2UP | FAKE3D_CLIPTOP | FAKE3D_CLIPBOTTOM; clip3d->sclipTop = hl->next->height; } else { clip3d->fake3D = FAKE3D_DOWN2UP | FAKE3D_CLIPBOTTOM; } clip3d->sclipBottom = hl->height; R_DrawMaskedSingle(true); } clip3d->DeleteHeights(); clip3d->fake3D = 0; } R_DrawPlayerSprites(); } extern double BaseYaspectMul;; inline int sgn(int v) { return v < 0 ? -1 : v > 0 ? 1 : 0; } }