// 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. // // This file contains some code from the Build Engine. // // "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. // //----------------------------------------------------------------------------- #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 "r_local.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_plane.h" #include "r_segs.h" #include "r_3dfloors.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" // [RH] A c-buffer. Used for keeping track of offscreen voxel spans. struct FCoverageBuffer { struct Span { Span *NextSpan; short Start, Stop; }; FCoverageBuffer(int size); ~FCoverageBuffer(); void Clear(); void InsertSpan(int listnum, int start, int stop); Span *AllocSpan(); FMemArena SpanArena; Span **Spans; // [0..NumLists-1] span lists Span *FreeSpans; unsigned int NumLists; }; extern double globaluclip, globaldclip; extern float MaskedScaleY; #define MINZ double((2048*4) / double(1 << 20)) #define BASEXCENTER (160) #define BASEYCENTER (100) EXTERN_CVAR (Bool, st_scale) EXTERN_CVAR(Bool, r_shadercolormaps) EXTERN_CVAR(Int, r_drawfuzz) EXTERN_CVAR(Bool, r_deathcamera); CVAR(Bool, r_fullbrightignoresectorcolor, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); // // 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). // double pspritexscale; double pspritexiscale; double pspriteyscale; fixed_t sky1scale; // [RH] Sky 1 scale factor fixed_t sky2scale; // [RH] Sky 2 scale factor // Used to store a psprite's drawing information if it needs to be drawn later. struct vispsp_t { vissprite_t *vis; FDynamicColormap *basecolormap; int x1; }; TArray vispsprites; unsigned int vispspindex; static int spriteshade; FTexture *WallSpriteTile; // constant arrays // used for psprite clipping and initializing clipping short zeroarray[MAXWIDTH]; short screenheightarray[MAXWIDTH]; EXTERN_CVAR (Bool, r_drawplayersprites) EXTERN_CVAR (Bool, r_drawvoxels) // // INITIALIZATION FUNCTIONS // int OffscreenBufferWidth, OffscreenBufferHeight; BYTE *OffscreenColorBuffer; FCoverageBuffer *OffscreenCoverageBuffer; // // GAME FUNCTIONS // int MaxVisSprites; vissprite_t **vissprites; vissprite_t **firstvissprite; vissprite_t **vissprite_p; vissprite_t **lastvissprite; int newvissprite; bool DrewAVoxel; static vissprite_t **spritesorter; static int spritesortersize = 0; static int vsprcount; static void R_ProjectWallSprite(AActor *thing, const DVector3 &pos, FTextureID picnum, const DVector2 &scale, INTBOOL flip); void R_DeinitSprites() { // Free vissprites for (int i = 0; i < MaxVisSprites; ++i) { delete vissprites[i]; } free (vissprites); vissprites = NULL; vissprite_p = lastvissprite = NULL; MaxVisSprites = 0; // Free vissprites sorter if (spritesorter != NULL) { delete[] spritesorter; spritesortersize = 0; spritesorter = NULL; } // Free offscreen buffer if (OffscreenColorBuffer != NULL) { delete[] OffscreenColorBuffer; OffscreenColorBuffer = NULL; } if (OffscreenCoverageBuffer != NULL) { delete OffscreenCoverageBuffer; OffscreenCoverageBuffer = NULL; } OffscreenBufferHeight = OffscreenBufferWidth = 0; } // // R_ClearSprites // Called at frame start. // void R_ClearSprites (void) { vissprite_p = firstvissprite; DrewAVoxel = false; } // // R_NewVisSprite // vissprite_t *R_NewVisSprite (void) { if (vissprite_p == lastvissprite) { ptrdiff_t firstvisspritenum = firstvissprite - vissprites; ptrdiff_t prevvisspritenum = vissprite_p - vissprites; MaxVisSprites = MaxVisSprites ? MaxVisSprites * 2 : 128; vissprites = (vissprite_t **)M_Realloc (vissprites, MaxVisSprites * sizeof(vissprite_t)); lastvissprite = &vissprites[MaxVisSprites]; firstvissprite = &vissprites[firstvisspritenum]; vissprite_p = &vissprites[prevvisspritenum]; DPrintf (DMSG_NOTIFY, "MaxVisSprites increased to %d\n", MaxVisSprites); // Allocate sprites from the new pile for (vissprite_t **p = vissprite_p; p < lastvissprite; ++p) { *p = new vissprite_t; } } vissprite_p++; return *(vissprite_p-1); } // // R_DrawMaskedColumn // Used for sprites and masked mid textures. // Masked means: partly transparent, i.e. stored // in posts/runs of opaque pixels. // short* mfloorclip; short* mceilingclip; double spryscale; double sprtopscreen; bool sprflipvert; void R_DrawMaskedColumn (const BYTE *column, const FTexture::Span *span) { const fixed_t centeryfrac = FLOAT2FIXED(CenterY); const fixed_t texturemid = FLOAT2FIXED(dc_texturemid); while (span->Length != 0) { const int length = span->Length; const int top = span->TopOffset; // calculate unclipped screen coordinates for post dc_yl = xs_RoundToInt(sprtopscreen + spryscale * top); dc_yh = xs_RoundToInt(sprtopscreen + spryscale * (top + length)) - 1; if (sprflipvert) { swapvalues (dc_yl, dc_yh); } if (dc_yh >= mfloorclip[dc_x]) { dc_yh = mfloorclip[dc_x] - 1; } if (dc_yl < mceilingclip[dc_x]) { dc_yl = mceilingclip[dc_x]; } if (dc_yl <= dc_yh) { if (sprflipvert) { dc_texturefrac = (dc_yl*dc_iscale) - (top << FRACBITS) - FixedMul (centeryfrac, dc_iscale) - texturemid; const fixed_t maxfrac = length << FRACBITS; while (dc_texturefrac >= maxfrac) { if (++dc_yl > dc_yh) goto nextpost; dc_texturefrac += dc_iscale; } fixed_t endfrac = dc_texturefrac + (dc_yh-dc_yl)*dc_iscale; while (endfrac < 0) { if (--dc_yh < dc_yl) goto nextpost; endfrac -= dc_iscale; } } else { dc_texturefrac = texturemid - (top << FRACBITS) + (dc_yl*dc_iscale) - FixedMul (centeryfrac-FRACUNIT, dc_iscale); while (dc_texturefrac < 0) { if (++dc_yl > dc_yh) goto nextpost; dc_texturefrac += dc_iscale; } fixed_t endfrac = dc_texturefrac + (dc_yh-dc_yl)*dc_iscale; const fixed_t maxfrac = length << FRACBITS; if (dc_yh < mfloorclip[dc_x]-1 && endfrac < maxfrac - dc_iscale) { dc_yh++; } else while (endfrac >= maxfrac) { if (--dc_yh < dc_yl) goto nextpost; endfrac -= dc_iscale; } } dc_source = column + top; dc_dest = ylookup[dc_yl] + dc_x + dc_destorg; dc_count = dc_yh - dc_yl + 1; colfunc (); } nextpost: span++; } } // [ZZ] // R_ClipSpriteColumnWithPortals // 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); } } static inline bool R_ClipSpriteColumnWithPortals(vissprite_t* spr) { // [ZZ] 10.01.2016: don't clip sprites from the root of a skybox. if (CurrentPortalInSkybox) return false; for (drawseg_t *seg : portaldrawsegs) { // ignore segs from other portals if (seg->CurrentPortalUniq != 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_DrawVisSprite // mfloorclip and mceilingclip should also be set. // void R_DrawVisSprite (vissprite_t *vis) { const BYTE *pixels; const FTexture::Span *spans; fixed_t frac; FTexture *tex; int x2, stop4; fixed_t xiscale; ESPSResult mode; bool ispsprite = (!vis->sector && vis->gpos != FVector3(0, 0, 0)); if (vis->xscale == 0 || fabs(vis->yscale) < (1.0f / 32000.0f)) { // scaled to 0; can't see return; } fixed_t centeryfrac = FLOAT2FIXED(CenterY); dc_colormap = vis->Style.colormap; mode = R_SetPatchStyle (vis->Style.RenderStyle, vis->Style.Alpha, vis->Translation, vis->FillColor); if (vis->Style.RenderStyle == LegacyRenderStyles[STYLE_Shaded]) { // For shaded sprites, R_SetPatchStyle sets a dc_colormap to an alpha table, but // it is the brightest one. We need to get back to the proper light level for // this sprite. dc_colormap += vis->ColormapNum << COLORMAPSHIFT; } if (mode != DontDraw) { if (mode == DoDraw0) { // One column at a time stop4 = vis->x1; } else // DoDraw1 { // Up to four columns at a time stop4 = vis->x2 & ~3; } tex = vis->pic; spryscale = vis->yscale; sprflipvert = false; dc_iscale = FLOAT2FIXED(1 / vis->yscale); frac = vis->startfrac; xiscale = vis->xiscale; dc_texturemid = vis->texturemid; if (vis->renderflags & RF_YFLIP) { sprflipvert = true; spryscale = -spryscale; dc_iscale = -dc_iscale; dc_texturemid -= vis->pic->GetHeight(); sprtopscreen = CenterY + dc_texturemid * spryscale; } else { sprflipvert = false; sprtopscreen = CenterY - dc_texturemid * spryscale; } dc_x = vis->x1; x2 = vis->x2; if (dc_x < x2) { while ((dc_x < stop4) && (dc_x & 3)) { pixels = tex->GetColumn (frac >> FRACBITS, &spans); if (ispsprite || !R_ClipSpriteColumnWithPortals(vis)) R_DrawMaskedColumn (pixels, spans); dc_x++; frac += xiscale; } while (dc_x < stop4) { rt_initcols(); for (int zz = 4; zz; --zz) { pixels = tex->GetColumn (frac >> FRACBITS, &spans); if (ispsprite || !R_ClipSpriteColumnWithPortals(vis)) R_DrawMaskedColumnHoriz (pixels, spans); dc_x++; frac += xiscale; } rt_draw4cols (dc_x - 4); } while (dc_x < x2) { pixels = tex->GetColumn (frac >> FRACBITS, &spans); if (ispsprite || !R_ClipSpriteColumnWithPortals(vis)) R_DrawMaskedColumn (pixels, spans); dc_x++; frac += xiscale; } } } R_FinishSetPatchStyle (); NetUpdate (); } void R_DrawWallSprite(vissprite_t *spr) { int x1, x2; double iyscale; x1 = MAX(spr->x1, spr->wallc.sx1); x2 = MIN(spr->x2, spr->wallc.sx2); if (x1 >= x2) return; WallT.InitFromWallCoords(&spr->wallc); PrepWall(swall, lwall, spr->pic->GetWidth() << FRACBITS, x1, x2); iyscale = 1 / spr->yscale; dc_texturemid = (spr->gzt - ViewPos.Z) * iyscale; if (spr->renderflags & RF_XFLIP) { int right = (spr->pic->GetWidth() << FRACBITS) - 1; for (int i = x1; i < x2; i++) { lwall[i] = right - lwall[i]; } } // Prepare lighting bool calclighting = false; FDynamicColormap *usecolormap = basecolormap; bool rereadcolormap = true; // Decals that are added to the scene must fade to black. if (spr->Style.RenderStyle == LegacyRenderStyles[STYLE_Add] && usecolormap->Fade != 0) { usecolormap = GetSpecialLights(usecolormap->Color, 0, usecolormap->Desaturate); rereadcolormap = false; } int shade = LIGHT2SHADE(spr->sector->lightlevel + r_actualextralight); GlobVis = r_WallVisibility; rw_lightleft = float (GlobVis / spr->wallc.sz1); rw_lightstep = float((GlobVis / spr->wallc.sz2 - rw_lightleft) / (spr->wallc.sx2 - spr->wallc.sx1)); rw_light = rw_lightleft + (x1 - spr->wallc.sx1) * rw_lightstep; if (fixedlightlev >= 0) dc_colormap = usecolormap->Maps + fixedlightlev; else if (fixedcolormap != NULL) dc_colormap = fixedcolormap; else if (!foggy && (spr->renderflags & RF_FULLBRIGHT)) dc_colormap = (r_fullbrightignoresectorcolor) ? &FullNormalLight->Maps : usecolormap->Maps; else calclighting = true; // Draw it WallSpriteTile = spr->pic; if (spr->renderflags & RF_YFLIP) { sprflipvert = true; iyscale = -iyscale; dc_texturemid -= spr->pic->GetHeight(); } else { sprflipvert = false; } MaskedScaleY = (float)iyscale; dc_x = x1; ESPSResult mode; mode = R_SetPatchStyle (spr->Style.RenderStyle, spr->Style.Alpha, spr->Translation, spr->FillColor); // R_SetPatchStyle can modify basecolormap. if (rereadcolormap) { usecolormap = basecolormap; } if (mode == DontDraw) { return; } else { int stop4; if (mode == DoDraw0) { // 1 column at a time stop4 = dc_x; } else // DoDraw1 { // up to 4 columns at a time stop4 = x2 & ~3; } while ((dc_x < stop4) && (dc_x & 3)) { if (calclighting) { // calculate lighting dc_colormap = usecolormap->Maps + (GETPALOOKUP (rw_light, shade) << COLORMAPSHIFT); } if (!R_ClipSpriteColumnWithPortals(spr)) R_WallSpriteColumn(R_DrawMaskedColumn); dc_x++; } while (dc_x < stop4) { if (calclighting) { // calculate lighting dc_colormap = usecolormap->Maps + (GETPALOOKUP (rw_light, shade) << COLORMAPSHIFT); } rt_initcols(); for (int zz = 4; zz; --zz) { if (!R_ClipSpriteColumnWithPortals(spr)) R_WallSpriteColumn(R_DrawMaskedColumnHoriz); dc_x++; } rt_draw4cols(dc_x - 4); } while (dc_x < x2) { if (calclighting) { // calculate lighting dc_colormap = usecolormap->Maps + (GETPALOOKUP (rw_light, shade) << COLORMAPSHIFT); } if (!R_ClipSpriteColumnWithPortals(spr)) R_WallSpriteColumn(R_DrawMaskedColumn); dc_x++; } } R_FinishSetPatchStyle(); } void R_WallSpriteColumn (void (*drawfunc)(const BYTE *column, const FTexture::Span *spans)) { float iscale = swall[dc_x] * MaskedScaleY; dc_iscale = FLOAT2FIXED(iscale); spryscale = 1 / iscale; if (sprflipvert) sprtopscreen = CenterY + dc_texturemid * spryscale; else sprtopscreen = CenterY - dc_texturemid * spryscale; const BYTE *column; const FTexture::Span *spans; column = WallSpriteTile->GetColumn (lwall[dc_x] >> FRACBITS, &spans); dc_texturefrac = 0; drawfunc (column, spans); rw_light += rw_lightstep; } void R_DrawVisVoxel(vissprite_t *spr, int minslabz, int maxslabz, short *cliptop, short *clipbot) { ESPSResult mode; int flags = 0; // Do setup for blending. dc_colormap = spr->Style.colormap; mode = R_SetPatchStyle(spr->Style.RenderStyle, spr->Style.Alpha, spr->Translation, spr->FillColor); if (mode == DontDraw) { return; } if (colfunc == fuzzcolfunc || colfunc == R_FillColumnP) { flags = DVF_OFFSCREEN | DVF_SPANSONLY; } else if (colfunc != basecolfunc) { flags = DVF_OFFSCREEN; } if (flags != 0) { R_CheckOffscreenBuffer(RenderTarget->GetWidth(), RenderTarget->GetHeight(), !!(flags & DVF_SPANSONLY)); } if (spr->bInMirror) { flags |= DVF_MIRRORED; } // Render the voxel, either directly to the screen or offscreen. R_DrawVoxel(spr->pa.vpos, spr->pa.vang, spr->gpos, spr->Angle, spr->xscale, FLOAT2FIXED(spr->yscale), spr->voxel, spr->Style.colormap, cliptop, clipbot, minslabz, maxslabz, flags); // Blend the voxel, if that's what we need to do. if ((flags & ~DVF_MIRRORED) != 0) { for (int x = 0; x < viewwidth; ++x) { if (!(flags & DVF_SPANSONLY) && (x & 3) == 0) { rt_initcols(OffscreenColorBuffer + x * OffscreenBufferHeight); } for (FCoverageBuffer::Span *span = OffscreenCoverageBuffer->Spans[x]; span != NULL; span = span->NextSpan) { if (flags & DVF_SPANSONLY) { dc_x = x; dc_yl = span->Start; dc_yh = span->Stop - 1; dc_count = span->Stop - span->Start; dc_dest = ylookup[span->Start] + x + dc_destorg; colfunc(); } else { unsigned int **tspan = &dc_ctspan[x & 3]; (*tspan)[0] = span->Start; (*tspan)[1] = span->Stop - 1; *tspan += 2; } } if (!(flags & DVF_SPANSONLY) && (x & 3) == 3) { rt_draw4cols(x - 3); } } } R_FinishSetPatchStyle(); NetUpdate(); } // // R_ProjectSprite // Generates a vissprite for a thing if it might be visible. // void R_ProjectSprite (AActor *thing, int fakeside, F3DFloor *fakefloor, F3DFloor *fakeceiling) { 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; } // [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 (!CurrentPortalInSkybox && CurrentPortal && !!P_PointOnLineSidePrecise(thing->Pos(), 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); 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 (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 == FAKED_AboveCeiling) { if (gzt < heightsec->ceilingplane.ZatPoint(pos)) return; } else if (fakeside == FAKED_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 ^= 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; x1 = centerx + xs_RoundToInt(tx * xscale); // off the right side? if (x1 >= WindowRight) return; tx += tex->GetWidth() * thingxscalemul; x2 = centerx + xs_RoundToInt(tx * xscale); // off the left side or too small? if ((x2 < WindowLeft || x2 <= x1)) return; xscale = spriteScale.X * xscale / tex->Scale.X; iscale = (tex->GetWidth() << FRACBITS) / (x2 - x1); double yscale = spriteScale.Y / tex->Scale.Y; // store information in a vissprite vis = R_NewVisSprite(); vis->CurrentPortalUniq = 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 < WindowLeft ? WindowLeft : x1; vis->x2 = x2 > WindowRight ? 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; } if (vis->x1 > x1) vis->startfrac += vis->xiscale * (vis->x1 - x1); } else { vis = R_NewVisSprite(); vis->CurrentPortalUniq = CurrentPortalUniq; vis->xscale = FLOAT2FIXED(xscale); vis->yscale = (float)yscale; vis->x1 = WindowLeft; vis->x2 = 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->ColormapNum = 0; vis->bInMirror = 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; // 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.colormap = fixedcolormap; } else { if (invertcolormap) { mybasecolormap = GetSpecialLights(mybasecolormap->Color, mybasecolormap->Fade.InverseColor(), mybasecolormap->Desaturate); } if (fixedlightlev >= 0) { vis->Style.colormap = mybasecolormap->Maps + fixedlightlev; } else if (!foggy && ((renderflags & RF_FULLBRIGHT) || (thing->flags5 & MF5_BRIGHT))) { // full bright vis->Style.colormap = (r_fullbrightignoresectorcolor) ? &FullNormalLight->Maps : mybasecolormap->Maps; } else { // diminished light vis->ColormapNum = GETPALOOKUP( r_SpriteVisibility / MAX(tz, MINZ), spriteshade); vis->Style.colormap = mybasecolormap->Maps + (vis->ColormapNum << COLORMAPSHIFT); } } } static void R_ProjectWallSprite(AActor *thing, const DVector3 &pos, FTextureID picnum, const DVector2 &scale, int renderflags) { FWallCoords wallc; double x1, x2; DVector2 left, right; double gzb, gzt, tz; FTexture *pic = TexMan(picnum, true); DAngle ang = thing->Angles.Yaw + 90; double angcos = ang.Cos(); double angsin = ang.Sin(); vissprite_t *vis; // Determine left and right edges of sprite. The sprite's angle is its normal, // so the edges are 90 degrees each side of it. x2 = pic->GetScaledWidth(); x1 = pic->GetScaledLeftOffset(); x1 *= scale.X; x2 *= scale.X; left.X = pos.X - x1 * angcos - ViewPos.X; left.Y = pos.Y - x1 * angsin - ViewPos.Y; right.X = left.X + x2 * angcos; right.Y = right.Y + x2 * angsin; // Is it off-screen? if (wallc.Init(left, right, TOO_CLOSE_Z)) return; if (wallc.sx1 >= WindowRight || wallc.sx2 <= WindowLeft) return; // Sprite sorting should probably treat these as walls, not sprites, // but right now, I just want to get them drawing. tz = (pos.X - ViewPos.X) * ViewTanCos + (pos.Y - ViewPos.Y) * ViewTanSin; int scaled_to = pic->GetScaledTopOffset(); int scaled_bo = scaled_to - pic->GetScaledHeight(); gzt = pos.Z + scale.Y * scaled_to; gzb = pos.Z + scale.Y * scaled_bo; vis = R_NewVisSprite(); vis->CurrentPortalUniq = CurrentPortalUniq; vis->x1 = wallc.sx1 < WindowLeft ? WindowLeft : wallc.sx1; vis->x2 = wallc.sx2 >= WindowRight ? WindowRight : wallc.sx2; vis->yscale = (float)scale.Y; vis->idepth = float(1 / tz); vis->depth = (float)tz; vis->sector = thing->Sector; vis->heightsec = NULL; vis->gpos = { (float)pos.X, (float)pos.Y, (float)pos.Z }; vis->gzb = (float)gzb; vis->gzt = (float)gzt; 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; vis->FakeFlatStat = 0; vis->Style.Alpha = float(thing->Alpha); vis->fakefloor = NULL; vis->fakeceiling = NULL; vis->ColormapNum = 0; vis->bInMirror = MirrorFlags & RF_XFLIP; vis->pic = pic; vis->bIsVoxel = false; vis->bWallSprite = true; vis->ColormapNum = GETPALOOKUP( r_SpriteVisibility / MAX(tz, MINZ), spriteshade); vis->Style.colormap = basecolormap->Maps + (vis->ColormapNum << COLORMAPSHIFT); vis->wallc = wallc; } // // 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, int fakeside) { AActor *thing; 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->thinglist == NULL || sec->validcount == validcount) return; // Well, now it will be done. sec->validcount = validcount; spriteshade = LIGHT2SHADE(lightlevel + r_actualextralight); // Handle all things in sector. for (thing = sec->thinglist; thing; thing = thing->snext) { 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 : frontsector->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); fakeceiling = NULL; fakefloor = NULL; } } // // R_DrawPSprite // void R_DrawPSprite(DPSprite *pspr, AActor *owner, float bobx, float boby, double wx, double wy, double ticfrac) { double tx; int x1; int x2; double sx, sy; spritedef_t* sprdef; spriteframe_t* sprframe; FTextureID picnum; WORD flip; FTexture* tex; vissprite_t* vis; bool noaccel; static TArray avis; if (avis.Size() < vispspindex + 1) avis.Reserve(avis.Size() - vispspindex + 1); // decide which patch to use if ((unsigned)pspr->GetSprite() >= (unsigned)sprites.Size()) { DPrintf(DMSG_ERROR, "R_DrawPSprite: invalid sprite number %i\n", pspr->GetSprite()); return; } sprdef = &sprites[pspr->GetSprite()]; if (pspr->GetFrame() >= sprdef->numframes) { DPrintf(DMSG_ERROR, "R_DrawPSprite: invalid sprite frame %i : %i\n", pspr->GetSprite(), pspr->GetFrame()); return; } sprframe = &SpriteFrames[sprdef->spriteframes + pspr->GetFrame()]; picnum = sprframe->Texture[0]; flip = sprframe->Flip & 1; tex = TexMan(picnum); if (tex->UseType == FTexture::TEX_Null) return; if (pspr->firstTic) { // Can't interpolate the first tic. pspr->firstTic = false; pspr->oldx = pspr->x; pspr->oldy = pspr->y; } sx = pspr->oldx + (pspr->x - pspr->oldx) * ticfrac; sy = pspr->oldy + (pspr->y - pspr->oldy) * ticfrac; if (pspr->Flags & PSPF_ADDBOB) { sx += bobx; sy += boby; } if (pspr->Flags & PSPF_ADDWEAPON && pspr->GetID() != PSP_WEAPON) { sx += wx; sy += wy; } // calculate edges of the shape tx = sx - BASEXCENTER; tx -= tex->GetScaledLeftOffset(); x1 = xs_RoundToInt(CenterX + tx * pspritexscale); // off the right side if (x1 > viewwidth) return; tx += tex->GetScaledWidth(); x2 = xs_RoundToInt(CenterX + tx * pspritexscale); // off the left side if (x2 <= 0) return; // store information in a vissprite vis = &avis[vispspindex]; vis->renderflags = owner->renderflags; vis->floorclip = 0; vis->texturemid = (BASEYCENTER - sy) * tex->Scale.Y + tex->TopOffset; if (camera->player && (RenderTarget != screen || viewheight == RenderTarget->GetHeight() || (RenderTarget->GetWidth() > (BASEXCENTER * 2) && !st_scale))) { // Adjust PSprite for fullscreen views AWeapon *weapon = dyn_cast(pspr->GetCaller()); if (weapon != nullptr && weapon->YAdjust != 0) { if (RenderTarget != screen || viewheight == RenderTarget->GetHeight()) { vis->texturemid -= weapon->YAdjust; } else { vis->texturemid -= StatusBar->GetDisplacement() * weapon->YAdjust; } } } if (pspr->GetID() < PSP_TARGETCENTER) { // Move the weapon down for 1280x1024. vis->texturemid -= AspectPspriteOffset(WidescreenRatio); } vis->x1 = x1 < 0 ? 0 : x1; vis->x2 = x2 >= viewwidth ? viewwidth : x2; vis->xscale = FLOAT2FIXED(pspritexscale / tex->Scale.X); vis->yscale = float(pspriteyscale / tex->Scale.Y); vis->Translation = 0; // [RH] Use default colors vis->pic = tex; vis->ColormapNum = 0; if (!(flip) != !(pspr->Flags & PSPF_FLIP)) { vis->xiscale = -FLOAT2FIXED(pspritexiscale * tex->Scale.X); vis->startfrac = (tex->GetWidth() << FRACBITS) - 1; } else { vis->xiscale = FLOAT2FIXED(pspritexiscale * tex->Scale.X); vis->startfrac = 0; } if (vis->x1 > x1) vis->startfrac += vis->xiscale*(vis->x1 - x1); noaccel = false; FDynamicColormap *colormap_to_use = nullptr; if (pspr->GetID() < PSP_TARGETCENTER) { vis->Style.Alpha = float(owner->Alpha); vis->Style.RenderStyle = owner->RenderStyle; // 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 (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); } } if (realfixedcolormap != nullptr) { // fixed color vis->Style.colormap = realfixedcolormap->Colormap; } else { if (invertcolormap) { mybasecolormap = GetSpecialLights(mybasecolormap->Color, mybasecolormap->Fade.InverseColor(), mybasecolormap->Desaturate); } if (fixedlightlev >= 0) { vis->Style.colormap = (r_fullbrightignoresectorcolor) ? (&FullNormalLight->Maps + fixedlightlev) : (mybasecolormap->Maps + fixedlightlev); } else if (!foggy && pspr->GetState()->GetFullbright()) { // full bright vis->Style.colormap = (r_fullbrightignoresectorcolor) ? &FullNormalLight->Maps : mybasecolormap->Maps; // [RH] use basecolormap } else { // local light vis->Style.colormap = mybasecolormap->Maps + (GETPALOOKUP(0, spriteshade) << COLORMAPSHIFT); } } if (camera->Inventory != nullptr) { lighttable_t *oldcolormap = vis->Style.colormap; camera->Inventory->AlterWeaponSprite(&vis->Style); if (vis->Style.colormap != oldcolormap) { // The colormap has changed. Is it one we can easily identify? // If not, then don't bother trying to identify it for // hardware accelerated drawing. if (vis->Style.colormap < SpecialColormaps[0].Colormap || vis->Style.colormap > SpecialColormaps.Last().Colormap) { noaccel = true; } // Has the basecolormap changed? If so, we can't hardware accelerate it, // since we don't know what it is anymore. else if (vis->Style.colormap < mybasecolormap->Maps || vis->Style.colormap >= mybasecolormap->Maps + NUMCOLORMAPS * 256) { noaccel = true; } } } // If we're drawing with a special colormap, but shaders for them are disabled, do // not accelerate. if (!r_shadercolormaps && (vis->Style.colormap >= SpecialColormaps[0].Colormap && vis->Style.colormap <= SpecialColormaps.Last().Colormap)) { noaccel = true; } // If drawing with a BOOM colormap, disable acceleration. if (mybasecolormap == &NormalLight && NormalLight.Maps != realcolormaps) { noaccel = true; } // If the main colormap has fixed lights, and this sprite is being drawn with that // colormap, disable acceleration so that the lights can remain fixed. if (!noaccel && realfixedcolormap == nullptr && NormalLightHasFixedLights && mybasecolormap == &NormalLight && vis->pic->UseBasePalette()) { noaccel = true; } // [SP] If emulating GZDoom fullbright, disable acceleration if (r_fullbrightignoresectorcolor && fixedlightlev >= 0) mybasecolormap = &FullNormalLight; if (r_fullbrightignoresectorcolor && !foggy && pspr->GetState()->GetFullbright()) mybasecolormap = &FullNormalLight; colormap_to_use = mybasecolormap; } else { colormap_to_use = basecolormap; vis->Style.colormap = basecolormap->Maps; vis->Style.RenderStyle = STYLE_Normal; } // Check for hardware-assisted 2D. If it's available, and this sprite is not // fuzzy, don't draw it until after the switch to 2D mode. if (!noaccel && RenderTarget == screen && (DFrameBuffer *)screen->Accel2D) { FRenderStyle style = vis->Style.RenderStyle; style.CheckFuzz(); if (style.BlendOp != STYLEOP_Fuzz) { if (vispsprites.Size() < vispspindex + 1) vispsprites.Reserve(vispsprites.Size() - vispspindex + 1); vispsprites[vispspindex].vis = vis; vispsprites[vispspindex].basecolormap = colormap_to_use; vispsprites[vispspindex].x1 = x1; vispspindex++; return; } } R_DrawVisSprite(vis); } //========================================================================== // // R_DrawPlayerSprites // //========================================================================== void R_DrawPlayerSprites () { int i; int lightnum; DPSprite* psp; DPSprite* weapon; sector_t* sec = NULL; static sector_t tempsec; int floorlight, ceilinglight; F3DFloor *rover; if (!r_drawplayersprites || !camera || !camera->player || (players[consoleplayer].cheats & CF_CHASECAM) || (r_deathcamera && camera->health <= 0)) return; if (fixedlightlev < 0 && viewsector->e && viewsector->e->XFloor.lightlist.Size()) { for (i = viewsector->e->XFloor.lightlist.Size() - 1; i >= 0; i--) { if (ViewPos.Z <= viewsector->e->XFloor.lightlist[i].plane.Zat0()) { rover = viewsector->e->XFloor.lightlist[i].caster; if (rover) { if (rover->flags & FF_DOUBLESHADOW && ViewPos.Z <= rover->bottom.plane->Zat0()) break; sec = rover->model; if (rover->flags & FF_FADEWALLS) basecolormap = sec->ColorMap; else basecolormap = viewsector->e->XFloor.lightlist[i].extra_colormap; } break; } } if(!sec) { sec = viewsector; basecolormap = sec->ColorMap; } floorlight = ceilinglight = sec->lightlevel; } else { // This used to use camera->Sector but due to interpolation that can be incorrect // when the interpolated viewpoint is in a different sector than the camera. sec = R_FakeFlat (viewsector, &tempsec, &floorlight, &ceilinglight, false); // [RH] set basecolormap basecolormap = sec->ColorMap; } // [RH] set foggy flag foggy = (level.fadeto || basecolormap->Fade || (level.flags & LEVEL_HASFADETABLE)); r_actualextralight = foggy ? 0 : extralight << 4; // get light level lightnum = ((floorlight + ceilinglight) >> 1) + r_actualextralight; spriteshade = LIGHT2SHADE(lightnum) - 24*FRACUNIT; // clip to screen bounds mfloorclip = screenheightarray; mceilingclip = zeroarray; if (camera->player != NULL) { double centerhack = CenterY; double wx, wy; float bobx, boby; CenterY = viewheight / 2; P_BobWeapon (camera->player, &bobx, &boby, r_TicFracF); // Interpolate the main weapon layer once so as to be able to add it to other layers. if ((weapon = camera->player->FindPSprite(PSP_WEAPON)) != nullptr) { if (weapon->firstTic) { wx = weapon->x; wy = weapon->y; } else { wx = weapon->oldx + (weapon->x - weapon->oldx) * r_TicFracF; wy = weapon->oldy + (weapon->y - weapon->oldy) * r_TicFracF; } } else { wx = 0; wy = 0; } // add all active psprites psp = camera->player->psprites; while (psp) { // [RH] Don't draw the targeter's crosshair if the player already has a crosshair set. // It's possible this psprite's caller is now null but the layer itself hasn't been destroyed // because it didn't tick yet (if we typed 'take all' while in the console for example). // In this case let's simply not draw it to avoid crashing. if ((psp->GetID() != PSP_TARGETCENTER || CrosshairImage == nullptr) && psp->GetCaller() != nullptr) { R_DrawPSprite(psp, camera, bobx, boby, wx, wy, r_TicFracF); } psp = psp->GetNext(); } CenterY = centerhack; } } //========================================================================== // // R_DrawRemainingPlayerSprites // // Called from D_Display to draw sprites that were not drawn by // R_DrawPlayerSprites(). // //========================================================================== void R_DrawRemainingPlayerSprites() { for (unsigned int i = 0; i < vispspindex; i++) { vissprite_t *vis; vis = vispsprites[i].vis; FDynamicColormap *colormap = vispsprites[i].basecolormap; bool flip = vis->xiscale < 0; FSpecialColormap *special = NULL; PalEntry overlay = 0; FColormapStyle colormapstyle; bool usecolormapstyle = false; if (vis->Style.colormap >= SpecialColormaps[0].Colormap && vis->Style.colormap < SpecialColormaps[SpecialColormaps.Size()].Colormap) { // Yuck! There needs to be a better way to store colormaps in the vissprite... :( ptrdiff_t specialmap = (vis->Style.colormap - SpecialColormaps[0].Colormap) / sizeof(FSpecialColormap); special = &SpecialColormaps[specialmap]; } else if (colormap->Color == PalEntry(255,255,255) && colormap->Desaturate == 0) { overlay = colormap->Fade; overlay.a = BYTE(((vis->Style.colormap - colormap->Maps) >> 8) * 255 / NUMCOLORMAPS); } else { usecolormapstyle = true; colormapstyle.Color = colormap->Color; colormapstyle.Fade = colormap->Fade; colormapstyle.Desaturate = colormap->Desaturate; colormapstyle.FadeLevel = ((vis->Style.colormap - colormap->Maps) >> 8) / float(NUMCOLORMAPS); } screen->DrawTexture(vis->pic, viewwindowx + vispsprites[i].x1, viewwindowy + viewheight/2 - vis->texturemid * vis->yscale - 0.5, DTA_DestWidthF, FIXED2DBL(vis->pic->GetWidth() * vis->xscale), DTA_DestHeightF, vis->pic->GetHeight() * vis->yscale, DTA_Translation, TranslationToTable(vis->Translation), DTA_FlipX, flip, DTA_TopOffset, 0, DTA_LeftOffset, 0, DTA_ClipLeft, viewwindowx, DTA_ClipTop, viewwindowy, DTA_ClipRight, viewwindowx + viewwidth, DTA_ClipBottom, viewwindowy + viewheight, DTA_AlphaF, vis->Style.Alpha, DTA_RenderStyle, vis->Style.RenderStyle, DTA_FillColor, vis->FillColor, DTA_SpecialColormap, special, DTA_ColorOverlay, overlay.d, DTA_ColormapStyle, usecolormapstyle ? &colormapstyle : NULL, TAG_DONE); } vispspindex = 0; } // // 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; } CVAR (Bool, r_splitsprites, true, CVAR_ARCHIVE) // 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; lighttable_t *colormap = spr->Style.colormap; F3DFloor *rover; FDynamicColormap *mybasecolormap; // [RH] Check for particles if (!spr->bIsVoxel && spr->pic == NULL) { // kg3D - reject invisible parts if ((fake3D & FAKE3D_CLIPBOTTOM) && spr->gpos.Z <= sclipBottom) return; if ((fake3D & FAKE3D_CLIPTOP) && spr->gpos.Z >= 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 ((fake3D & FAKE3D_CLIPBOTTOM) && spr->gzt <= sclipBottom) return; if ((fake3D & FAKE3D_CLIPTOP) && spr->gzb >= sclipTop) return; // kg3D - correct colors now if (!fixedcolormap && fixedlightlev < 0 && spr->sector->e && spr->sector->e->XFloor.lightlist.Size()) { if (!(fake3D & FAKE3D_CLIPTOP)) { sclipTop = spr->sector->ceilingplane.ZatPoint(ViewPos); } sector_t *sec = NULL; for (i = spr->sector->e->XFloor.lightlist.Size() - 1; i >= 0; i--) { if (sclipTop <= spr->sector->e->XFloor.lightlist[i].plane.Zat0()) { rover = spr->sector->e->XFloor.lightlist[i].caster; if (rover) { if (rover->flags & FF_DOUBLESHADOW && 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.colormap = mybasecolormap->Maps + fixedlightlev; } else if (!foggy && (spr->renderflags & RF_FULLBRIGHT)) { // full bright spr->Style.colormap = (r_fullbrightignoresectorcolor) ? &FullNormalLight->Maps : mybasecolormap->Maps; } else { // diminished light spriteshade = LIGHT2SHADE(sec->lightlevel + r_actualextralight); spr->Style.colormap = mybasecolormap->Maps + (GETPALOOKUP ( r_SpriteVisibility / MAX(MINZ, (double)spr->depth), spriteshade) << COLORMAPSHIFT); } } } // [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 != FAKED_AboveCeiling) { double hz = spr->heightsec->floorplane.ZatPoint(spr->gpos); int h = xs_RoundToInt(CenterY - (hz - ViewPos.Z) * scale); if (spr->FakeFlatStat == FAKED_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 != FAKED_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 == FAKED_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 (fake3D & FAKE3D_CLIPBOTTOM) { if (!spr->bIsVoxel) { double hz = sclipBottom; if (spr->fakefloor) { double floorz = spr->fakefloor->top.plane->Zat0(); if (ViewPos.Z > floorz && floorz == 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, sclipBottom); } if (fake3D & FAKE3D_CLIPTOP) { if (!spr->bIsVoxel) { double hz = sclipTop; if (spr->fakeceiling != NULL) { double ceilingZ = spr->fakeceiling->bottom.plane->Zat0(); if (ViewPos.Z < ceilingZ && ceilingZ == 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, sclipTop); } #if 0 // [RH] Sprites that were split by a drawseg should also be clipped // by the sector's floor and ceiling. (Not sure how/if to handle this // with fake floors, since those already do clipping.) if (spr->bSplitSprite && (spr->heightsec == NULL || (spr->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC))) { fixed_t h = spr->sector->floorplane.ZatPoint (spr->gx, spr->gy); h = (centeryfrac - FixedMul (h-viewz, scale)) >> FRACBITS; if (h < botclip) { botclip = MAX (0, h); } h = spr->sector->ceilingplane.ZatPoint (spr->gx, spr->gy); h = (centeryfrac - FixedMul (h-viewz, scale)) >> FRACBITS; if (h > topclip) { topclip = short(MIN(h, viewheight)); } } #endif if (topclip >= botclip) { spr->Style.colormap = colormap; 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)) { // seg is behind sprite, so draw the mid texture if it has one if (ds->CurrentPortalUniq == 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) { mfloorclip = clipbot; mceilingclip = cliptop; if (!spr->bWallSprite) { R_DrawVisSprite(spr); } else { R_DrawWallSprite(spr); } } 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.colormap = colormap; return; } } // Add everything outside the left and right edges to the clipping array // for R_DrawVisVoxel(). if (x1 > 0) { clearbufshort(cliptop, x1, viewheight); } if (x2 < viewwidth - 1) { clearbufshort(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.colormap = colormap; } // kg3D: // R_DrawMasked contains sorting // original renamed to R_DrawMaskedSingle void R_DrawMaskedSingle (bool renew) { drawseg_t *ds; int i; #if 0 R_SplitVisSprites (); #endif for (i = vsprcount; i > 0; i--) { if (spritesorter[i-1]->CurrentPortalUniq != 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) { fake3D |= FAKE3D_REFRESHCLIP; } for (ds = ds_p; ds-- > firstdrawseg; ) // new -- killough { // [ZZ] the same as above if (ds->CurrentPortalUniq != 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 (void) { R_CollectPortals(); R_SortVisSprites (DrewAVoxel ? sv_compare2d : sv_compare, firstvissprite - vissprites); if (height_top == NULL) { // kg3D - no visible 3D floors, normal rendering R_DrawMaskedSingle(false); } else { // kg3D - correct sorting HeightLevel *hl; // ceilings for (hl = height_cur; hl != NULL && hl->height >= ViewPos.Z; hl = hl->prev) { if (hl->next) { fake3D = FAKE3D_CLIPBOTTOM | FAKE3D_CLIPTOP; sclipTop = hl->next->height; } else { fake3D = FAKE3D_CLIPBOTTOM; } sclipBottom = hl->height; R_DrawMaskedSingle(true); R_DrawHeightPlanes(hl->height); } // floors fake3D = FAKE3D_DOWN2UP | FAKE3D_CLIPTOP; sclipTop = height_top->height; R_DrawMaskedSingle(true); hl = height_top; for (hl = height_top; hl != NULL && hl->height < ViewPos.Z; hl = hl->next) { R_DrawHeightPlanes(hl->height); if (hl->next) { fake3D = FAKE3D_DOWN2UP | FAKE3D_CLIPTOP | FAKE3D_CLIPBOTTOM; sclipTop = hl->next->height; } else { fake3D = FAKE3D_DOWN2UP | FAKE3D_CLIPBOTTOM; } sclipBottom = hl->height; R_DrawMaskedSingle(true); } R_3D_DeleteHeights(); fake3D = 0; } R_DrawPlayerSprites (); } void R_ProjectParticle (particle_t *particle, const sector_t *sector, int shade, int fakeside) { double tr_x, tr_y; double tx, ty; double tz, tiz; double xscale, yscale; int x1, x2, y1, y2; vissprite_t* vis; sector_t* heightsec = NULL; BYTE* map; // [ZZ] Particle not visible through the portal plane if (CurrentPortal && !!P_PointOnLineSide(particle->Pos, CurrentPortal->dst)) return; // transform the origin point tr_x = particle->Pos.X - ViewPos.X; tr_y = particle->Pos.Y - ViewPos.Y; tz = tr_x * ViewTanCos + tr_y * ViewTanSin; // particle is behind view plane? if (tz < MINZ) return; tx = tr_x * ViewSin - tr_y * ViewCos; // Flip for mirrors if (MirrorFlags & RF_XFLIP) { tx = viewwidth - tx - 1; } // too far off the side? if (tz <= fabs(tx)) return; tiz = 1 / tz; xscale = centerx * tiz; // calculate edges of the shape double psize = particle->size / 8.0; x1 = MAX(WindowLeft, centerx + xs_RoundToInt((tx - psize) * xscale)); x2 = MIN(WindowRight, centerx + xs_RoundToInt((tx + psize) * xscale)); if (x1 >= x2) return; yscale = YaspectMul * xscale; ty = particle->Pos.Z - ViewPos.Z; y1 = xs_RoundToInt(CenterY - (ty + psize) * yscale); y2 = xs_RoundToInt(CenterY - (ty - psize) * yscale); // Clip the particle now. Because it's a point and projected as its subsector is // entered, we don't need to clip it to drawsegs like a normal sprite. // Clip particles behind walls. if (y1 < ceilingclip[x1]) y1 = ceilingclip[x1]; if (y1 < ceilingclip[x2-1]) y1 = ceilingclip[x2-1]; if (y2 >= floorclip[x1]) y2 = floorclip[x1] - 1; if (y2 >= floorclip[x2-1]) y2 = floorclip[x2-1] - 1; if (y1 > y2) return; // Clip particles above the ceiling or below the floor. heightsec = sector->GetHeightSec(); const secplane_t *topplane; const secplane_t *botplane; FTextureID toppic; FTextureID botpic; if (heightsec) // only clip things which are in special sectors { if (fakeside == FAKED_AboveCeiling) { topplane = §or->ceilingplane; botplane = &heightsec->ceilingplane; toppic = sector->GetTexture(sector_t::ceiling); botpic = heightsec->GetTexture(sector_t::ceiling); map = heightsec->ColorMap->Maps; } else if (fakeside == FAKED_BelowFloor) { topplane = &heightsec->floorplane; botplane = §or->floorplane; toppic = heightsec->GetTexture(sector_t::floor); botpic = sector->GetTexture(sector_t::floor); map = heightsec->ColorMap->Maps; } else { topplane = &heightsec->ceilingplane; botplane = &heightsec->floorplane; toppic = heightsec->GetTexture(sector_t::ceiling); botpic = heightsec->GetTexture(sector_t::floor); map = sector->ColorMap->Maps; } } else { topplane = §or->ceilingplane; botplane = §or->floorplane; toppic = sector->GetTexture(sector_t::ceiling); botpic = sector->GetTexture(sector_t::floor); map = sector->ColorMap->Maps; } if (botpic != skyflatnum && particle->Pos.Z < botplane->ZatPoint (particle->Pos)) return; if (toppic != skyflatnum && particle->Pos.Z >= topplane->ZatPoint (particle->Pos)) return; // store information in a vissprite vis = R_NewVisSprite (); vis->CurrentPortalUniq = CurrentPortalUniq; vis->heightsec = heightsec; vis->xscale = FLOAT2FIXED(xscale); vis->yscale = (float)xscale; // vis->yscale *= InvZtoScale; vis->depth = (float)tz; vis->idepth = float(1 / tz); vis->gpos = { (float)particle->Pos.X, (float)particle->Pos.Y, (float)particle->Pos.Z }; vis->y1 = y1; vis->y2 = y2; vis->x1 = x1; vis->x2 = x2; vis->Translation = 0; vis->startfrac = 255 & (particle->color >>24); vis->pic = NULL; vis->bIsVoxel = false; vis->renderflags = particle->trans; vis->FakeFlatStat = fakeside; vis->floorclip = 0; vis->ColormapNum = 0; if (fixedlightlev >= 0) { vis->Style.colormap = map + fixedlightlev; } else if (fixedcolormap) { vis->Style.colormap = fixedcolormap; } else if (particle->bright) { vis->Style.colormap = (r_fullbrightignoresectorcolor) ? &FullNormalLight->Maps : map; } else { // Particles are slightly more visible than regular sprites. vis->ColormapNum = GETPALOOKUP(tiz * r_SpriteVisibility * 0.5, shade); vis->Style.colormap = map + (vis->ColormapNum << COLORMAPSHIFT); } } static void R_DrawMaskedSegsBehindParticle (const vissprite_t *vis) { const int x1 = vis->x1; const int x2 = vis->x2; // Draw any masked textures behind this particle so that when the // particle is drawn, it will be in front of them. for (unsigned int p = InterestingDrawsegs.Size(); p-- > FirstInterestingDrawseg; ) { drawseg_t *ds = &drawsegs[InterestingDrawsegs[p]]; // kg3D - no fake segs if(ds->fake) continue; if (ds->x1 >= x2 || ds->x2 <= x1) { continue; } if ((ds->siz2 - ds->siz1) * ((x2 + x1)/2 - ds->sx1) / (ds->sx2 - ds->sx1) + ds->siz1 < vis->idepth) { // [ZZ] only draw stuff that's inside the same portal as the particle, other portals will care for themselves if (ds->CurrentPortalUniq == vis->CurrentPortalUniq) R_RenderMaskedSegRange (ds, MAX(ds->x1, x1), MIN(ds->x2, x2)); } } } void R_DrawParticle (vissprite_t *vis) { DWORD *bg2rgb; int spacing; BYTE *dest; DWORD fg; BYTE color = vis->Style.colormap[vis->startfrac]; int yl = vis->y1; int ycount = vis->y2 - yl + 1; int x1 = vis->x1; int countbase = vis->x2 - x1; R_DrawMaskedSegsBehindParticle (vis); // vis->renderflags holds translucency level (0-255) { fixed_t fglevel, bglevel; DWORD *fg2rgb; fglevel = ((vis->renderflags + 1) << 8) & ~0x3ff; bglevel = FRACUNIT-fglevel; fg2rgb = Col2RGB8[fglevel>>10]; bg2rgb = Col2RGB8[bglevel>>10]; fg = fg2rgb[color]; } /* spacing = RenderTarget->GetPitch() - countbase; dest = ylookup[yl] + x1 + dc_destorg; do { int count = countbase; do { DWORD bg = bg2rgb[*dest]; bg = (fg+bg) | 0x1f07c1f; *dest++ = RGB32k.All[bg & (bg>>15)]; } while (--count); dest += spacing; } while (--ycount);*/ // original was row-wise // width = countbase // height = ycount spacing = RenderTarget->GetPitch(); for (int x = x1; x < (x1+countbase); x++) { dc_x = x; if (R_ClipSpriteColumnWithPortals(vis)) continue; dest = ylookup[yl] + x + dc_destorg; for (int y = 0; y < ycount; y++) { DWORD bg = bg2rgb[*dest]; bg = (fg+bg) | 0x1f07c1f; *dest = RGB32k.All[bg & (bg>>15)]; dest += spacing; } } } extern double BaseYaspectMul;; void R_DrawVoxel(const FVector3 &globalpos, FAngle viewangle, const FVector3 &dasprpos, DAngle dasprang, fixed_t daxscale, fixed_t dayscale, FVoxel *voxobj, lighttable_t *colormap, short *daumost, short *dadmost, int minslabz, int maxslabz, int flags) { int i, j, k, x, y, syoff, ggxstart, ggystart, nxoff; fixed_t cosang, sinang, sprcosang, sprsinang; int backx, backy, gxinc, gyinc; int daxscalerecip, dayscalerecip, cnt, gxstart, gystart, dazscale; int lx, rx, nx, ny, x1=0, y1=0, x2=0, y2=0, yinc=0; int yoff, xs=0, ys=0, xe, ye, xi=0, yi=0, cbackx, cbacky, dagxinc, dagyinc; kvxslab_t *voxptr, *voxend; FVoxelMipLevel *mip; int z1a[64], z2a[64], yplc[64]; const int nytooclose = centerxwide * 2100, nytoofar = 32768*32768 - 1048576; const int xdimenscale = FLOAT2FIXED(centerxwide * YaspectMul / 160); const double centerxwide_f = centerxwide; const double centerxwidebig_f = centerxwide_f * 65536*65536*8; // Convert to Build's coordinate system. fixed_t globalposx = xs_Fix<4>::ToFix(globalpos.X); fixed_t globalposy = xs_Fix<4>::ToFix(-globalpos.Y); fixed_t globalposz = xs_Fix<8>::ToFix(-globalpos.Z); fixed_t dasprx = xs_Fix<4>::ToFix(dasprpos.X); fixed_t daspry = xs_Fix<4>::ToFix(-dasprpos.Y); fixed_t dasprz = xs_Fix<8>::ToFix(-dasprpos.Z); // Shift the scales from 16 bits of fractional precision to 6. // Also do some magic voodoo scaling to make them the right size. daxscale = daxscale / (0xC000 >> 6); dayscale = dayscale / (0xC000 >> 6); if (daxscale <= 0 || dayscale <= 0) { // won't be visible. return; } angle_t viewang = viewangle.BAMs(); cosang = FLOAT2FIXED(viewangle.Cos()) >> 2; sinang = FLOAT2FIXED(-viewangle.Sin()) >> 2; sprcosang = FLOAT2FIXED(dasprang.Cos()) >> 2; sprsinang = FLOAT2FIXED(-dasprang.Sin()) >> 2; R_SetupDrawSlab(colormap); // Select mip level i = abs(DMulScale6(dasprx - globalposx, cosang, daspry - globalposy, sinang)); i = DivScale6(i, MIN(daxscale, dayscale)); j = xs_Fix<13>::ToFix(FocalLengthX); for (k = 0; i >= j && k < voxobj->NumMips; ++k) { i >>= 1; } if (k >= voxobj->NumMips) k = voxobj->NumMips - 1; mip = &voxobj->Mips[k]; if (mip->SlabData == NULL) return; minslabz >>= k; maxslabz >>= k; daxscale <<= (k+8); dayscale <<= (k+8); dazscale = FixedDiv(dayscale, FLOAT2FIXED(BaseYaspectMul)); daxscale = fixed_t(daxscale / YaspectMul); daxscale = Scale(daxscale, xdimenscale, centerxwide << 9); dayscale = Scale(dayscale, FixedMul(xdimenscale, viewingrangerecip), centerxwide << 9); daxscalerecip = (1<<30) / daxscale; dayscalerecip = (1<<30) / dayscale; fixed_t piv_x = fixed_t(mip->Pivot.X*256.); fixed_t piv_y = fixed_t(mip->Pivot.Y*256.); fixed_t piv_z = fixed_t(mip->Pivot.Z*256.); x = FixedMul(globalposx - dasprx, daxscalerecip); y = FixedMul(globalposy - daspry, daxscalerecip); backx = (DMulScale10(x, sprcosang, y, sprsinang) + piv_x) >> 8; backy = (DMulScale10(y, sprcosang, x, -sprsinang) + piv_y) >> 8; cbackx = clamp(backx, 0, mip->SizeX - 1); cbacky = clamp(backy, 0, mip->SizeY - 1); sprcosang = MulScale14(daxscale, sprcosang); sprsinang = MulScale14(daxscale, sprsinang); x = (dasprx - globalposx) - DMulScale18(piv_x, sprcosang, piv_y, -sprsinang); y = (daspry - globalposy) - DMulScale18(piv_y, sprcosang, piv_x, sprsinang); cosang = FixedMul(cosang, dayscalerecip); sinang = FixedMul(sinang, dayscalerecip); gxstart = y*cosang - x*sinang; gystart = x*cosang + y*sinang; gxinc = DMulScale10(sprsinang, cosang, sprcosang, -sinang); gyinc = DMulScale10(sprcosang, cosang, sprsinang, sinang); if ((abs(globalposz - dasprz) >> 10) >= abs(dazscale)) return; x = 0; y = 0; j = MAX(mip->SizeX, mip->SizeY); fixed_t *ggxinc = (fixed_t *)alloca((j + 1) * sizeof(fixed_t) * 2); fixed_t *ggyinc = ggxinc + (j + 1); for (i = 0; i <= j; i++) { ggxinc[i] = x; x += gxinc; ggyinc[i] = y; y += gyinc; } syoff = DivScale21(globalposz - dasprz, FixedMul(dazscale, 0xE800)) + (piv_z << 7); yoff = (abs(gxinc) + abs(gyinc)) >> 1; for (cnt = 0; cnt < 8; cnt++) { switch (cnt) { case 0: xs = 0; ys = 0; xi = 1; yi = 1; break; case 1: xs = mip->SizeX-1; ys = 0; xi = -1; yi = 1; break; case 2: xs = 0; ys = mip->SizeY-1; xi = 1; yi = -1; break; case 3: xs = mip->SizeX-1; ys = mip->SizeY-1; xi = -1; yi = -1; break; case 4: xs = 0; ys = cbacky; xi = 1; yi = 2; break; case 5: xs = mip->SizeX-1; ys = cbacky; xi = -1; yi = 2; break; case 6: xs = cbackx; ys = 0; xi = 2; yi = 1; break; case 7: xs = cbackx; ys = mip->SizeY-1; xi = 2; yi = -1; break; } xe = cbackx; ye = cbacky; if (cnt < 4) { if ((xi < 0) && (xe >= xs)) continue; if ((xi > 0) && (xe <= xs)) continue; if ((yi < 0) && (ye >= ys)) continue; if ((yi > 0) && (ye <= ys)) continue; } else { if ((xi < 0) && (xe > xs)) continue; if ((xi > 0) && (xe < xs)) continue; if ((yi < 0) && (ye > ys)) continue; if ((yi > 0) && (ye < ys)) continue; xe += xi; ye += yi; } i = ksgn(ys-backy)+ksgn(xs-backx)*3+4; switch(i) { case 6: case 7: x1 = 0; y1 = 0; break; case 8: case 5: x1 = gxinc; y1 = gyinc; break; case 0: case 3: x1 = gyinc; y1 = -gxinc; break; case 2: case 1: x1 = gxinc+gyinc; y1 = gyinc-gxinc; break; } switch(i) { case 2: case 5: x2 = 0; y2 = 0; break; case 0: case 1: x2 = gxinc; y2 = gyinc; break; case 8: case 7: x2 = gyinc; y2 = -gxinc; break; case 6: case 3: x2 = gxinc+gyinc; y2 = gyinc-gxinc; break; } BYTE oand = (1 << int(xs 0) { dagxinc = gxinc; dagyinc = FixedMul(gyinc, viewingrangerecip); } else { dagxinc = -gxinc; dagyinc = -FixedMul(gyinc, viewingrangerecip); } /* Fix for non 90 degree viewing ranges */ nxoff = FixedMul(x2 - x1, viewingrangerecip); x1 = FixedMul(x1, viewingrangerecip); ggxstart = gxstart + ggyinc[ys]; ggystart = gystart - ggxinc[ys]; for (x = xs; x != xe; x += xi) { BYTE *slabxoffs = &mip->SlabData[mip->OffsetX[x]]; short *xyoffs = &mip->OffsetXY[x * (mip->SizeY + 1)]; nx = FixedMul(ggxstart + ggxinc[x], viewingrangerecip) + x1; ny = ggystart + ggyinc[x]; for (y = ys; y != ye; y += yi, nx += dagyinc, ny -= dagxinc) { if ((ny <= nytooclose) || (ny >= nytoofar)) continue; voxptr = (kvxslab_t *)(slabxoffs + xyoffs[y]); voxend = (kvxslab_t *)(slabxoffs + xyoffs[y+1]); if (voxptr >= voxend) continue; lx = xs_RoundToInt(nx * centerxwide_f / (ny + y1)) + centerx; if (lx < 0) lx = 0; rx = xs_RoundToInt((nx + nxoff) * centerxwide_f / (ny + y2)) + centerx; if (rx > viewwidth) rx = viewwidth; if (rx <= lx) continue; if (flags & DVF_MIRRORED) { int t = viewwidth - lx; lx = viewwidth - rx; rx = t; } fixed_t l1 = xs_RoundToInt(centerxwidebig_f / (ny - yoff)); fixed_t l2 = xs_RoundToInt(centerxwidebig_f / (ny + yoff)); for (; voxptr < voxend; voxptr = (kvxslab_t *)((BYTE *)voxptr + voxptr->zleng + 3)) { const BYTE *col = voxptr->col; int zleng = voxptr->zleng; int ztop = voxptr->ztop; fixed_t z1, z2; if (ztop < minslabz) { int diff = minslabz - ztop; ztop = minslabz; col += diff; zleng -= diff; } if (ztop + zleng > maxslabz) { int diff = ztop + zleng - maxslabz; zleng -= diff; } if (zleng <= 0) continue; j = (ztop << 15) - syoff; if (j < 0) { k = j + (zleng << 15); if (k < 0) { if ((voxptr->backfacecull & oand32) == 0) continue; z2 = MulScale32(l2, k) + centery; /* Below slab */ } else { if ((voxptr->backfacecull & oand) == 0) continue; /* Middle of slab */ z2 = MulScale32(l1, k) + centery; } z1 = MulScale32(l1, j) + centery; } else { if ((voxptr->backfacecull & oand16) == 0) continue; z1 = MulScale32(l2, j) + centery; /* Above slab */ z2 = MulScale32(l1, j + (zleng << 15)) + centery; } if (z2 <= z1) continue; if (zleng == 1) { yinc = 0; } else { if (z2-z1 >= 1024) yinc = FixedDiv(zleng, z2 - z1); else yinc = (((1 << 24) - 1) / (z2 - z1)) * zleng >> 8; } // [RH] Clip each column separately, not just by the first one. for (int stripwidth = MIN(countof(z1a), rx - lx), lxt = lx; lxt < rx; (lxt += countof(z1a)), stripwidth = MIN(countof(z1a), rx - lxt)) { // Calculate top and bottom pixels locations for (int xxx = 0; xxx < stripwidth; ++xxx) { if (zleng == 1) { yplc[xxx] = 0; z1a[xxx] = MAX(z1, daumost[lxt + xxx]); } else { if (z1 < daumost[lxt + xxx]) { yplc[xxx] = yinc * (daumost[lxt + xxx] - z1); z1a[xxx] = daumost[lxt + xxx]; } else { yplc[xxx] = 0; z1a[xxx] = z1; } } z2a[xxx] = MIN(z2, dadmost[lxt + xxx]); } // Find top and bottom pixels that match and draw them as one strip for (int xxl = 0, xxr; xxl < stripwidth; ) { if (z1a[xxl] >= z2a[xxl]) { // No column here xxl++; continue; } int z1 = z1a[xxl]; int z2 = z2a[xxl]; // How many columns share the same extents? for (xxr = xxl + 1; xxr < stripwidth; ++xxr) { if (z1a[xxr] != z1 || z2a[xxr] != z2) break; } if (!(flags & DVF_OFFSCREEN)) { // Draw directly to the screen. R_DrawSlab(xxr - xxl, yplc[xxl], z2 - z1, yinc, col, ylookup[z1] + lxt + xxl + dc_destorg); } else { // Record the area covered and possibly draw to an offscreen buffer. dc_yl = z1; dc_yh = z2 - 1; dc_count = z2 - z1; dc_iscale = yinc; for (int x = xxl; x < xxr; ++x) { OffscreenCoverageBuffer->InsertSpan(lxt + x, z1, z2); if (!(flags & DVF_SPANSONLY)) { dc_x = lxt + x; rt_initcols(OffscreenColorBuffer + (dc_x & ~3) * OffscreenBufferHeight); dc_source = col; dc_texturefrac = yplc[xxl]; hcolfunc_pre(); } } } xxl = xxr; } } } } } } } //========================================================================== // // FCoverageBuffer Constructor // //========================================================================== FCoverageBuffer::FCoverageBuffer(int lists) : Spans(NULL), FreeSpans(NULL) { NumLists = lists; Spans = new Span *[lists]; memset(Spans, 0, sizeof(Span*)*lists); } //========================================================================== // // FCoverageBuffer Destructor // //========================================================================== FCoverageBuffer::~FCoverageBuffer() { if (Spans != NULL) { delete[] Spans; } } //========================================================================== // // FCoverageBuffer :: Clear // //========================================================================== void FCoverageBuffer::Clear() { SpanArena.FreeAll(); memset(Spans, 0, sizeof(Span*)*NumLists); FreeSpans = NULL; } //========================================================================== // // FCoverageBuffer :: InsertSpan // // start is inclusive. // stop is exclusive. // //========================================================================== void FCoverageBuffer::InsertSpan(int listnum, int start, int stop) { assert(unsigned(listnum) < NumLists); assert(start < stop); Span **span_p = &Spans[listnum]; Span *span; if (*span_p == NULL || (*span_p)->Start > stop) { // This list is empty or the first entry is after this one, so we can just insert the span. goto addspan; } // Insert the new span in order, merging with existing ones. while (*span_p != NULL) { if ((*span_p)->Stop < start) // ===== (existing span) { // Span ends before this one starts. // ++++ (new span) span_p = &(*span_p)->NextSpan; continue; } // Does the new span overlap or abut the existing one? if ((*span_p)->Start <= start) { if ((*span_p)->Stop >= stop) // ============= { // The existing span completely covers this one. // +++++ return; } extend: // Extend the existing span with the new one. // ====== span = *span_p; // +++++++ span->Stop = stop; // (or) +++++ // Free up any spans we just covered up. span_p = &(*span_p)->NextSpan; while (*span_p != NULL && (*span_p)->Start <= stop && (*span_p)->Stop <= stop) { Span *span = *span_p; // ====== ====== *span_p = span->NextSpan; // +++++++++++++ span->NextSpan = FreeSpans; FreeSpans = span; } if (*span_p != NULL && (*span_p)->Start <= stop) // ======= ======== { // Our new span connects two existing spans. // ++++++++++++++ // They should all be collapsed into a single span. span->Stop = (*span_p)->Stop; span = *span_p; *span_p = span->NextSpan; span->NextSpan = FreeSpans; FreeSpans = span; } goto check; } else if ((*span_p)->Start <= stop) // ===== { // The new span extends the existing span from // ++++ // the beginning. // (or) ++++ (*span_p)->Start = start; if ((*span_p)->Stop < stop) { // The new span also extends the existing span // ====== // at the bottom // ++++++++++++++ goto extend; } goto check; } else // ====== { // No overlap, so insert a new span. // +++++ goto addspan; } } // Append a new span to the end of the list. addspan: span = AllocSpan(); span->NextSpan = *span_p; span->Start = start; span->Stop = stop; *span_p = span; check: #ifdef _DEBUG // Validate the span list: Spans must be in order, and there must be // at least one pixel between spans. for (span = Spans[listnum]; span != NULL; span = span->NextSpan) { assert(span->Start < span->Stop); if (span->NextSpan != NULL) { assert(span->Stop < span->NextSpan->Start); } } #endif ; } //========================================================================== // // FCoverageBuffer :: AllocSpan // //========================================================================== FCoverageBuffer::Span *FCoverageBuffer::AllocSpan() { Span *span; if (FreeSpans != NULL) { span = FreeSpans; FreeSpans = span->NextSpan; } else { span = (Span *)SpanArena.Alloc(sizeof(Span)); } return span; } //========================================================================== // // R_CheckOffscreenBuffer // // Allocates the offscreen coverage buffer and optionally the offscreen // color buffer. If they already exist but are the wrong size, they will // be reallocated. // //========================================================================== void R_CheckOffscreenBuffer(int width, int height, bool spansonly) { if (OffscreenCoverageBuffer == NULL) { assert(OffscreenColorBuffer == NULL && "The color buffer cannot exist without the coverage buffer"); OffscreenCoverageBuffer = new FCoverageBuffer(width); } else if (OffscreenCoverageBuffer->NumLists != (unsigned)width) { delete OffscreenCoverageBuffer; OffscreenCoverageBuffer = new FCoverageBuffer(width); if (OffscreenColorBuffer != NULL) { delete[] OffscreenColorBuffer; OffscreenColorBuffer = NULL; } } else { OffscreenCoverageBuffer->Clear(); } if (!spansonly) { if (OffscreenColorBuffer == NULL) { OffscreenColorBuffer = new BYTE[width * height]; } else if (OffscreenBufferWidth != width || OffscreenBufferHeight != height) { delete[] OffscreenColorBuffer; OffscreenColorBuffer = new BYTE[width * height]; } } OffscreenBufferWidth = width; OffscreenBufferHeight = height; }