// 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 "templates.h" #include "doomdef.h" #include "m_swap.h" #include "m_argv.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 "r_bsp.h" #include "r_plane.h" #include "r_segs.h" #include "v_palette.h" extern FTexture *CrosshairImage; extern fixed_t globaluclip, globaldclip; #define MINZ (2048*4) #define BASEYCENTER (100) EXTERN_CVAR (Bool, st_scale) CVAR (Bool, r_drawfuzz, true, CVAR_ARCHIVE) // // 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 pspritexscale; fixed_t pspriteyscale; fixed_t pspritexiscale; fixed_t sky1scale; // [RH] Sky 1 scale factor fixed_t sky2scale; // [RH] Sky 2 scale factor static int spriteshade; TArray ParticlesInSubsec; // constant arrays // used for psprite clipping and initializing clipping short zeroarray[MAXWIDTH]; short screenheightarray[MAXWIDTH]; #define MAX_SPRITE_FRAMES 29 // [RH] Macro-ized as in BOOM. CVAR (Bool, r_drawplayersprites, true, 0) // [RH] Draw player sprites? // // INITIALIZATION FUNCTIONS // // variables used to look up // and range check thing_t sprites patches TArray sprites; TArray SpriteFrames; DWORD NumStdSprites; // The first x sprites that don't belong to skins. struct spriteframewithrotate : public spriteframe_t { int rotate; } sprtemp[MAX_SPRITE_FRAMES]; int maxframe; char* spritename; // [RH] skin globals FPlayerSkin *skins; size_t numskins; BYTE OtherGameSkinRemap[256]; PalEntry OtherGameSkinPalette[256]; // [RH] particle globals WORD NumParticles; WORD ActiveParticles; WORD InactiveParticles; particle_t *Particles; CVAR (Bool, r_particles, true, 0); // // R_InstallSpriteLump // Local function for R_InitSprites. // // [RH] Removed checks for coexistance of rotation 0 with other // rotations and made it look more like BOOM's version. // static void R_InstallSpriteLump (FTextureID lump, unsigned frame, char rot, bool flipped) { unsigned rotation; if (rot >= '0' && rot <= '9') { rotation = rot - '0'; } else if (rot >= 'A') { rotation = rot - 'A' + 10; } else { rotation = 17; } if (frame >= MAX_SPRITE_FRAMES || rotation > 16) I_FatalError ("R_InstallSpriteLump: Bad frame characters in lump %s", TexMan[lump]->Name); if ((int)frame > maxframe) maxframe = frame; if (rotation == 0) { // the lump should be used for all rotations // false=0, true=1, but array initialised to -1 // allows doom to have a "no value set yet" boolean value! int r; for (r = 14; r >= 0; r -= 2) { if (!sprtemp[frame].Texture[r].isValid()) { sprtemp[frame].Texture[r] = lump; if (flipped) { sprtemp[frame].Flip |= 1 << r; } sprtemp[frame].rotate = false; } } } else { if (rotation <= 8) { rotation = (rotation - 1) * 2; } else { rotation = (rotation - 9) * 2 + 1; } if (!sprtemp[frame].Texture[rotation].isValid()) { // the lump is only used for one rotation sprtemp[frame].Texture[rotation] = lump; if (flipped) { sprtemp[frame].Flip |= 1 << rotation; } sprtemp[frame].rotate = true; } } } // [RH] Seperated out of R_InitSpriteDefs() static void R_InstallSprite (int num) { int frame; int framestart; int rot; // int undefinedFix; if (maxframe == -1) { sprites[num].numframes = 0; return; } maxframe++; // [RH] If any frames are undefined, but there are some defined frames, map // them to the first defined frame. This is a fix for Doom Raider, which actually // worked with ZDoom 2.0.47, because of a bug here. It does not define frames A, // B, or C for the sprite PSBG, but because I had sprtemp[].rotate defined as a // bool, this code never detected that it was not actually present. After switching // to the unified texture system, this caused it to crash while loading the wad. // [RH] Let undefined frames actually be blank because LWM uses this in at least // one of her wads. // for (frame = 0; frame < maxframe && sprtemp[frame].rotate == -1; ++frame) // { } // // undefinedFix = frame; for (frame = 0; frame < maxframe; ++frame) { switch (sprtemp[frame].rotate) { case -1: // no rotations were found for that frame at all //I_FatalError ("R_InstallSprite: No patches found for %s frame %c", sprites[num].name, frame+'A'); break; case 0: // only the first rotation is needed for (rot = 1; rot < 16; ++rot) { sprtemp[frame].Texture[rot] = sprtemp[frame].Texture[0]; } // If the frame is flipped, they all should be if (sprtemp[frame].Flip & 1) { sprtemp[frame].Flip = 0xFFFF; } break; case 1: // must have all 8 frame pairs for (rot = 0; rot < 8; ++rot) { if (!sprtemp[frame].Texture[rot*2+1].isValid()) { sprtemp[frame].Texture[rot*2+1] = sprtemp[frame].Texture[rot*2]; if (sprtemp[frame].Flip & (1 << (rot*2))) { sprtemp[frame].Flip |= 1 << (rot*2+1); } } if (!sprtemp[frame].Texture[rot*2].isValid()) { sprtemp[frame].Texture[rot*2] = sprtemp[frame].Texture[rot*2+1]; if (sprtemp[frame].Flip & (1 << (rot*2+1))) { sprtemp[frame].Flip |= 1 << (rot*2); } } } for (rot = 0; rot < 16; ++rot) { if (!sprtemp[frame].Texture[rot].isValid()) I_FatalError ("R_InstallSprite: Sprite %s frame %c is missing rotations", sprites[num].name, frame+'A'); } break; } } for (frame = 0; frame < maxframe; ++frame) { if (sprtemp[frame].rotate == -1) { memset (&sprtemp[frame], 0, sizeof(sprtemp[0])); } } // allocate space for the frames present and copy sprtemp to it sprites[num].numframes = maxframe; sprites[num].spriteframes = WORD(framestart = SpriteFrames.Reserve (maxframe)); for (frame = 0; frame < maxframe; ++frame) { memcpy (SpriteFrames[framestart+frame].Texture, sprtemp[frame].Texture, sizeof(sprtemp[frame].Texture)); SpriteFrames[framestart+frame].Flip = sprtemp[frame].Flip; } // Let the textures know about the rotations for (frame = 0; frame < maxframe; ++frame) { if (sprtemp[frame].rotate == 1) { for (int rot = 0; rot < 16; ++rot) { TexMan[sprtemp[frame].Texture[rot]]->Rotations = framestart + frame; } } } } // // R_InitSpriteDefs // Pass a null terminated list of sprite names // (4 chars exactly) to be used. // Builds the sprite rotation matrices to account // for horizontally flipped sprites. // Will report an error if the lumps are inconsistant. // Only called at startup. // // Sprite lump names are 4 characters for the actor, // a letter for the frame, and a number for the rotation. // A sprite that is flippable will have an additional // letter/number appended. // The rotation character can be 0 to signify no rotations. // void R_InitSpriteDefs () { struct Hasher { int Head, Next; } *hashes; unsigned int i, max; DWORD intname; // Create a hash table to speed up the process max = TexMan.NumTextures(); hashes = (Hasher *)alloca (sizeof(Hasher) * max); for (i = 0; i < max; ++i) { hashes[i].Head = -1; } for (i = 0; i < max; ++i) { FTexture *tex = TexMan.ByIndex(i); if (tex->UseType == FTexture::TEX_Sprite && strlen (tex->Name) >= 6) { DWORD bucket = *(DWORD *)tex->Name % max; hashes[i].Next = hashes[bucket].Head; hashes[bucket].Head = i; } } // scan all the lump names for each of the names, noting the highest frame letter. for (i = 0; i < sprites.Size(); ++i) { memset (sprtemp, 0xFF, sizeof(sprtemp)); for (int j = 0; j < MAX_SPRITE_FRAMES; ++j) { sprtemp[j].Flip = 0; } maxframe = -1; intname = *(DWORD *)sprites[i].name; // scan the lumps, filling in the frames for whatever is found int hash = hashes[intname % max].Head; while (hash != -1) { FTexture *tex = TexMan[hash]; if (*(DWORD *)tex->Name == intname) { R_InstallSpriteLump (FTextureID(hash), tex->Name[4] - 'A', tex->Name[5], false); if (tex->Name[6]) R_InstallSpriteLump (FTextureID(hash), tex->Name[6] - 'A', tex->Name[7], true); } hash = hashes[hash].Next; } R_InstallSprite ((int)i); } } // [RH] // R_InitSkins // Reads in everything applicable to a skin. The skins should have already // been counted and had their identifiers assigned to namespaces. // #define NUMSKINSOUNDS 17 static const char *skinsoundnames[NUMSKINSOUNDS][2] = { // The *painXXX sounds must be the first four { "dsplpain", "*pain100" }, { "dsplpain", "*pain75" }, { "dsplpain", "*pain50" }, { "dsplpain", "*pain25" }, { "dsplpain", "*poison" }, { "dsoof", "*grunt" }, { "dsoof", "*land" }, { "dspldeth", "*death" }, { "dspldeth", "*wimpydeath" }, { "dspdiehi", "*xdeath" }, { "dspdiehi", "*crazydeath" }, { "dsnoway", "*usefail" }, { "dsnoway", "*puzzfail" }, { "dsslop", "*gibbed" }, { "dsslop", "*splat" }, { "dspunch", "*fist" }, { "dsjump", "*jump" } }; /* static int STACK_ARGS skinsorter (const void *a, const void *b) { return stricmp (((FPlayerSkin *)a)->name, ((FPlayerSkin *)b)->name); } */ void R_InitSkins (void) { FSoundID playersoundrefs[NUMSKINSOUNDS]; spritedef_t temp; int sndlumps[NUMSKINSOUNDS]; char key[65]; DWORD intname, crouchname; size_t i; int j, k, base; int lastlump; int aliasid; bool remove; const PClass *basetype, *transtype; key[sizeof(key)-1] = 0; i = PlayerClasses.Size () - 1; lastlump = 0; for (j = 0; j < NUMSKINSOUNDS; ++j) { playersoundrefs[j] = skinsoundnames[j][1]; } while ((base = Wads.FindLump ("S_SKIN", &lastlump, true)) != -1) { // The player sprite has 23 frames. This means that the S_SKIN // marker needs a minimum of 23 lumps after it. if (base >= Wads.GetNumLumps() - 23 || base == -1) continue; i++; for (j = 0; j < NUMSKINSOUNDS; j++) sndlumps[j] = -1; skins[i].namespc = Wads.GetLumpNamespace (base); FScanner sc(base); intname = 0; crouchname = 0; remove = false; basetype = NULL; transtype = NULL; // Data is stored as "key = data". while (sc.GetString ()) { strncpy (key, sc.String, sizeof(key)-1); if (!sc.GetString() || sc.String[0] != '=') { Printf (PRINT_BOLD, "Bad format for skin %d: %s\n", (int)i, key); break; } sc.GetString (); if (0 == stricmp (key, "name")) { strncpy (skins[i].name, sc.String, 16); for (j = 0; (size_t)j < i; j++) { if (stricmp (skins[i].name, skins[j].name) == 0) { mysnprintf (skins[i].name, countof(skins[i].name), "skin%d", (int)i); Printf (PRINT_BOLD, "Skin %s duplicated as %s\n", skins[j].name, skins[i].name); break; } } } else if (0 == stricmp (key, "sprite")) { for (j = 3; j >= 0; j--) sc.String[j] = toupper (sc.String[j]); intname = *((DWORD *)sc.String); } else if (0 == stricmp (key, "crouchsprite")) { for (j = 3; j >= 0; j--) sc.String[j] = toupper (sc.String[j]); crouchname = *((DWORD *)sc.String); } else if (0 == stricmp (key, "face")) { for (j = 2; j >= 0; j--) skins[i].face[j] = toupper (sc.String[j]); skins[i].face[3] = '\0'; } else if (0 == stricmp (key, "gender")) { skins[i].gender = D_GenderToInt (sc.String); } else if (0 == stricmp (key, "scale")) { skins[i].ScaleX = clamp (FLOAT2FIXED(atof (sc.String)), 1, 256*FRACUNIT); skins[i].ScaleY = skins[i].ScaleX; } else if (0 == stricmp (key, "game")) { if (gameinfo.gametype == GAME_Heretic) basetype = PClass::FindClass (NAME_HereticPlayer); else if (gameinfo.gametype == GAME_Strife) basetype = PClass::FindClass (NAME_StrifePlayer); else basetype = PClass::FindClass (NAME_DoomPlayer); transtype = basetype; if (stricmp (sc.String, "heretic") == 0) { if (gameinfo.gametype & GAME_DoomChex) { transtype = PClass::FindClass (NAME_HereticPlayer); skins[i].othergame = true; } else if (gameinfo.gametype != GAME_Heretic) { remove = true; } } else if (stricmp (sc.String, "strife") == 0) { if (gameinfo.gametype != GAME_Strife) { remove = true; } } else { if (gameinfo.gametype == GAME_Heretic) { transtype = PClass::FindClass (NAME_DoomPlayer); skins[i].othergame = true; } else if (!(gameinfo.gametype & GAME_DoomChex)) { remove = true; } } if (remove) break; } else if (0 == stricmp (key, "class")) { // [GRB] Define the skin for a specific player class int pclass = D_PlayerClassToInt (sc.String); if (pclass < 0) { remove = true; break; } basetype = transtype = PlayerClasses[pclass].Type; } else if (key[0] == '*') { // Player sound replacment (ZDoom extension) int lump = Wads.CheckNumForName (sc.String, skins[i].namespc); if (lump == -1) { lump = Wads.CheckNumForFullName (sc.String, true, ns_sounds); } if (lump != -1) { if (stricmp (key, "*pain") == 0) { // Replace all pain sounds in one go aliasid = S_AddPlayerSound (skins[i].name, skins[i].gender, playersoundrefs[0], lump, true); for (int l = 3; l > 0; --l) { S_AddPlayerSoundExisting (skins[i].name, skins[i].gender, playersoundrefs[l], aliasid, true); } } else { int sndref = S_FindSoundNoHash (key); if (sndref != 0) { S_AddPlayerSound (skins[i].name, skins[i].gender, sndref, lump, true); } } } } else { for (j = 0; j < NUMSKINSOUNDS; j++) { if (stricmp (key, skinsoundnames[j][0]) == 0) { sndlumps[j] = Wads.CheckNumForName (sc.String, skins[i].namespc); if (sndlumps[j] == -1) { // Replacement not found, try finding it in the global namespace sndlumps[j] = Wads.CheckNumForFullName (sc.String, true, ns_sounds); } } } //if (j == 8) // Printf ("Funny info for skin %i: %s = %s\n", i, key, sc.String); } } // [GRB] Assume Doom skin by default if (!remove && basetype == NULL) { if (gameinfo.gametype & GAME_DoomChex) { basetype = transtype = PClass::FindClass (NAME_DoomPlayer); } else if (gameinfo.gametype == GAME_Heretic) { basetype = PClass::FindClass (NAME_HereticPlayer); transtype = PClass::FindClass (NAME_DoomPlayer); skins[i].othergame = true; } else { remove = true; } } if (!remove) { skins[i].range0start = transtype->Meta.GetMetaInt (APMETA_ColorRange) & 0xff; skins[i].range0end = transtype->Meta.GetMetaInt (APMETA_ColorRange) >> 8; remove = true; for (j = 0; j < (int)PlayerClasses.Size (); j++) { const PClass *type = PlayerClasses[j].Type; if (type->IsDescendantOf (basetype) && GetDefaultByType (type)->SpawnState->sprite == GetDefaultByType (basetype)->SpawnState->sprite && type->Meta.GetMetaInt (APMETA_ColorRange) == basetype->Meta.GetMetaInt (APMETA_ColorRange)) { PlayerClasses[j].Skins.Push ((int)i); remove = false; } } } if (!remove) { if (skins[i].name[0] == 0) mysnprintf (skins[i].name, countof(skins[i].name), "skin%d", (int)i); // Now collect the sprite frames for this skin. If the sprite name was not // specified, use whatever immediately follows the specifier lump. if (intname == 0) { char name[9]; Wads.GetLumpName (name, base+1); intname = *(DWORD *)name; } int basens = Wads.GetLumpNamespace(base); for(int spr = 0; spr<2; spr++) { memset (sprtemp, 0xFFFF, sizeof(sprtemp)); for (k = 0; k < MAX_SPRITE_FRAMES; ++k) { sprtemp[k].Flip = 0; } maxframe = -1; if (spr == 1) { if (crouchname !=0 && crouchname != intname) { intname = crouchname; } else { skins[i].crouchsprite = -1; break; } } for (k = base + 1; Wads.GetLumpNamespace(k) == basens; k++) { char lname[9]; Wads.GetLumpName (lname, k); if (*(DWORD *)lname == intname) { FTextureID picnum = TexMan.CreateTexture(k, FTexture::TEX_SkinSprite); R_InstallSpriteLump (picnum, lname[4] - 'A', lname[5], false); if (lname[6]) R_InstallSpriteLump (picnum, lname[6] - 'A', lname[7], true); } } if (spr == 0 && maxframe <= 0) { Printf (PRINT_BOLD, "Skin %s (#%d) has no frames. Removing.\n", skins[i].name, (int)i); remove = true; break; } Wads.GetLumpName (temp.name, base+1); temp.name[4] = 0; int sprno = (int)sprites.Push (temp); if (spr==0) skins[i].sprite = sprno; else skins[i].crouchsprite = sprno; R_InstallSprite (sprno); } } if (remove) { if (i < numskins-1) memmove (&skins[i], &skins[i+1], sizeof(skins[0])*(numskins-i-1)); i--; continue; } // Register any sounds this skin provides aliasid = 0; for (j = 0; j < NUMSKINSOUNDS; j++) { if (sndlumps[j] != -1) { if (j == 0 || sndlumps[j] != sndlumps[j-1]) { aliasid = S_AddPlayerSound (skins[i].name, skins[i].gender, playersoundrefs[j], sndlumps[j], true); } else { S_AddPlayerSoundExisting (skins[i].name, skins[i].gender, playersoundrefs[j], aliasid, true); } } } // Make sure face prefix is a full 3 chars if (skins[i].face[1] == 0 || skins[i].face[2] == 0) { skins[i].face[0] = 0; } } if (numskins > PlayerClasses.Size ()) { // The sound table may have changed, so rehash it. S_HashSounds (); S_ShrinkPlayerSoundLists (); } } // [RH] Find a skin by name int R_FindSkin (const char *name, int pclass) { if (stricmp ("base", name) == 0) { return pclass; } for (unsigned i = PlayerClasses.Size(); i < numskins; i++) { if (strnicmp (skins[i].name, name, 16) == 0) { if (PlayerClasses[pclass].CheckSkin (i)) return i; else return pclass; } } return pclass; } // [RH] List the names of all installed skins CCMD (skins) { int i; for (i = PlayerClasses.Size ()-1; i < (int)numskins; i++) Printf ("% 3d %s\n", i-PlayerClasses.Size ()+1, skins[i].name); } // // GAME FUNCTIONS // int MaxVisSprites; vissprite_t **vissprites; vissprite_t **firstvissprite; vissprite_t **vissprite_p; vissprite_t **lastvissprite; int newvissprite; static vissprite_t **spritesorter; static int spritesortersize = 0; static int vsprcount; static void R_CreateSkinTranslation (const char *palname) { FMemLump lump = Wads.ReadLump (palname); const BYTE *otherPal = (BYTE *)lump.GetMem(); for (int i = 0; i < 256; ++i) { OtherGameSkinRemap[i] = ColorMatcher.Pick (otherPal[0], otherPal[1], otherPal[2]); OtherGameSkinPalette[i] = PalEntry(otherPal[0], otherPal[1], otherPal[2]); otherPal += 3; } } // // R_InitSprites // Called at program start. // void R_InitSprites () { int lump, lastlump; unsigned int i, j; clearbufshort (zeroarray, MAXWIDTH, 0); // [RH] Create a standard translation to map skins between Heretic and Doom if (gameinfo.gametype == GAME_DoomChex) { R_CreateSkinTranslation ("SPALHTIC"); } else { R_CreateSkinTranslation ("SPALDOOM"); } // [RH] Count the number of skins. numskins = PlayerClasses.Size (); lastlump = 0; while ((lump = Wads.FindLump ("S_SKIN", &lastlump, true)) != -1) { numskins++; } // [RH] Do some preliminary setup skins = new FPlayerSkin[numskins]; memset (skins, 0, sizeof(*skins) * numskins); for (i = 0; i < numskins; i++) { // Assume Doom skin by default const PClass *type = PlayerClasses[0].Type; skins[i].range0start = type->Meta.GetMetaInt (APMETA_ColorRange) & 255; skins[i].range0end = type->Meta.GetMetaInt (APMETA_ColorRange) >> 8; skins[i].ScaleX = GetDefaultByType (type)->scaleX; skins[i].ScaleY = GetDefaultByType (type)->scaleY; } R_InitSpriteDefs (); NumStdSprites = sprites.Size(); R_InitSkins (); // [RH] Finish loading skin data // [RH] Set up base skin // [GRB] Each player class has its own base skin for (i = 0; i < PlayerClasses.Size (); i++) { const PClass *basetype = PlayerClasses[i].Type; const char *pclassface = basetype->Meta.GetMetaString (APMETA_Face); strcpy (skins[i].name, "Base"); if (pclassface == NULL || strcmp(pclassface, "None") == 0) { skins[i].face[0] = 'S'; skins[i].face[1] = 'T'; skins[i].face[2] = 'F'; skins[i].face[3] = '\0'; } else { strcpy(skins[i].face, pclassface); } skins[i].range0start = basetype->Meta.GetMetaInt (APMETA_ColorRange) & 255; skins[i].range0end = basetype->Meta.GetMetaInt (APMETA_ColorRange) >> 8; skins[i].ScaleX = GetDefaultByType (basetype)->scaleX; skins[i].ScaleY = GetDefaultByType (basetype)->scaleY; skins[i].sprite = GetDefaultByType (basetype)->SpawnState->sprite; skins[i].namespc = ns_global; PlayerClasses[i].Skins.Push (i); if (memcmp (sprites[skins[i].sprite].name, "PLAY", 4) == 0) { for (j = 0; j < sprites.Size (); j++) { if (memcmp (sprites[j].name, deh.PlayerSprite, 4) == 0) { skins[i].sprite = (int)j; break; } } } } // [RH] Sort the skins, but leave base as skin 0 //qsort (&skins[PlayerClasses.Size ()], numskins-PlayerClasses.Size (), sizeof(FPlayerSkin), skinsorter); } void R_DeinitSprites() { // Free skins if (skins != NULL) { delete[] skins; skins = NULL; } // 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; } } // // R_ClearSprites // Called at frame start. // void R_ClearSprites (void) { vissprite_p = firstvissprite; } // // 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 ("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; fixed_t spryscale; fixed_t sprtopscreen; bool sprflipvert; void R_DrawMaskedColumn (const BYTE *column, const FTexture::Span *span) { while (span->Length != 0) { const int length = span->Length; const int top = span->TopOffset; // calculate unclipped screen coordinates for post dc_yl = (sprtopscreen + spryscale * top) >> FRACBITS; dc_yh = (sprtopscreen + spryscale * (top + length) - FRACUNIT) >> FRACBITS; if (sprflipvert) { swap (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) - dc_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 = dc_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++; } } // // 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; dc_colormap = vis->colormap; mode = R_SetPatchStyle (vis->RenderStyle, vis->alpha, vis->Translation, vis->FillColor); 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 + 1) & ~3; } tex = vis->pic; spryscale = vis->yscale; sprflipvert = false; dc_iscale = 0xffffffffu / (unsigned)vis->yscale; dc_texturemid = vis->texturemid; frac = vis->startfrac; xiscale = vis->xiscale; sprtopscreen = centeryfrac - FixedMul (dc_texturemid, spryscale); dc_x = vis->x1; x2 = vis->x2 + 1; if (dc_x < x2) { while ((dc_x < stop4) && (dc_x & 3)) { pixels = tex->GetColumn (frac >> FRACBITS, &spans); 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); R_DrawMaskedColumnHoriz (pixels, spans); dc_x++; frac += xiscale; } rt_draw4cols (dc_x - 4); } while (dc_x < x2) { pixels = tex->GetColumn (frac >> FRACBITS, &spans); R_DrawMaskedColumn (pixels, spans); dc_x++; frac += xiscale; } } } R_FinishSetPatchStyle (); NetUpdate (); } // // R_ProjectSprite // Generates a vissprite for a thing if it might be visible. // void R_ProjectSprite (AActor *thing, int fakeside) { fixed_t fx, fy, fz; fixed_t tr_x; fixed_t tr_y; fixed_t gzt; // killough 3/27/98 fixed_t gzb; // [RH] use bottom of sprite, not actor fixed_t tx, tx2; fixed_t tz; fixed_t xscale; int x1; int x2; FTextureID picnum; FTexture *tex; WORD flip; 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)) { return; } // [RH] Interpolate the sprite's position to make it look smooth fx = thing->PrevX + FixedMul (r_TicFrac, thing->x - thing->PrevX); fy = thing->PrevY + FixedMul (r_TicFrac, thing->y - thing->PrevY); fz = thing->PrevZ + FixedMul (r_TicFrac, thing->z - thing->PrevZ); // transform the origin point tr_x = fx - viewx; tr_y = fy - viewy; tz = DMulScale20 (tr_x, viewtancos, tr_y, viewtansin); // thing is behind view plane? if (tz < MINZ) return; tx = DMulScale16 (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 ((abs (tx) >> 6) > tz) { return; } xscale = DivScale12 (centerxfrac, tz); if (thing->picnum.isValid()) { picnum = thing->picnum; tex = TexMan(picnum); if (tex->UseType == FTexture::TEX_Null) { return; } flip = 0; if (tex->Rotations != 0xFFFF) { // choose a different rotation based on player view spriteframe_t *sprframe = &SpriteFrames[tex->Rotations]; angle_t ang = R_PointToAngle (fx, fy); angle_t rot; if (sprframe->Texture[0] == sprframe->Texture[1]) { rot = (ang - thing->angle + (angle_t)(ANGLE_45/2)*9) >> 28; } else { rot = (ang - thing->angle + (angle_t)(ANGLE_45/2)*9-(angle_t)(ANGLE_180/16)) >> 28; } picnum = sprframe->Texture[rot]; flip = sprframe->Flip & (1 << rot); tex = TexMan[picnum]; // Do not animate the rotation } } else { // decide which texture to use for the sprite #ifdef RANGECHECK if ((unsigned)thing->sprite >= (unsigned)sprites.Size ()) { DPrintf ("R_ProjectSprite: invalid sprite number %i\n", thing->sprite); return; } #endif spritedef_t *sprdef = &sprites[thing->sprite]; 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]; angle_t ang = R_PointToAngle (fx, fy); angle_t rot; if (sprframe->Texture[0] == sprframe->Texture[1]) { rot = (ang - thing->angle + (angle_t)(ANGLE_45/2)*9) >> 28; } else { rot = (ang - thing->angle + (angle_t)(ANGLE_45/2)*9-(angle_t)(ANGLE_180/16)) >> 28; } picnum = sprframe->Texture[rot]; flip = sprframe->Flip & (1 << rot); tex = TexMan[picnum]; // Do not animate the rotation } } if (tex == NULL || tex->UseType == FTexture::TEX_Null) { return; } // [RH] Added scaling int scaled_to = tex->GetScaledTopOffset(); int scaled_bo = scaled_to - tex->GetScaledHeight(); gzt = fz + thing->scaleY * scaled_to; gzb = fz + thing->scaleY * scaled_bo; // [RH] Reject sprites that are off the top or bottom of the screen if (MulScale12 (globaluclip, tz) > viewz - gzb || MulScale12 (globaldclip, tz) < viewz - gzt) { return; } // [RH] Flip for mirrors and renderflags if ((MirrorFlags ^ thing->renderflags) & RF_XFLIP) { flip = !flip; } // calculate edges of the shape const fixed_t thingxscalemul = DivScale16(thing->scaleX, tex->xScale); tx -= (flip ? (tex->GetWidth() - tex->LeftOffset - 1) : tex->LeftOffset) * thingxscalemul; x1 = centerx + MulScale32 (tx, xscale); // off the right side? if (x1 > WindowRight) return; tx += tex->GetWidth() * thingxscalemul; x2 = centerx + MulScale32 (tx, xscale); // off the left side or too small? if (x2 < WindowLeft || x2 <= x1) return; xscale = FixedDiv(FixedMul(thing->scaleX, xscale), tex->xScale); iscale = (tex->GetWidth() << FRACBITS) / (x2 - x1); x2--; // 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->heightsec; if (heightsec != NULL && heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC) { heightsec = NULL; } if (heightsec) // only clip things which are in special sectors { if (fakeside == FAKED_AboveCeiling) { if (gzt < heightsec->ceilingplane.ZatPoint (fx, fy)) return; } else if (fakeside == FAKED_BelowFloor) { if (gzb >= heightsec->floorplane.ZatPoint (fx, fy)) return; } else { if (gzt < heightsec->floorplane.ZatPoint (fx, fy)) return; if (gzb >= heightsec->ceilingplane.ZatPoint (fx, fy)) return; } } // store information in a vissprite vis = R_NewVisSprite (); // killough 3/27/98: save sector for special clipping later vis->heightsec = heightsec; vis->sector = thing->Sector; fixed_t yscale = DivScale16(thing->scaleY, tex->yScale); vis->renderflags = thing->renderflags; vis->RenderStyle = thing->RenderStyle; vis->FillColor = thing->fillcolor; vis->xscale = xscale; vis->yscale = Scale (InvZtoScale, yscale, tz)>>4; vis->depth = tz; vis->idepth = (DWORD)DivScale32 (1, tz) >> 1; // tz is 20.12, so idepth ought to be 12.20, but vis->cx = tx2; // signed math makes it 13.19 vis->gx = fx; vis->gy = fy; vis->gz = gzb; // [RH] use gzb, not thing->z vis->gzt = gzt; // killough 3/27/98 vis->floorclip = FixedDiv (thing->floorclip, yscale); vis->texturemid = (tex->TopOffset << FRACBITS) - FixedDiv (viewz-fz+thing->floorclip, yscale); vis->x1 = x1 < WindowLeft ? WindowLeft : x1; vis->x2 = x2 > WindowRight ? WindowRight : x2; vis->Translation = thing->Translation; // [RH] thing translation table vis->FakeFlatStat = fakeside; vis->alpha = thing->alpha; vis->pic = tex; if (flip) { 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); // 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->RenderStyle.Flags & STYLEF_InvertOverlay); if (vis->RenderStyle.Flags & STYLEF_InvertSource) { invertcolormap = !invertcolormap; } FDynamicColormap *mybasecolormap = basecolormap; if (vis->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 (fixedlightlev) { if (invertcolormap) { mybasecolormap = GetSpecialLights(mybasecolormap->Color, mybasecolormap->Fade.InverseColor(), mybasecolormap->Desaturate); } vis->colormap = mybasecolormap->Maps + fixedlightlev; } else if (fixedcolormap) { // fixed map vis->colormap = fixedcolormap; } else if (!foggy && ((thing->renderflags & RF_FULLBRIGHT) || (thing->flags5 & MF5_BRIGHT))) { // full bright if (invertcolormap) { mybasecolormap = GetSpecialLights(mybasecolormap->Color, mybasecolormap->Fade.InverseColor(), mybasecolormap->Desaturate); } vis->colormap = mybasecolormap->Maps; } else { // diminished light if (invertcolormap) { mybasecolormap = GetSpecialLights(mybasecolormap->Color, mybasecolormap->Fade.InverseColor(), mybasecolormap->Desaturate); } vis->colormap = mybasecolormap->Maps + (GETPALOOKUP ( (fixed_t)DivScale12 (r_SpriteVisibility, tz), spriteshade) << COLORMAPSHIFT); } } // // 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; // 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) { R_ProjectSprite (thing, fakeside); } } // // R_DrawPSprite // void R_DrawPSprite (pspdef_t* psp, int pspnum, AActor *owner, fixed_t sx, fixed_t sy) { fixed_t tx; int x1; int x2; spritedef_t* sprdef; spriteframe_t* sprframe; FTextureID picnum; WORD flip; FTexture* tex; vissprite_t* vis; vissprite_t avis; // decide which patch to use if ( (unsigned)psp->state->sprite >= (unsigned)sprites.Size ()) { DPrintf ("R_DrawPSprite: invalid sprite number %i\n", psp->state->sprite); return; } sprdef = &sprites[psp->state->sprite]; if (psp->state->GetFrame() >= sprdef->numframes) { DPrintf ("R_DrawPSprite: invalid sprite frame %i : %i\n", psp->state->sprite, psp->state->GetFrame()); return; } sprframe = &SpriteFrames[sprdef->spriteframes + psp->state->GetFrame()]; picnum = sprframe->Texture[0]; flip = sprframe->Flip & 1; tex = TexMan(picnum); if (tex->UseType == FTexture::TEX_Null) return; // calculate edges of the shape tx = sx-((320/2)<GetScaledLeftOffset() << FRACBITS; x1 = (centerxfrac + FixedMul (tx, pspritexscale)) >>FRACBITS; // off the right side if (x1 > viewwidth) return; tx += tex->GetScaledWidth() << FRACBITS; x2 = ((centerxfrac + FixedMul (tx, pspritexscale)) >>FRACBITS) - 1; // off the left side if (x2 < 0) return; // store information in a vissprite vis = &avis; vis->renderflags = owner->renderflags; vis->floorclip = 0; vis->texturemid = MulScale16((BASEYCENTER<yScale) + (tex->TopOffset << FRACBITS); if (camera->player && (RenderTarget != screen || viewheight == RenderTarget->GetHeight() || (RenderTarget->GetWidth() > 320 && !st_scale))) { // Adjust PSprite for fullscreen views AWeapon *weapon = NULL; if (camera->player != NULL) { weapon = camera->player->ReadyWeapon; } if (pspnum <= ps_flash && weapon != NULL && weapon->YAdjust != 0) { if (RenderTarget != screen || viewheight == RenderTarget->GetHeight()) { vis->texturemid -= weapon->YAdjust; } else { vis->texturemid -= FixedMul (StatusBar->GetDisplacement (), weapon->YAdjust); } } } if (pspnum <= ps_flash) { // Move the weapon down for 1280x1024. vis->texturemid -= BaseRatioSizes[WidescreenRatio][2]; } vis->x1 = x1 < 0 ? 0 : x1; vis->x2 = x2 >= viewwidth ? viewwidth-1 : x2; vis->xscale = DivScale16(pspritexscale, tex->xScale); vis->yscale = DivScale16(pspriteyscale, tex->yScale); vis->Translation = 0; // [RH] Use default colors vis->pic = tex; if (flip) { vis->xiscale = -MulScale16(pspritexiscale, tex->xScale); vis->startfrac = (tex->GetWidth() << FRACBITS) - 1; } else { vis->xiscale = MulScale16(pspritexiscale, tex->xScale); vis->startfrac = 0; } if (vis->x1 > x1) vis->startfrac += vis->xiscale*(vis->x1-x1); if (pspnum <= ps_flash) { vis->alpha = owner->alpha; vis->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->RenderStyle.Flags & STYLEF_InvertOverlay); if (vis->RenderStyle.Flags & STYLEF_InvertSource) { invertcolormap = !invertcolormap; } FDynamicColormap *mybasecolormap = basecolormap; if (vis->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 (fixedlightlev) { if (invertcolormap) { mybasecolormap = GetSpecialLights(mybasecolormap->Color, mybasecolormap->Fade.InverseColor(), mybasecolormap->Desaturate); } vis->colormap = mybasecolormap->Maps + fixedlightlev; } else if (fixedcolormap) { // fixed color vis->colormap = fixedcolormap; } else if (!foggy && psp->state->GetFullbright()) { // full bright if (invertcolormap) { mybasecolormap = GetSpecialLights(mybasecolormap->Color, mybasecolormap->Fade.InverseColor(), mybasecolormap->Desaturate); } vis->colormap = mybasecolormap->Maps; // [RH] use basecolormap } else { // local light if (invertcolormap) { mybasecolormap = GetSpecialLights(mybasecolormap->Color, mybasecolormap->Fade.InverseColor(), mybasecolormap->Desaturate); } vis->colormap = mybasecolormap->Maps + (GETPALOOKUP (0, spriteshade) << COLORMAPSHIFT); } if (camera->Inventory != NULL) { camera->Inventory->AlterWeaponSprite (vis); } } else { vis->RenderStyle = STYLE_Normal; } R_DrawVisSprite (vis); } // // R_DrawPlayerSprites // void R_DrawPlayerSprites (void) { int i; int lightnum; pspdef_t* psp; sector_t* sec; static sector_t tempsec; int floorlight, ceilinglight; if (!r_drawplayersprites || !camera->player || (players[consoleplayer].cheats & CF_CHASECAM)) return; // 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 foggy flag foggy = (level.fadeto || sec->ColorMap->Fade || (level.flags & LEVEL_HASFADETABLE)); r_actualextralight = foggy ? 0 : extralight << 4; // [RH] set basecolormap basecolormap = sec->ColorMap; // 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) { fixed_t centerhack = centeryfrac; fixed_t ofsx, ofsy; centery = viewheight >> 1; centeryfrac = centery << FRACBITS; P_BobWeapon (camera->player, &camera->player->psprites[ps_weapon], &ofsx, &ofsy); // add all active psprites for (i = 0, psp = camera->player->psprites; i < NUMPSPRITES; i++, psp++) { // [RH] Don't draw the targeter's crosshair if the player already has a crosshair set. if (psp->state && (i != ps_targetcenter || CrosshairImage == NULL)) { R_DrawPSprite (psp, i, camera, psp->sx + ofsx, psp->sy + ofsy); } // [RH] Don't bob the targeter. if (i == ps_flash) { ofsx = ofsy = 0; } } centeryfrac = centerhack; centery = centerhack >> FRACBITS; } } // // 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 static int STACK_ARGS sv_compare (const void *arg1, const void *arg2) { int diff = (*(vissprite_t **)arg2)->idepth - (*(vissprite_t **)arg1)->idepth; // If two sprites are the same distance, then the higher one gets precedence if (diff == 0) return (*(vissprite_t **)arg2)->gzt - (*(vissprite_t **)arg1)->gzt; return diff; } #if 0 static drawseg_t **drawsegsorter; static int drawsegsortersize = 0; // Sort vissprites by leftmost column, left to right static int STACK_ARGS 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 STACK_ARGS 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 void R_SortVisSprites (int (STACK_ARGS *compare)(const void *, const void *), 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; } for (i = 0, spr = firstvissprite; i < vsprcount; i++, spr++) { spritesorter[i] = *spr; } qsort (spritesorter, vsprcount, sizeof (vissprite_t *), compare); } // // R_DrawSprite // void R_DrawSprite (vissprite_t *spr) { static short clipbot[MAXWIDTH]; static short cliptop[MAXWIDTH]; drawseg_t *ds; int i; int r1, r2; short topclip, botclip; short *clip1, *clip2; // [RH] Check for particles if (spr->pic == NULL) { R_DrawParticle (spr); return; } // [RH] Quickly reject sprites with bad x ranges. if (spr->x1 > spr->x2) return; // [RH] Sprites split behind a one-sided line can also be discarded. if (spr->sector == NULL) return; // [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 fixed_t scale = MulScale19 (InvZtoScale, spr->idepth); if (spr->heightsec && !(spr->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC)) { // only things in specially marked sectors if (spr->FakeFlatStat != FAKED_AboveCeiling) { fixed_t h = spr->heightsec->floorplane.ZatPoint (spr->gx, spr->gy); //h = (centeryfrac - FixedMul (h-viewz, spr->yscale)) >> FRACBITS; h = (centeryfrac - FixedMul (h-viewz, scale)) >> FRACBITS; if (spr->FakeFlatStat == FAKED_BelowFloor) { // seen below floor: clip top if (h > topclip) { topclip = MIN (h, viewheight); } } else { // seen in the middle: clip bottom if (h < botclip) { botclip = MAX (0, h); } } } if (spr->FakeFlatStat != FAKED_BelowFloor) { fixed_t h = spr->heightsec->ceilingplane.ZatPoint (spr->gx, spr->gy); h = (centeryfrac - FixedMul (h-viewz, scale)) >> FRACBITS; if (spr->FakeFlatStat == FAKED_AboveCeiling) { // seen above ceiling: clip bottom if (h < botclip) { botclip = MAX (0, h); } } else { // seen in the middle: clip top if (h > topclip) { topclip = MIN (h, viewheight); } } } } // killough 3/27/98: end special clipping for deep water / fake ceilings else if (spr->floorclip) { // [RH] Move floorclip stuff from R_DrawVisSprite to here int clip = ((centeryfrac - FixedMul (spr->texturemid - (spr->pic->GetHeight() << FRACBITS) + spr->floorclip, spr->yscale)) >> FRACBITS); if (clip < botclip) { botclip = MAX (0, clip); } } #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 = MIN (h, viewheight); } } #endif i = spr->x2 - spr->x1 + 1; clip1 = clipbot + spr->x1; clip2 = cliptop + spr->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 { // determine if the drawseg obscures the sprite if (ds->x1 > spr->x2 || ds->x2 < spr->x1 || (!(ds->silhouette & SIL_BOTH) && ds->maskedtexturecol == -1 && !ds->bFogBoundary) ) { // does not cover sprite continue; } r1 = MAX (ds->x1, spr->x1); r2 = MIN (ds->x2, spr->x2); fixed_t neardepth, fardepth; if (ds->sz1 < ds->sz2) { neardepth = ds->sz1, fardepth = ds->sz2; } else { neardepth = ds->sz2, fardepth = ds->sz1; } if (neardepth > spr->depth || (fardepth > spr->depth && // Check if sprite is in front of draw seg: DMulScale24 (spr->depth - ds->cy, ds->cdx, ds->cdy, ds->cx - spr->cx) < 0)) { // seg is behind sprite, so draw the mid texture if it has one if (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 + 1; 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 + 1; do { if (*clip1 < *clip2) *clip1 = *clip2; clip1++; clip2++; } while (--i); } } // all clipping has been performed, so draw the sprite mfloorclip = clipbot; mceilingclip = cliptop; R_DrawVisSprite (spr); } // // R_DrawMasked // void R_DrawMasked (void) { drawseg_t *ds; int i; #if 0 R_SplitVisSprites (); #endif R_SortVisSprites (sv_compare, firstvissprite - vissprites); for (i = vsprcount; i > 0; i--) { 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 for (ds = ds_p; ds-- > firstdrawseg; ) // new -- killough { if (ds->maskedtexturecol != -1 || ds->bFogBoundary) { R_RenderMaskedSegRange (ds, ds->x1, ds->x2); } } // draw the psprites on top of everything but does not draw on side views if (!viewangleoffset) { R_DrawPlayerSprites (); } } // // [RH] Particle functions // // [BC] Allow the maximum number of particles to be specified by a cvar (so people // with lots of nice hardware can have lots of particles!). CUSTOM_CVAR( Int, r_maxparticles, 4000, CVAR_ARCHIVE ) { if ( self == 0 ) self = 4000; else if ( self < 100 ) self = 100; if ( gamestate != GS_STARTUP ) { R_DeinitParticles( ); R_InitParticles( ); } } void R_InitParticles () { char *i; if ((i = Args->CheckValue ("-numparticles"))) NumParticles = atoi (i); // [BC] Use r_maxparticles now. else NumParticles = r_maxparticles; // This should be good, but eh... if ( NumParticles < 100 ) NumParticles = 100; Particles = new particle_t[NumParticles]; R_ClearParticles (); atterm (R_DeinitParticles); } void R_DeinitParticles() { if (Particles != NULL) { delete[] Particles; Particles = NULL; } } void R_ClearParticles () { int i; memset (Particles, 0, NumParticles * sizeof(particle_t)); ActiveParticles = NO_PARTICLE; InactiveParticles = 0; for (i = 0; i < NumParticles-1; i++) Particles[i].tnext = i + 1; Particles[i].tnext = NO_PARTICLE; } // Group particles by subsectors. Because particles are always // in motion, there is little benefit to caching this information // from one frame to the next. void R_FindParticleSubsectors () { if (ParticlesInSubsec.Size() < (size_t)numsubsectors) { ParticlesInSubsec.Reserve (numsubsectors - ParticlesInSubsec.Size()); } clearbufshort (&ParticlesInSubsec[0], numsubsectors, NO_PARTICLE); if (!r_particles) { return; } for (WORD i = ActiveParticles; i != NO_PARTICLE; i = Particles[i].tnext) { subsector_t *ssec = R_PointInSubsector (Particles[i].x, Particles[i].y); int ssnum = ssec-subsectors; Particles[i].subsector = ssec; Particles[i].snext = ParticlesInSubsec[ssnum]; ParticlesInSubsec[ssnum] = i; } } void R_ProjectParticle (particle_t *particle, const sector_t *sector, int shade, int fakeside) { fixed_t tr_x; fixed_t tr_y; fixed_t tx, ty; fixed_t tz, tiz; fixed_t xscale, yscale; int x1, x2, y1, y2; vissprite_t* vis; sector_t* heightsec = NULL; BYTE* map; // transform the origin point tr_x = particle->x - viewx; tr_y = particle->y - viewy; tz = DMulScale20 (tr_x, viewtancos, tr_y, viewtansin); // particle is behind view plane? if (tz < MINZ) return; tx = DMulScale20 (tr_x, viewsin, -tr_y, viewcos); // Flip for mirrors if (MirrorFlags & RF_XFLIP) { tx = viewwidth - tx - 1; } // too far off the side? if (tz <= abs (tx)) return; tiz = 268435456 / tz; xscale = centerx * tiz; // calculate edges of the shape int psize = particle->size << (12-3); x1 = MAX (WindowLeft, (centerxfrac + MulScale12 (tx-psize, xscale)) >> FRACBITS); x2 = MIN (WindowRight, (centerxfrac + MulScale12 (tx+psize, xscale)) >> FRACBITS); if (x1 >= x2) return; yscale = MulScale16 (yaspectmul, xscale); ty = particle->z - viewz; psize <<= 4; y1 = (centeryfrac - FixedMul (ty+psize, yscale)) >> FRACBITS; y2 = (centeryfrac - FixedMul (ty-psize, yscale)) >> FRACBITS; // 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->heightsec; if (heightsec != NULL && heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC) { heightsec = NULL; } 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->z < botplane->ZatPoint (particle->x, particle->y)) return; if (toppic != skyflatnum && particle->z >= topplane->ZatPoint (particle->x, particle->y)) return; // store information in a vissprite vis = R_NewVisSprite (); vis->heightsec = heightsec; vis->xscale = xscale; // vis->yscale = FixedMul (xscale, InvZtoScale); vis->yscale = xscale; vis->depth = tz; vis->idepth = (DWORD)DivScale32 (1, tz) >> 1; vis->cx = tx; vis->gx = particle->x; vis->gy = particle->y; vis->gz = y1; vis->gzt = y2; vis->x1 = x1; vis->x2 = x2; vis->Translation = 0; vis->startfrac = particle->color; vis->pic = NULL; vis->renderflags = particle->trans; vis->FakeFlatStat = fakeside; vis->floorclip = 0; vis->heightsec = heightsec; if (fixedlightlev) { vis->colormap = map + fixedlightlev; } else if (fixedcolormap) { vis->colormap = fixedcolormap; } else { // Using MulScale15 instead of 16 makes particles slightly more visible // than regular sprites. vis->colormap = map + (GETPALOOKUP (MulScale15 (tiz, r_SpriteVisibility), shade) << 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]]; if (ds->x1 >= x2 || ds->x2 < x1) { continue; } if (Scale (ds->siz2 - ds->siz1, (x2 + x1)/2 - ds->sx1, ds->sx2 - ds->sx1) + ds->siz1 < vis->idepth) { R_RenderMaskedSegRange (ds, MAX (ds->x1, x1), MIN (ds->x2, x2-1)); } } } void R_DrawParticle (vissprite_t *vis) { DWORD *bg2rgb; int spacing; BYTE *dest; DWORD fg; BYTE color = vis->colormap[vis->startfrac]; int yl = vis->gz; int ycount = vis->gzt - yl + 1; int x1 = vis->x1; int countbase = vis->x2 - x1 + 1; 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[0][0][bg & (bg>>15)]; } while (--count); dest += spacing; } while (--ycount); }