// "Build Engine & Tools" Copyright (c) 1993-1997 Ken Silverman // Ken Silverman's official web site: "http://www.advsys.net/ken" // See the included license file "BUILDLIC.TXT" for license info. // // This file has been modified from Ken Silverman's original release // by Jonathon Fowler (jf@jonof.id.au) // by the EDuke32 team (development@voidpoint.com) #define engine_c_ #include "gl_load.h" #include "baselayer.h" #include "build.h" #include "imagehelpers.h" #include "common.h" #include "compat.h" #include "engine_priv.h" #include "osd.h" #include "palette.h" #include "pragmas.h" #include "scriptfile.h" #include "gamecvars.h" #include "c_console.h" #include "v_2ddrawer.h" #include "v_draw.h" #include "stats.h" #include "menu.h" #include "version.h" #ifdef USE_OPENGL # include "hightile.h" # include "mdsprite.h" # include "polymost.h" #include "v_video.h" #include "../../glbackend/glbackend.h" #include "gl_renderer.h" #endif ////////// // Compilation switches for optional/extended engine features #if !defined(__arm__) && !defined(GEKKO) # define HIGH_PRECISION_SPRITE #endif #if !defined EDUKE32_TOUCH_DEVICES && !defined GEKKO && !defined __OPENDINGUX__ // Handle absolute z difference of floor/ceiling to camera >= 1<<24. // Also: higher precision view-relative x and y for drawvox(). # define CLASSIC_Z_DIFF_64 #endif #define MULTI_COLUMN_VLINE int32_t mapversion=7; // JBF 20040211: default mapversion to 7 int32_t g_loadedMapVersion = -1; // -1: none (e.g. started new) // Handle nonpow2-ysize walls the old way? static FORCE_INLINE int32_t oldnonpow2(void) { #if !defined CLASSIC_NONPOW2_YSIZE_WALLS return 1; #else return (g_loadedMapVersion < 10); #endif } bool playing_rr; bool playing_blood; int32_t rendmode=0; int32_t glrendmode = REND_POLYMOST; int32_t r_scenebrightness = 0; int32_t r_rortexture = 0; int32_t r_rortexturerange = 0; int32_t r_rorphase = 0; int32_t mdtims, omdtims; uint8_t alphahackarray[MAXTILES]; int32_t polymostcenterhoriz = 100; float fcosglobalang, fsinglobalang; float fxdim, fydim, fydimen, fviewingrange; uint8_t globalr = 255, globalg = 255, globalb = 255; int16_t pskybits_override = -1; // This was on the cache but is permanently allocated, so put it into something static. This needs some rethinking anyway static TArray> voxelmemory; void (*loadvoxel_replace)(int32_t voxindex) = NULL; int16_t tiletovox[MAXTILES]; #ifdef USE_OPENGL char *voxfilenames[MAXVOXELS]; #endif char g_haveVoxels; //#define kloadvoxel loadvoxel int32_t novoxmips = 1; //These variables need to be copied into BUILD #define MAXXSIZ 256 #define MAXYSIZ 256 #define MAXZSIZ 255 int32_t voxscale[MAXVOXELS]; static int32_t beforedrawrooms = 1; static int32_t oxdimen = -1, oviewingrange = -1, oxyaspect = -1; // r_usenewaspect is the cvar, newaspect_enable to trigger the new behaviour in the code CVAR(Bool, r_usenewaspect, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); int32_t newaspect_enable=0; int32_t r_fpgrouscan = 1; int32_t globalflags; static int8_t tempbuf[MAXWALLS]; // referenced from asm int32_t reciptable[2048]; intptr_t asm1, asm2, asm3; int32_t globalx1, globaly2, globalx3, globaly3; static int32_t no_radarang2 = 0; static int16_t radarang[1280]; static int32_t qradarang[10240]; uint16_t ATTRIBUTE((used)) sqrtable[4096], ATTRIBUTE((used)) shlookup[4096+256], ATTRIBUTE((used)) sqrtable_old[2048]; char britable[16][256]; // JBF 20040207: full 8bit precision static char kensmessage[128]; const char *engineerrstr = "No error"; int32_t showfirstwall=0; int32_t showheightindicators=1; int32_t circlewall=-1; int16_t editstatus = 0; static fix16_t global100horiz; // (-100..300)-scale horiz (the one passed to drawrooms) int32_t(*getpalookup_replace)(int32_t davis, int32_t dashade) = NULL; // adapted from build.c static void getclosestpointonwall_internal(vec2_t const p, int32_t const dawall, vec2_t *const closest) { vec2_t const w = wall[dawall].pos; vec2_t const w2 = wall[wall[dawall].point2].pos; vec2_t const d = { w2.x - w.x, w2.y - w.y }; int64_t i = d.x * ((int64_t)p.x - w.x) + d.y * ((int64_t)p.y - w.y); if (i <= 0) { *closest = w; return; } int64_t const j = (int64_t)d.x * d.x + (int64_t)d.y * d.y; if (i >= j) { *closest = w2; return; } i = ((i << 15) / j) << 15; //i = tabledivide64((i << 15), j) << 15; *closest = { (int32_t)(w.x + ((d.x * i) >> 30)), (int32_t)(w.y + ((d.y * i) >> 30)) }; } void faketimerhandler() { } #if !defined YAX_ENABLE # warning Non-TROR builds are supported only for debugging. Expect savegame breakage etc... #endif #ifdef YAX_ENABLE // all references to floor/ceiling bunchnums should be through the // get/set functions! int32_t g_nodraw = 0; int32_t scansector_retfast = 0; int32_t scansector_collectsprites = 1; int32_t yax_globalcf = -1, yax_nomaskpass=0, yax_nomaskdidit; // engine internal int32_t r_tror_nomaskpass = 1; // cvar int32_t yax_globallev = YAX_MAXDRAWS; int32_t yax_globalbunch = -1; int32_t yax_polymostclearzbuffer = 1; // duplicated tsprites // [i]: // i==MAXDRAWS: base level // iMAXDRAWS: i-MAXDRAWS-1 is level towards floor static int16_t yax_spritesortcnt[1 + 2*YAX_MAXDRAWS]; static uint16_t yax_tsprite[1 + 2*YAX_MAXDRAWS][MAXSPRITESONSCREEN]; static uint8_t yax_tsprfrombunch[1 + 2*YAX_MAXDRAWS][MAXSPRITESONSCREEN]; // drawn sectors uint8_t yax_gotsector[(MAXSECTORS+7)>>3]; // engine internal # if !defined NEW_MAP_FORMAT // Game-time YAX data structures, V7-V9 map formats. int16_t yax_bunchnum[MAXSECTORS][2]; int16_t yax_nextwall[MAXWALLS][2]; static FORCE_INLINE int32_t yax_islockededge(int32_t line, int32_t cf) { return !!(wall[line].cstat&(YAX_NEXTWALLBIT(cf))); } #define YAX_PTRBUNCHNUM(Ptr, Sect, Cf) (*(&Ptr[Sect].ceilingxpanning + 8*Cf)) #define YAX_BUNCHNUM(Sect, Cf) YAX_PTRBUNCHNUM(sector, Sect, Cf) //// bunch getters/setters int16_t yax_getbunch(int16_t i, int16_t cf) { if (editstatus==0) return yax_bunchnum[i][cf]; return (*(§or[i].ceilingstat + cf) & YAX_BIT) ? YAX_BUNCHNUM(i, cf) : -1; } # endif // bunchnum: -1: also clear yax-nextwalls (forward and reverse) // -2: don't clear reverse yax-nextwalls // -3: don't clear either forward or reverse yax-nextwalls void yax_setbunch(int16_t i, int16_t cf, int16_t bunchnum) { if (editstatus==0) { yax_bunchnum[i][cf] = bunchnum; return; } if (bunchnum < 0) { if (bunchnum > -3) { // TODO: for in-game too? for (bssize_t ynw, j=sector[i].wallptr; j= 0) { if (bunchnum > -2) yax_setnextwall(ynw, !cf, -1); yax_setnextwall(j, cf, -1); } } } *(§or[i].ceilingstat + cf) &= ~YAX_BIT; // NOTE: Don't reset xpanning-as-index, since we can be called from // e.g. Mapster32's "Inner loop made into new sector" functionality. return; } *(§or[i].ceilingstat + cf) |= YAX_BIT; YAX_BUNCHNUM(i, cf) = bunchnum; } void yax_setbunches(int16_t i, int16_t cb, int16_t fb) { yax_setbunch(i, YAX_CEILING, cb); yax_setbunch(i, YAX_FLOOR, fb); } # if !defined NEW_MAP_FORMAT //// nextwall getters/setters int16_t yax_getnextwall(int16_t wal, int16_t cf) { if (editstatus==0) return yax_nextwall[wal][cf]; return yax_islockededge(wal, cf) ? YAX_NEXTWALL(wal, cf) : -1; } // unchecked! void yax_setnextwall(int16_t wal, int16_t cf, int16_t thenextwall) { if (editstatus==0) { yax_nextwall[wal][cf] = thenextwall; return; } if (thenextwall >= 0) { wall[wal].cstat |= YAX_NEXTWALLBIT(cf); YAX_NEXTWALL(wal, cf) = thenextwall; } else { wall[wal].cstat &= ~YAX_NEXTWALLBIT(cf); YAX_NEXTWALL(wal, cf) = YAX_NEXTWALLDEFAULT(cf); } } # endif // make one step in the vertical direction, and if the wall we arrive at // is red, return its nextsector. int16_t yax_vnextsec(int16_t line, int16_t cf) { int16_t const ynw = yax_getnextwall(line, cf); return (ynw < 0) ? -1 : wall[ynw].nextsector; } //// in-struct --> array transfer (only resetstat==0); list construction // resetstat: 0: reset and read data from structs and construct linked lists etc. // 1: only reset // 2: read data from game-time arrays and construct linked lists etc. void yax_update(int32_t resetstat) { int32_t i; #if !defined NEW_MAP_FORMAT int32_t j; const int32_t oeditstatus=editstatus; #endif int16_t cb, fb; if (resetstat != 2) numyaxbunches = 0; for (i=0; i=numsectors) yax_bunchnum[i][0] = yax_bunchnum[i][1] = -1; #endif nextsectbunch[0][i] = nextsectbunch[1][i] = -1; } for (i=0; i=numwalls) yax_nextwall[i][0] = yax_nextwall[i][1] = -1; #endif if (resetstat==1) return; // Constuct singly linked list of sectors-of-bunch. #if !defined NEW_MAP_FORMAT // Read bunchnums directly from the sector struct in yax_[gs]etbunch{es}! editstatus = (resetstat==0); // NOTE: Use oeditstatus to check for in-gamedness from here on! #endif if (resetstat==0) { // make bunchnums consecutive uint8_t *const havebunch = (uint8_t *)tempbuf; uint8_t *const bunchmap = havebunch + ((YAX_MAXBUNCHES+7)>>3); int32_t dasub = 0; Bmemset(havebunch, 0, (YAX_MAXBUNCHES+7)>>3); for (i=0; i=0) havebunch[cb>>3] |= pow2char[cb&7]; if (fb>=0) havebunch[fb>>3] |= pow2char[fb&7]; } for (i=0; i>3]&pow2char[i&7])==0) { bunchmap[i] = 255; dasub++; continue; } bunchmap[i] = i-dasub; } for (i=0; i=0) yax_setbunch(i, YAX_CEILING, bunchmap[cb]); if (fb>=0) yax_setbunch(i, YAX_FLOOR, bunchmap[fb]); } } // In-struct --> array transfer (resetstat==0 and !defined NEW_MAP_FORMAT) // and list construction. for (i=numsectors-1; i>=0; i--) { yax_getbunches(i, &cb, &fb); #if !defined NEW_MAP_FORMAT if (resetstat==0) { yax_bunchnum[i][0] = cb; yax_bunchnum[i][1] = fb; } #endif if (cb >= 0) { #if !defined NEW_MAP_FORMAT if (resetstat==0) for (j=sector[i].wallptr; j= 0) { #if !defined NEW_MAP_FORMAT if (resetstat==0) for (j=sector[i].wallptr; j>3]; static inline int32_t yax_walldist(int32_t w) { vec2_t closest; getclosestpointonwall_internal({ globalposx, globalposy }, w, &closest); return klabs(closest.x-globalposx) + klabs(closest.y-globalposy); } // calculate distances to bunches and best start-drawing sectors static void yax_scanbunches(int32_t bbeg, int32_t numhere, const uint8_t *lastgotsector) { int32_t bnchcnt, bunchnum, j, k; int32_t startwall, endwall; UNREFERENCED_PARAMETER(lastgotsector); scansector_retfast = 1; scansector_collectsprites = 0; for (bnchcnt=bbeg; bnchcnt=0) if ((ns=wall[w].nextsector)>=0) if ((lastgotsector[ns>>3]&pow2char[ns&7])==0) continue; */ walldist = yax_walldist(j); if (walldist < bestwalldist) { checkthisec = 1; bestwalldist = walldist; } } if (checkthisec) { numscans = numbunches = 0; polymost_scansector(k); if (numbunches > 0) { bestsec = k; bestbestdist = bestwalldist; } } } bunchsec[bunchnum] = bestsec; bunchdist[bunchnum] = bestbestdist; } scansector_collectsprites = 1; scansector_retfast = 0; } static int yax_cmpbunches(const void *b1, const void *b2) { return (bunchdist[B_UNBUF16(b2)] - bunchdist[B_UNBUF16(b1)]); } void yax_tweakpicnums(int32_t bunchnum, int32_t cf, int32_t restore) { // for polymer, this is called before polymer_drawrooms() with restore==0 // and after polymer_drawmasks() with restore==1 int32_t i, dastat; static int16_t opicnum[2][MAXSECTORS]; for (SECTORS_OF_BUNCH(bunchnum, cf, i)) { dastat = (SECTORFLD(i,stat, cf)&(128+256)); // only consider non-masked ceilings/floors if (dastat==0 || (restore==1 && opicnum[cf][i]&0x8000)) { if (!restore) { opicnum[cf][i] = SECTORFLD(i,picnum, cf); if (editstatus && showinvisibility) SECTORFLD(i,picnum, cf) = MAXTILES-1; else //if ((dastat&(128+256))==0) SECTORFLD(i,picnum, cf) = playing_blood ? MAXTILES-2 : 13; //FOF; } else { SECTORFLD(i,picnum, cf) = opicnum[cf][i]; } } } } static void yax_copytsprites() { int32_t i, spritenum, gotthrough, sectnum; int32_t sortcnt = yax_spritesortcnt[yax_globallev]; uspriteptr_t spr; for (i=0; isectnum; if (gotthrough == (MAXSPRITES|(MAXSPRITES<<1))) { if (yax_globalbunch != yax_tsprfrombunch[yax_globallev][i]) continue; } else { int32_t cf = -1; if (gotthrough == MAXSPRITES) cf = YAX_CEILING; // sprite got here through the ceiling of lower sector else if (gotthrough == (MAXSPRITES<<1)) cf = YAX_FLOOR; // sprite got here through the floor of upper sector if (cf != -1) { if ((yax_globallev-YAX_MAXDRAWS)*(-1 + 2*cf) > 0) if (yax_getbunch(sectnum, cf) != yax_globalbunch) continue; sectnum = yax_getneighborsect(spr->x, spr->y, sectnum, cf); if (sectnum < 0) continue; } } if (spritesortcnt >= maxspritesonscreen) break; tspriteptr_t tsp = renderAddTSpriteFromSprite(spritenum); tsp->sectnum = sectnum; // potentially tweak sectnum! } } void yax_preparedrawrooms(void) { if (videoGetRenderMode() == REND_POLYMER || numyaxbunches==0) return; g_nodraw = 1; memset(yax_spritesortcnt, 0, sizeof(yax_spritesortcnt)); memset(haveymost, 0, (numyaxbunches+7)>>3); } void yax_drawrooms(void (*SpriteAnimFunc)(int32_t,int32_t,int32_t,int32_t,int32_t), int16_t sectnum, int32_t didmirror, int32_t smoothr) { static uint8_t havebunch[(YAX_MAXBUNCHES+7)>>3]; const fix16_t horiz = global100horiz; int32_t i, j, k, lev, cf, nmp; int32_t bnchcnt, bnchnum[2] = {0,0}, maxlev[2]; int16_t ourbunch[2] = {-1,-1}, osectnum=sectnum; int32_t bnchbeg[YAX_MAXDRAWS][2], bnchend[YAX_MAXDRAWS][2]; int32_t bbeg, numhere; // original (1st-draw) and accumulated ('per-level') gotsector bitmaps static uint8_t ogotsector[(MAXSECTORS+7)>>3], lgotsector[(MAXSECTORS+7)>>3]; #ifdef YAX_DEBUG uint64_t t; #endif if (videoGetRenderMode() == REND_POLYMER || numyaxbunches==0) { return; } // if we're here, there was just a drawrooms() call with g_nodraw=1 Bmemcpy(ogotsector, gotsector, (numsectors+7)>>3); if (sectnum >= 0) yax_getbunches(sectnum, &ourbunch[0], &ourbunch[1]); Bmemset(&havebunch, 0, (numyaxbunches+7)>>3); // first scan all bunches above, then all below... for (cf=0; cf<2; cf++) { yax_globalcf = cf; if (cf==1) { sectnum = osectnum; Bmemcpy(gotsector, ogotsector, (numsectors+7)>>3); } for (lev=0; /*lev>3]&pow2char[i&7])) continue; j = yax_getbunch(i, cf); if (j >= 0 && !(havebunch[j>>3]&pow2char[j&7])) { if ((SECTORFLD(i,stat, cf)&2) || (cf==0 && globalposz >= sector[i].ceilingz) || (cf==1 && globalposz <= sector[i].floorz)) { havebunch[j>>3] |= pow2char[j&7]; bunches[cf][bnchnum[cf]++] = j; bnchend[lev][cf]++; numhere++; } } } if (numhere > 0) { // found bunches -- need to fake-draw yax_scanbunches(bbeg, numhere, (uint8_t *)gotsector); qsort(&bunches[cf][bbeg], numhere, sizeof(int16_t), &yax_cmpbunches); if (numhere > 1 && lev != YAX_MAXDRAWS-1) Bmemset(lgotsector, 0, sizeof(lgotsector)); for (bnchcnt=bbeg; bnchcnt < bbeg+numhere; bnchcnt++) { j = bunches[cf][bnchcnt]; // the actual bunchnum... yax_globalbunch = j; #ifdef YAX_DEBUG t=timerGetTicksU64(); #endif k = bunchsec[j]; if (k < 0) { yaxprintf("%s, l %d: skipped bunch %d\n", cf?"v":"^", lev, j); continue; } if (lev != YAX_MAXDRAWS-1) { #ifdef YAX_DEBUG int32_t odsprcnt = yax_spritesortcnt[yax_globallev]; #endif // +MAXSECTORS: force renderDrawRoomsQ16(globalposx,globalposy,globalposz,qglobalang,horiz,k+MAXSECTORS); if (numhere > 1) for (i=0; i<(numsectors+7)>>3; i++) lgotsector[i] |= gotsector[i]; yaxdebug("l%d: faked (bn %2d) sec %4d,%3d dspr, ob=[%2d,%2d], sn=%4d, %.3f ms", yax_globallev-YAX_MAXDRAWS, j, k, yax_spritesortcnt[yax_globallev]-odsprcnt, ourbunch[0],ourbunch[1],sectnum, (double)(1000*(timerGetTicksU64()-t))/u64tickspersec); } if (ourbunch[cf]==j) { ourbunch[cf] = yax_getbunch(k, cf); sectnum = k; } } if (numhere > 1 && lev != YAX_MAXDRAWS-1) Bmemcpy(gotsector, lgotsector, (numsectors+7)>>3); } if (numhere==0 || lev==YAX_MAXDRAWS-1) { // no new bunches or max level reached maxlev[cf] = lev - (numhere==0); break; } } } // yax_globalcf = -1; // now comes the real drawing! g_nodraw = 0; scansector_collectsprites = 0; #ifdef USE_OPENGL if (videoGetRenderMode() == REND_POLYMOST) { GLInterface.ClearScreen(0, true); yax_polymostclearzbuffer = 0; } #endif for (cf=0; cf<2; cf++) { yax_globalcf = cf; for (lev=maxlev[cf]; lev>=0; lev--) { yax_globallev = YAX_MAXDRAWS + (-1 + 2*cf)*(lev+1); scansector_collectsprites = (lev == YAX_MAXDRAWS-1); for (bnchcnt=bnchbeg[lev][cf]; bnchcnt=0; nmp--) { yax_nomaskpass = nmp; renderDrawRoomsQ16(globalposx,globalposy,globalposz,qglobalang,horiz,k+MAXSECTORS); // +MAXSECTORS: force if (nmp==1) { yaxdebug("nm1 l%d: DRAWN (bn %2d) sec %4d, %.3f ms", yax_globallev-YAX_MAXDRAWS, j, k, (double)(1000*(timerGetTicksU64()-t))/u64tickspersec); if (!yax_nomaskdidit) { yax_nomaskpass = 0; break; // no need to draw the same stuff twice } Bmemcpy(yax_gotsector, gotsector, (numsectors+7)>>3); } } if (!scansector_collectsprites) spritesortcnt = 0; yax_copytsprites(); yaxdebug("nm0 l%d: DRAWN (bn %2d) sec %4d,%3d tspr, %.3f ms", yax_globallev-YAX_MAXDRAWS, j, k, spritesortcnt, (double)(1000*(timerGetTicksU64()-t))/u64tickspersec); SpriteAnimFunc(globalposx, globalposy, globalposz, globalang, smoothr); renderDrawMasks(); } if (lev < maxlev[cf]) for (bnchcnt=bnchbeg[lev+1][cf]; bnchcnt= 0) for (bnchcnt=bnchbeg[0][cf]; bnchcnt w1f) swaplong(&w1c, &w1f); if (w2c > w2f) swaplong(&w2c, &w2f); // now: c <= f for each "wall-vline" int32_t maxceil = max(w1c, w2c); int32_t minflor = min(w1f, w2f); return minflor-maxceil; } const int16_t *chsecptr_onextwall = NULL; int32_t checksectorpointer(int16_t i, int16_t sectnum) { int32_t startsec, endsec; int32_t j, k, startwall, endwall, x1, y1, x2, y2, numnewwalls=0; int32_t bestnextwall=-1, bestnextsec=-1, bestwallscore=INT32_MIN; int32_t cz[4], fz[4], tmp[2], tmpscore=0; #ifdef YAX_ENABLE int16_t cb[2], fb[2]; #endif x1 = wall[i].x; y1 = wall[i].y; x2 = (wall[wall[i].point2]).x; y2 = (wall[wall[i].point2]).y; k = wall[i].nextwall; if (k >= 0) //Check for early exit { if (WALLS_ARE_CONSISTENT(k)) return 0; wall[k].nextwall = wall[k].nextsector = -1; } if ((unsigned)wall[i].nextsector < (unsigned)numsectors && wall[i].nextwall < 0) { // if we have a nextsector but no nextwall, take this as a hint // to search only the walls of that sector startsec = wall[i].nextsector; endsec = startsec+1; } else { startsec = 0; endsec = numsectors; } wall[i].nextsector = wall[i].nextwall = -1; if (chsecptr_onextwall && (k=chsecptr_onextwall[i])>=0 && wall[k].nextwall<0) { // old next wall found if (WALLS_ARE_CONSISTENT(k)) { j = sectorofwall(k); wall[i].nextsector = j; wall[i].nextwall = k; wall[k].nextsector = sectnum; wall[k].nextwall = i; return 1; } } for (j=startsec; j=0 && wall[k].nextwall != i) continue; #ifdef YAX_ENABLE yax_getbunches(sectnum, &cb[0], &fb[0]); yax_getbunches(j, &cb[1], &fb[1]); if ((cb[0]>=0 && cb[0]==cb[1]) || (fb[0]>=0 && fb[0]==fb[1])) { tmpscore = INT32_MAX; } else #endif { getzsofslope(sectnum, x1,y1, &cz[0],&fz[0]); getzsofslope(sectnum, x2,y2, &cz[1],&fz[1]); getzsofslope(j, x1,y1, &cz[2],&fz[2]); getzsofslope(j, x2,y2, &cz[3],&fz[3]); tmp[0] = getscore(cz[0],fz[0], cz[2],fz[2]); tmp[1] = getscore(cz[1],fz[1], cz[3],fz[3]); if ((tmp[0]^tmp[1]) >= 0) tmpscore = tmp[0]+tmp[1]; else tmpscore = max(tmp[0], tmp[1]); } if (bestnextwall == -1 || tmpscore > bestwallscore) { bestwallscore = tmpscore; bestnextwall = k; bestnextsec = j; } numnewwalls++; } } // sectnum -2 means dry run if (bestnextwall >= 0 && sectnum!=-2) #ifdef YAX_ENABLE // for walls with TROR neighbors, be conservative in case if score <=0 // (meaning that no wall area is mutually visible) -- it could be that // another sector is a better candidate later on if ((yax_getnextwall(i, 0)<0 && yax_getnextwall(i, 1)<0) || bestwallscore>0) #endif { // Printf("w%d new nw=%d (score %d)\n", i, bestnextwall, bestwallscore) wall[i].nextsector = bestnextsec; wall[i].nextwall = bestnextwall; wall[bestnextwall].nextsector = sectnum; wall[bestnextwall].nextwall = i; } return numnewwalls; } #undef WALLS_ARE_CONSISTENT int32_t xb1[MAXWALLSB]; // Polymost uses this as a temp array static int32_t yb1[MAXWALLSB], xb2[MAXWALLSB], yb2[MAXWALLSB]; int32_t rx1[MAXWALLSB], ry1[MAXWALLSB]; static int32_t rx2[MAXWALLSB], ry2[MAXWALLSB]; int16_t bunchp2[MAXWALLSB], thesector[MAXWALLSB]; int16_t bunchfirst[MAXWALLSB], bunchlast[MAXWALLSB]; static vec3_t spritesxyz[MAXSPRITESONSCREEN+1]; int32_t xdimen = -1, xdimenrecip, halfxdimen, xdimenscale, xdimscale; float fxdimen = -1.f; int32_t ydimen; static int32_t nrx1[8], nry1[8], nrx2[8], nry2[8]; // JBF 20031206: Thanks Ken int32_t rxi[8], ryi[8]; static int32_t rzi[8], rxi2[8], ryi2[8], rzi2[8]; static int32_t xsi[8], ysi[8]; int32_t globalposx, globalposy, globalposz, globalhoriz; fix16_t qglobalhoriz; float fglobalposx, fglobalposy, fglobalposz; int16_t globalang, globalcursectnum; fix16_t qglobalang; int32_t globalpal, cosglobalang, singlobalang; int32_t cosviewingrangeglobalang, sinviewingrangeglobalang; static int32_t globaluclip, globaldclip; int32_t globvis, globalvisibility; int32_t globalhisibility, globalpisibility, globalcisibility; #ifdef USE_OPENGL int32_t globvis2, globalvisibility2, globalhisibility2, globalpisibility2, globalcisibility2; #endif //char globparaceilclip, globparaflorclip; int32_t xyaspect; static int32_t viewingrangerecip; static char globalxshift, globalyshift; static int32_t globalxpanning, globalypanning; int32_t globalshade, globalorientation; int16_t globalpicnum; static int16_t globalshiftval; #ifdef HIGH_PRECISION_SPRITE static int64_t globalzd; #else static int32_t globalzd; #endif static int32_t globalyscale; static int32_t globalxspan, globalyspan, globalispow2=1; // true if texture has power-of-two x and y size static intptr_t globalbufplc; static int32_t globaly1, globalx2; int16_t sectorborder[256]; int32_t ydim16, qsetmode = 0; int16_t pointhighlight=-1, linehighlight=-1, highlightcnt=0; int32_t halfxdim16, midydim16; EDUKE32_STATIC_ASSERT(MAXWALLSB < INT16_MAX); int16_t numscans, numbunches; static int16_t numhits; int16_t searchit; int32_t searchx = -1, searchy; //search input int16_t searchsector, searchwall, searchstat; //search output // SEARCHBOTTOMWALL: // When aiming at a the bottom part of a 2-sided wall whose bottom part // is swapped (.cstat&2), searchbottomwall equals that wall's .nextwall. In all // other cases (when aiming at a wall), searchbottomwall equals searchwall. // // SEARCHISBOTTOM: // When aiming at a 2-sided wall, 1 if aiming at the bottom part, 0 else int16_t searchbottomwall, searchisbottom; char inpreparemirror = 0; static int32_t mirrorsx1, mirrorsy1, mirrorsx2, mirrorsy2; #define MAXSETVIEW 4 #ifdef GAMENAME char apptitle[256] = GAMENAME; #else char apptitle[256] = "Build Engine"; #endif // // Internal Engine Functions // // returns: 0=continue sprite collecting; // 1=break out of sprite collecting; int32_t renderAddTsprite(int16_t z, int16_t sectnum) { auto const spr = (uspriteptr_t)&sprite[z]; #ifdef YAX_ENABLE if (g_nodraw==0) { if (numyaxbunches==0) { #endif if (spritesortcnt >= maxspritesonscreen) return 1; renderAddTSpriteFromSprite(z); #ifdef YAX_ENABLE } } else if (yax_nomaskpass==0) { int16_t *sortcnt = &yax_spritesortcnt[yax_globallev]; if (*sortcnt >= maxspritesonscreen) return 1; yax_tsprite[yax_globallev][*sortcnt] = z; if (yax_globalbunch >= 0) { yax_tsprite[yax_globallev][*sortcnt] |= (MAXSPRITES|(MAXSPRITES<<1)); yax_tsprfrombunch[yax_globallev][*sortcnt] = yax_globalbunch; } (*sortcnt)++; // now check whether the tsprite needs duplication into another level if ((spr->cstat&48)==32) return 0; int16_t cb, fb; yax_getbunches(sectnum, &cb, &fb); if (cb < 0 && fb < 0) return 0; int32_t spheight; int16_t spzofs = spriteheightofs(z, &spheight, 1); // TODO: get*zofslope? if (cb>=0 && spr->z+spzofs-spheight < sector[sectnum].ceilingz) { sortcnt = &yax_spritesortcnt[yax_globallev-1]; if (*sortcnt < maxspritesonscreen) { yax_tsprite[yax_globallev-1][*sortcnt] = z|MAXSPRITES; (*sortcnt)++; } } if (fb>=0 && spr->z+spzofs > sector[sectnum].floorz) { sortcnt = &yax_spritesortcnt[yax_globallev+1]; if (*sortcnt < maxspritesonscreen) { yax_tsprite[yax_globallev+1][*sortcnt] = z|(MAXSPRITES<<1); (*sortcnt)++; } } } #endif return 0; } static FORCE_INLINE vec2_t get_rel_coords(int32_t const x, int32_t const y) { return { dmulscale6(y, cosglobalang, -x, singlobalang), dmulscale6(x, cosviewingrangeglobalang, y, sinviewingrangeglobalang) }; } // Note: the returned y coordinates are not actually screen coordinates, but // potentially clipped player-relative y coordinates. static int get_screen_coords(const vec2_t &p1, const vec2_t &p2, int32_t *sx1ptr, int32_t *sy1ptr, int32_t *sx2ptr, int32_t *sy2ptr) { int32_t sx1, sy1, sx2, sy2; // First point. if (p1.x >= -p1.y) { if (p1.x > p1.y || p1.y == 0) return 0; sx1 = halfxdimen + scale(p1.x, halfxdimen, p1.y) + (p1.x >= 0); // Fix for SIGNED divide if (sx1 >= xdimen) sx1 = xdimen-1; sy1 = p1.y; } else { if (p2.x < -p2.y) return 0; sx1 = 0; int32_t tempint = (p1.x + p1.y) - (p2.x + p2.y); if (tempint == 0) return 0; sy1 = p1.y + scale(p2.y-p1.y, p1.x+p1.y, tempint); } if (sy1 < 256) return 0; // Second point. if (p2.x <= p2.y) { if (p2.x < -p2.y || p2.y == 0) return 0; sx2 = halfxdimen + scale(p2.x,halfxdimen,p2.y) - 1 + (p2.x >= 0); // Fix for SIGNED divide if (sx2 >= xdimen) sx2 = xdimen-1; sy2 = p2.y; } else { if (p1.x > p1.y) return 0; sx2 = xdimen-1; int32_t const tempint = (p1.y - p1.x) + (p2.x - p2.y); sy2 = p1.y + scale(p2.y-p1.y, p1.y-p1.x, tempint); } if (sy2 < 256 || sx1 > sx2) return 0; *sx1ptr = sx1; *sy1ptr = sy1; *sx2ptr = sx2; *sy2ptr = sy2; return 1; } // // wallfront (internal) // int32_t wallfront(int32_t l1, int32_t l2) { vec2_t const l1vect = wall[thewall[l1]].pos; vec2_t const l1p2vect = wall[wall[thewall[l1]].point2].pos; vec2_t const l2vect = wall[thewall[l2]].pos; vec2_t const l2p2vect = wall[wall[thewall[l2]].point2].pos; vec2_t d = { l1p2vect.x - l1vect.x, l1p2vect.y - l1vect.y }; int32_t t1 = dmulscale2(l2vect.x-l1vect.x, d.y, -d.x, l2vect.y-l1vect.y); //p1(l2) vs. l1 int32_t t2 = dmulscale2(l2p2vect.x-l1vect.x, d.y, -d.x, l2p2vect.y-l1vect.y); //p2(l2) vs. l1 if (t1 == 0) { if (t2 == 0) return -1; t1 = t2; } if (t2 == 0) t2 = t1; if ((t1^t2) >= 0) //pos vs. l1 return (dmulscale2(globalposx-l1vect.x, d.y, -d.x, globalposy-l1vect.y) ^ t1) >= 0; d.x = l2p2vect.x-l2vect.x; d.y = l2p2vect.y-l2vect.y; t1 = dmulscale2(l1vect.x-l2vect.x, d.y, -d.x, l1vect.y-l2vect.y); //p1(l1) vs. l2 t2 = dmulscale2(l1p2vect.x-l2vect.x, d.y, -d.x, l1p2vect.y-l2vect.y); //p2(l1) vs. l2 if (t1 == 0) { if (t2 == 0) return -1; t1 = t2; } if (t2 == 0) t2 = t1; if ((t1^t2) >= 0) //pos vs. l2 return (dmulscale2(globalposx-l2vect.x,d.y,-d.x,globalposy-l2vect.y) ^ t1) < 0; return -2; } // // spritewallfront (internal) // static inline int32_t spritewallfront(tspritetype const * const s, int32_t w) { auto const wal = (uwallptr_t)&wall[w]; auto const wal2 = (uwallptr_t)&wall[wal->point2]; const vec2_t v = { wal->x, wal->y }; return dmulscale32(wal2->x - v.x, s->y - v.y, -(s->x - v.x), wal2->y - v.y) >= 0; } // // bunchfront (internal) // static inline int32_t bunchfront(int32_t b1, int32_t b2) { int b1f = bunchfirst[b1]; int const x1b1 = xb1[b1f]; int const x2b2 = xb2[bunchlast[b2]] + 1; if (x1b1 >= x2b2) return -1; int b2f = bunchfirst[b2]; int const x1b2 = xb1[b2f]; int const x2b1 = xb2[bunchlast[b1]] + 1; if (x1b2 >= x2b1) return -1; if (x1b1 >= x1b2) { for (; xb2[b2f] < x1b1; b2f = bunchp2[b2f]) { } return wallfront(b1f, b2f); } for (; xb2[b1f] < x1b2; b1f = bunchp2[b1f]) { } return wallfront(b1f, b2f); } // // animateoffs (internal) // int32_t (*animateoffs_replace)(int const tilenum, int fakevar) = NULL; int32_t animateoffs(int const tilenum, int fakevar) { if (animateoffs_replace) { return animateoffs_replace(tilenum, fakevar); } int const animnum = picanm[tilenum].num; if (animnum <= 0) return 0; int const i = (int) totalclocklock >> (picanm[tilenum].sf & PICANM_ANIMSPEED_MASK); int offs = 0; switch (picanm[tilenum].sf & PICANM_ANIMTYPE_MASK) { case PICANM_ANIMTYPE_OSC: { int k = (i % (animnum << 1)); offs = (k < animnum) ? k : (animnum << 1) - k; } break; case PICANM_ANIMTYPE_FWD: offs = i % (animnum + 1); break; case PICANM_ANIMTYPE_BACK: offs = -(i % (animnum + 1)); break; } return offs; } static inline void wallmosts_finish(int16_t *mostbuf, int32_t z1, int32_t z2, int32_t ix1, int32_t iy1, int32_t ix2, int32_t iy2) { const int32_t y = scale(z1, xdimenscale, iy1)<<4; // PK 20110423: a bit consistency checking is a good thing: int32_t const tmp = (ix2 - ix1 >= 0) ? (ix2 - ix1 + 1) : 1; int32_t const yinc = tabledivide32((scale(z2, xdimenscale, iy2) << 4) - y, tmp); qinterpolatedown16short((intptr_t)&mostbuf[ix1], tmp, y + (globalhoriz << 16), yinc); mostbuf[ix1] = clamp(mostbuf[ix1], 0, ydimen); mostbuf[ix2] = clamp(mostbuf[ix2], 0, ydimen); } // globalpicnum --> globalxshift, globalyshift static void calc_globalshifts(void) { globalxshift = (8-(picsiz[globalpicnum]&15)); globalyshift = (8-(picsiz[globalpicnum]>>4)); if (globalorientation&8) { globalxshift++; globalyshift++; } // PK: the following can happen for large (>= 512) tile sizes. // NOTE that global[xy]shift are unsigned chars. if (globalxshift > 31) globalxshift=0; if (globalyshift > 31) globalyshift=0; } static void renderDrawSprite(int32_t snum) { polymost_drawsprite(snum); } // // drawmaskwall (internal) // static void renderDrawMaskedWall(int16_t damaskwallcnt) { if (videoGetRenderMode() == REND_POLYMOST) { polymost_drawmaskwall(damaskwallcnt); return; } } static uint32_t msqrtasm(uint32_t c) { uint32_t a = 0x40000000l, b = 0x20000000l; do { if (c >= a) { c -= a; a += b*4; } a -= b; a >>= 1; b >>= 2; } while (b); if (c >= a) a++; return a >> 1; } // // initksqrt (internal) // static inline void initksqrt(void) { int32_t i, j, k; uint32_t root, num; int32_t temp; j = 1; k = 0; for (i=0; i<4096; i++) { if (i >= j) { j <<= 2; k++; } sqrtable[i] = (uint16_t)(msqrtasm((i<<18)+131072)<<1); shlookup[i] = (k<<1)+((10-k)<<8); if (i < 256) shlookup[i+4096] = ((k+6)<<1)+((10-(k+6))<<8); } for(i=0;i<2048;i++) { root = 128; num = i<<20; do { temp = root; root = (root+num/root)>>1; } while((temp-root+1) > 2); temp = root*root-num; while (klabs(int32_t(temp-2*root+1)) < klabs(temp)) { temp += -(2*root)+1; root--; } while (klabs(int32_t(temp+2*root+1)) < klabs(temp)) { temp += 2*root+1; root++; } sqrtable_old[i] = root; } } // // dosetaspect // static void dosetaspect(void) { int32_t i, j; if (xyaspect != oxyaspect) { oxyaspect = xyaspect; j = xyaspect*320; } if (xdimen != oxdimen || viewingrange != oviewingrange) { int32_t k, x, xinc; no_radarang2 = 0; oviewingrange = viewingrange; xinc = mulscale32(viewingrange*2560,xdimenrecip); x = (5120<<16)-mulscale1(xinc,xdimen); for (i=0; i>16); x += xinc; if (k < 0 || k >= (int32_t)ARRAY_SIZE(qradarang)-1) { no_radarang2 = 1; break; } if (j != 0) j = mulscale16(qradarang[k+1]-qradarang[k], j); } oxdimen = xdimen; } } // // loadtables (internal) // static inline void calcbritable(void) { int32_t i, j; float a, b; for (i=0; i<16; i++) { a = 8.f / ((float)i+8.f); b = 255.f / powf(255.f, a); for (j=0; j<256; j++) // JBF 20040207: full 8bit precision britable[i][j] = (uint8_t) (powf((float)j, a) * b); } } static int32_t engineLoadTables(void) { static char tablesloaded = 0; if (tablesloaded == 0) { int32_t i; initksqrt(); for (i=0; i<2048; i++) reciptable[i] = divscale30(2048, i+2048); for (i=0; i<=512; i++) sintable[i] = (int16_t)(16384.f * sinf((float)i * BANG2RAD) + 0.0001f); for (i=513; i<1024; i++) sintable[i] = sintable[1024-i]; for (i=1024; i<2048; i++) sintable[i] = -sintable[i-1024]; for (i=0; i<640; i++) radarang[i] = (int16_t)(atanf(((float)(640-i)-0.5f) * (1.f/160.f)) * (-64.f * (1.f/BANG2RAD)) + 0.0001f); for (i=0; i<640; i++) radarang[1279-i] = -radarang[i]; for (i=0; i<5120; i++) qradarang[i] = fix16_from_float(atanf(((float)(5120-i)-0.5f) * (1.f/1280.f)) * (-64.f * (1.f/BANG2RAD))); for (i=0; i<5120; i++) qradarang[10239-i] = -qradarang[i]; calcbritable(); tablesloaded = 1; } return 0; } ////////// SPRITE LIST MANIPULATION FUNCTIONS ////////// #ifdef NETCODE_DISABLE # define LISTFN_STATIC static #else # define LISTFN_STATIC #endif ///// sector lists of sprites ///// // insert sprite at the head of sector list, change .sectnum LISTFN_STATIC void do_insertsprite_at_headofsect(int16_t spritenum, int16_t sectnum) { int16_t const ohead = headspritesect[sectnum]; prevspritesect[spritenum] = -1; nextspritesect[spritenum] = ohead; if (ohead >= 0) prevspritesect[ohead] = spritenum; headspritesect[sectnum] = spritenum; sprite[spritenum].sectnum = sectnum; } // remove sprite 'deleteme' from its sector list LISTFN_STATIC void do_deletespritesect(int16_t deleteme) { int32_t const sectnum = sprite[deleteme].sectnum; int32_t const prev = prevspritesect[deleteme]; int32_t const next = nextspritesect[deleteme]; if (headspritesect[sectnum] == deleteme) headspritesect[sectnum] = next; if (prev >= 0) nextspritesect[prev] = next; if (next >= 0) prevspritesect[next] = prev; } ///// now, status lists ///// // insert sprite at head of status list, change .statnum LISTFN_STATIC void do_insertsprite_at_headofstat(int16_t spritenum, int16_t statnum) { int16_t const ohead = headspritestat[statnum]; prevspritestat[spritenum] = -1; nextspritestat[spritenum] = ohead; if (ohead >= 0) prevspritestat[ohead] = spritenum; headspritestat[statnum] = spritenum; sprite[spritenum].statnum = statnum; } // insertspritestat (internal) LISTFN_STATIC int32_t insertspritestat(int16_t statnum) { if ((statnum >= MAXSTATUS) || (headspritestat[MAXSTATUS] == -1)) return -1; //list full // remove one sprite from the statnum-freelist int16_t const blanktouse = headspritestat[MAXSTATUS]; headspritestat[MAXSTATUS] = nextspritestat[blanktouse]; // make back-link of the new freelist head point to nil if (headspritestat[MAXSTATUS] >= 0) prevspritestat[headspritestat[MAXSTATUS]] = -1; else if (enginecompatibility_mode == ENGINECOMPATIBILITY_NONE) tailspritefree = -1; do_insertsprite_at_headofstat(blanktouse, statnum); return blanktouse; } // remove sprite 'deleteme' from its status list LISTFN_STATIC void do_deletespritestat(int16_t deleteme) { int32_t const sectnum = sprite[deleteme].statnum; int32_t const prev = prevspritestat[deleteme]; int32_t const next = nextspritestat[deleteme]; if (headspritestat[sectnum] == deleteme) headspritestat[sectnum] = next; if (prev >= 0) nextspritestat[prev] = next; if (next >= 0) prevspritestat[next] = prev; } // // insertsprite // int32_t(*insertsprite_replace)(int16_t sectnum, int16_t statnum) = NULL; int32_t insertsprite(int16_t sectnum, int16_t statnum) { if (insertsprite_replace) return insertsprite_replace(sectnum, statnum); // TODO: guard against bad sectnum? int32_t const newspritenum = insertspritestat(statnum); if (newspritenum >= 0) { Bassert((unsigned)sectnum < MAXSECTORS); do_insertsprite_at_headofsect(newspritenum, sectnum); Numsprites++; } return newspritenum; } // // deletesprite // int32_t (*deletesprite_replace)(int16_t spritenum) = NULL; int32_t deletesprite(int16_t spritenum) { if (deletesprite_replace) return deletesprite_replace(spritenum); Bassert((sprite[spritenum].statnum == MAXSTATUS) == (sprite[spritenum].sectnum == MAXSECTORS)); if (sprite[spritenum].statnum == MAXSTATUS) return -1; // already not in the world do_deletespritestat(spritenum); do_deletespritesect(spritenum); // (dummy) insert at tail of sector freelist, compat // for code that checks .sectnum==MAXSECTOR sprite[spritenum].sectnum = MAXSECTORS; // insert at tail of status freelist if (enginecompatibility_mode != ENGINECOMPATIBILITY_NONE) do_insertsprite_at_headofstat(spritenum, MAXSTATUS); else { prevspritestat[spritenum] = tailspritefree; nextspritestat[spritenum] = -1; if (tailspritefree >= 0) nextspritestat[tailspritefree] = spritenum; else headspritestat[MAXSTATUS] = spritenum; sprite[spritenum].statnum = MAXSTATUS; tailspritefree = spritenum; } Numsprites--; return 0; } // // changespritesect // int32_t (*changespritesect_replace)(int16_t spritenum, int16_t newsectnum) = NULL; int32_t changespritesect(int16_t spritenum, int16_t newsectnum) { if (changespritesect_replace) return changespritesect_replace(spritenum, newsectnum); // XXX: NOTE: MAXSECTORS is allowed if ((newsectnum < 0 || newsectnum > MAXSECTORS) || (sprite[spritenum].sectnum == MAXSECTORS)) return -1; if (sprite[spritenum].sectnum == newsectnum) return 0; do_deletespritesect(spritenum); do_insertsprite_at_headofsect(spritenum, newsectnum); return 0; } // // changespritestat // int32_t (*changespritestat_replace)(int16_t spritenum, int16_t newstatnum) = NULL; int32_t changespritestat(int16_t spritenum, int16_t newstatnum) { if (changespritestat_replace) return changespritestat_replace(spritenum, newstatnum); // XXX: NOTE: MAXSTATUS is allowed if ((newstatnum < 0 || newstatnum > MAXSTATUS) || (sprite[spritenum].statnum == MAXSTATUS)) return -1; // can't set the statnum of a sprite not in the world if (sprite[spritenum].statnum == newstatnum) return 0; // sprite already has desired statnum do_deletespritestat(spritenum); do_insertsprite_at_headofstat(spritenum, newstatnum); return 0; } // // lintersect (internal) // int32_t lintersect(const int32_t originX, const int32_t originY, const int32_t originZ, const int32_t destX, const int32_t destY, const int32_t destZ, const int32_t lineStartX, const int32_t lineStartY, const int32_t lineEndX, const int32_t lineEndY, int32_t *intersectionX, int32_t *intersectionY, int32_t *intersectionZ) { const vec2_t ray = { destX-originX, destY-originY }; const vec2_t lineVec = { lineEndX-lineStartX, lineEndY-lineStartY }; const vec2_t originDiff = { lineStartX-originX, lineStartY-originY }; const int32_t rayCrossLineVec = ray.x*lineVec.y - ray.y*lineVec.x; const int32_t originDiffCrossRay = originDiff.x*ray.y - originDiff.y*ray.x; if (rayCrossLineVec == 0) { if (originDiffCrossRay != 0 || enginecompatibility_mode != ENGINECOMPATIBILITY_NONE) { // line segments are parallel return 0; } // line segments are collinear const int32_t rayLengthSquared = ray.x*ray.x + ray.y*ray.y; const int32_t rayDotOriginDiff = ray.x*originDiff.x + ray.y*originDiff.y; const int32_t rayDotLineEndDiff = rayDotOriginDiff + ray.x*lineVec.x + ray.y*lineVec.y; int64_t t = min(rayDotOriginDiff, rayDotLineEndDiff); if (rayDotOriginDiff < 0) { if (rayDotLineEndDiff < 0) return 0; t = 0; } else if (rayDotOriginDiff > rayLengthSquared) { if (rayDotLineEndDiff > rayLengthSquared) return 0; t = rayDotLineEndDiff; } t = tabledivide64(t << 24L, rayLengthSquared); *intersectionX = originX + mulscale24(ray.x, t); *intersectionY = originY + mulscale24(ray.y, t); *intersectionZ = originZ + mulscale24(destZ-originZ, t); return 1; } const int32_t originDiffCrossLineVec = originDiff.x*lineVec.y - originDiff.y*lineVec.x; static const int32_t signBit = 1u<<31u; // Any point on either line can be expressed as p+t*r and q+u*s // The two line segments intersect when we can find a t & u such that p+t*r = q+u*s // If the point is outside of the bounds of the line segment, we know we don't have an intersection. // t is < 0 if (originDiffCrossLineVec^rayCrossLineVec) & signBit) // u is < 0 if (originDiffCrossRay^rayCrossLineVec) & signBit // t is > 1 if klabs(originDiffCrossLineVec) > klabs(rayCrossLineVec) // u is > 1 if klabs(originDiffCrossRay) > klabs(rayCrossLineVec) // where int32_t u = tabledivide64(((int64_t) originDiffCrossRay) << 24L, rayCrossLineVec); if (((originDiffCrossLineVec^rayCrossLineVec) & signBit) || ((originDiffCrossRay^rayCrossLineVec) & signBit) || klabs(originDiffCrossLineVec) > klabs(rayCrossLineVec) || klabs(originDiffCrossRay) > klabs(rayCrossLineVec)) { // line segments do not overlap return 0; } int64_t t = tabledivide64(((int64_t) originDiffCrossLineVec) << 24L, rayCrossLineVec); // For sake of completeness/readability, alternative to the above approach for an early out & avoidance of an extra division: *intersectionX = originX + mulscale24(ray.x, t); *intersectionY = originY + mulscale24(ray.y, t); *intersectionZ = originZ + mulscale24(destZ-originZ, t); return 1; } // // rintersect (internal) // // returns: -1 if didn't intersect, coefficient (x3--x4 fraction)<<16 else int32_t rintersect_old(int32_t x1, int32_t y1, int32_t z1, int32_t vx, int32_t vy, int32_t vz, int32_t x3, int32_t y3, int32_t x4, int32_t y4, int32_t *intx, int32_t *inty, int32_t *intz) { //p1 towards p2 is a ray int32_t const x34=x3-x4, y34=y3-y4; int32_t const x31=x3-x1, y31=y3-y1; int32_t const bot = vx*y34 - vy*x34; int32_t const topt = x31*y34 - y31*x34; if (bot == 0) return -1; int32_t const topu = vx*y31 - vy*x31; if (bot > 0 && (topt < 0 || topu < 0 || topu >= bot)) return -1; else if (bot < 0 && (topt > 0 || topu > 0 || topu <= bot)) return -1; int32_t t = divscale16(topt, bot); *intx = x1 + mulscale16(vx, t); *inty = y1 + mulscale16(vy, t); *intz = z1 + mulscale16(vz, t); t = divscale16(topu, bot); return t; } int32_t rintersect(int32_t x1, int32_t y1, int32_t z1, int32_t vx, int32_t vy, int32_t vz, int32_t x3, int32_t y3, int32_t x4, int32_t y4, int32_t *intx, int32_t *inty, int32_t *intz) { //p1 towards p2 is a ray if (enginecompatibility_mode != ENGINECOMPATIBILITY_NONE) return rintersect_old(x1,y1,z1,vx,vy,vz,x3,y3,x4,y4,intx,inty,intz); int64_t const x34=x3-x4, y34=y3-y4; int64_t const x31=x3-x1, y31=y3-y1; int64_t const bot = vx*y34 - vy*x34; int64_t const topt = x31*y34 - y31*x34; if (bot == 0) return -1; int64_t const topu = vx*y31 - vy*x31; if (bot > 0 && (topt < 0 || topu < 0 || topu >= bot)) return -1; else if (bot < 0 && (topt > 0 || topu > 0 || topu <= bot)) return -1; int64_t t = tabledivide64_noinline(topt<<16, bot); *intx = x1 + ((vx*t)>>16); *inty = y1 + ((vy*t)>>16); *intz = z1 + ((vz*t)>>16); t = tabledivide64_noinline(topu<<16, bot); Bassert((unsigned)t < 65536); return t; } int32_t rayintersect(int32_t x1, int32_t y1, int32_t z1, int32_t vx, int32_t vy, int32_t vz, int32_t x3, int32_t y3, int32_t x4, int32_t y4, int32_t *intx, int32_t *inty, int32_t *intz) { return (rintersect(x1, y1, z1, vx, vy, vz, x3, y3, x4, y4, intx, inty, intz) != -1); } // // multi-pskies // psky_t * tileSetupSky(int32_t const tilenum) { for (bssize_t i = 0; i < pskynummultis; i++) if (multipskytile[i] == tilenum) return &multipsky[i]; int32_t const newPskyID = pskynummultis++; multipsky = (psky_t *)Xrealloc(multipsky, pskynummultis * sizeof(psky_t)); multipskytile = (int32_t *)Xrealloc(multipskytile, pskynummultis * sizeof(int32_t)); psky_t * const newPsky = &multipsky[newPskyID]; Bmemset(newPsky, 0, sizeof(psky_t)); multipskytile[newPskyID] = tilenum; newPsky->yscale = 65536; return newPsky; } // // preinitengine // static int32_t preinitcalled = 0; #if !defined DEBUG_MAIN_ARRAYS static spriteext_t spriteext_s[MAXSPRITES+MAXUNIQHUDID]; static spritesmooth_t spritesmooth_s[MAXSPRITES+MAXUNIQHUDID]; static sectortype sector_s[MAXSECTORS + M32_FIXME_SECTORS]; static walltype wall_s[MAXWALLS + M32_FIXME_WALLS]; #ifndef NEW_MAP_FORMAT static wallext_t wallext_s[MAXWALLS]; #endif static spritetype sprite_s[MAXSPRITES]; static tspritetype tsprite_s[MAXSPRITESONSCREEN]; #endif int32_t enginePreInit(void) { polymost_initosdfuncs(); initdivtables(); #if !defined DEBUG_MAIN_ARRAYS sector = sector_s; wall = wall_s; # ifndef NEW_MAP_FORMAT wallext = wallext_s; # endif sprite = sprite_s; tsprite = tsprite_s; spriteext = spriteext_s; spritesmooth = spritesmooth_s; #endif #ifdef HAVE_CLIPSHAPE_FEATURE engineInitClipMaps(); #endif preinitcalled = 1; return 0; } // // initengine // int32_t engineInit(void) { int32_t i; if (!preinitcalled) { i = enginePreInit(); if (i) return i; } if (engineLoadTables()) return 1; xyaspect = -1; rotatesprite_y_offset = 0; rotatesprite_yxaspect = 65536; showinvisibility = 0; voxelmemory.Reset(); for (i=0; i 1 <-> 2 <-> ... <-> MAXSPRITES-1 -> nil // // That is, the dummy MAXSTATUS statnum has all sprites. for (i=0; i>1); globalhoriz = fix16_to_int(qglobalhoriz); globaluclip = (0-globalhoriz)*xdimscale; globaldclip = (ydimen-globalhoriz)*xdimscale; i = mulscale16(xdimenscale,viewingrangerecip); globalpisibility = mulscale16(parallaxvisibility,i); globalvisibility = g_visibility * xdimen; globalvisibility2 = mulscale16(g_visibility, i); globalhisibility = mulscale16(globalvisibility,xyaspect); globalcisibility = mulscale8(globalhisibility,320); #ifdef USE_OPENGL globalhisibility2 = mulscale16(globalvisibility2,xyaspect); globalcisibility2 = mulscale8(globalhisibility2,320); #endif globalcursectnum = dacursectnum; totalclocklock = totalclock; if ((xyaspect != oxyaspect) || (xdimen != oxdimen) || (viewingrange != oviewingrange)) dosetaspect(); Bmemset(gotsector, 0, sizeof(gotsector)); if (videoGetRenderMode() != REND_CLASSIC #ifdef YAX_ENABLE || yax_globallev==YAX_MAXDRAWS #endif ) { i = xdimen-1; } for (int i = 0; i < numwalls; ++i) { if (wall[i].cstat & CSTAT_WALL_ROTATE_90) { auto &w = wall[i]; auto &tile = RotTile(w.picnum+animateoffs(w.picnum,16384)); if (tile.newtile == -1 && tile.owner == -1) { auto owner = w.picnum + animateoffs(w.picnum, 16384); tile.newtile = TileFiles.tileCreateRotated(owner); Bassert(tile.newtile != -1); RotTile(tile.newtile).owner = w.picnum+animateoffs(w.picnum,16384); } } } // Update starting sector number (common to classic and Polymost). // ADJUST_GLOBALCURSECTNUM. if (globalcursectnum >= MAXSECTORS) globalcursectnum -= MAXSECTORS; else { i = globalcursectnum; updatesector(globalposx,globalposy,&globalcursectnum); if (globalcursectnum < 0) globalcursectnum = i; // PK 20110123: I'm not sure what the line above is supposed to do, but 'i' // *can* be negative, so let's just quit here in that case... if (globalcursectnum<0) return 0; } polymost_drawrooms(); return inpreparemirror; } // UTILITY TYPES AND FUNCTIONS FOR DRAWMASKS OCCLUSION TREE // typedef struct s_maskleaf // { // int32_t index; // _point2d p1, p2; // _equation maskeq, p1eq, p2eq; // struct s_maskleaf* branch[MAXWALLSB]; // int32_t drawing; // } _maskleaf; // // _maskleaf maskleaves[MAXWALLSB]; // returns equation of a line given two points static inline _equation equation(float const x1, float const y1, float const x2, float const y2) { const float f = x2-x1; // vertical if (f == 0.f) return { 1, 0, -x1 }; else { float const ff = (y2 - y1) / f; return { ff, -1, (y1 - (ff * x1)) }; } } int32_t wallvisible(int32_t const x, int32_t const y, int16_t const wallnum) { // 1 if wall is in front of player 0 otherwise auto w1 = (uwallptr_t)&wall[wallnum]; auto w2 = (uwallptr_t)&wall[w1->point2]; int32_t const a1 = getangle(w1->x - x, w1->y - y); int32_t const a2 = getangle(w2->x - x, w2->y - y); return (((a2 + (2048 - a1)) & 2047) <= 1024); } #if 0 // returns the intersection point between two lines _point2d intersection(_equation eq1, _equation eq2) { _point2d ret; float det; det = (float)(1) / (eq1.a*eq2.b - eq2.a*eq1.b); ret.x = ((eq1.b*eq2.c - eq2.b*eq1.c) * det); ret.y = ((eq2.a*eq1.c - eq1.a*eq2.c) * det); return ret; } // check if a point that's on the line is within the segment boundaries int32_t pointonmask(_point2d point, _maskleaf* wall) { if ((min(wall->p1.x, wall->p2.x) <= point.x) && (point.x <= max(wall->p1.x, wall->p2.x)) && (min(wall->p1.y, wall->p2.y) <= point.y) && (point.y <= max(wall->p1.y, wall->p2.y))) return 1; return 0; } // returns 1 if wall2 is hidden by wall1 int32_t wallobstructswall(_maskleaf* wall1, _maskleaf* wall2) { _point2d cross; cross = intersection(wall2->p1eq, wall1->maskeq); if (pointonmask(cross, wall1)) return 1; cross = intersection(wall2->p2eq, wall1->maskeq); if (pointonmask(cross, wall1)) return 1; cross = intersection(wall1->p1eq, wall2->maskeq); if (pointonmask(cross, wall2)) return 1; cross = intersection(wall1->p2eq, wall2->maskeq); if (pointonmask(cross, wall2)) return 1; return 0; } // recursive mask drawing function static inline void drawmaskleaf(_maskleaf* wall) { int32_t i; wall->drawing = 1; i = 0; while (wall->branch[i] != NULL) { if (wall->branch[i]->drawing == 0) { //Printf("Drawing parent of %i : mask %i\n", wall->index, wall->branch[i]->index); drawmaskleaf(wall->branch[i]); } i++; } //Printf("Drawing mask %i\n", wall->index); drawmaskwall(wall->index); } #endif static inline int32_t sameside(const _equation *eq, const vec2f_t *p1, const vec2f_t *p2) { const float sign1 = (eq->a * p1->x) + (eq->b * p1->y) + eq->c; const float sign2 = (eq->a * p2->x) + (eq->b * p2->y) + eq->c; return (sign1 * sign2) > 0.f; } // x1, y1: in/out // rest x/y: out #ifdef DEBUG_MASK_DRAWING int32_t g_maskDrawMode = 0; #endif static inline int comparetsprites(int const k, int const l) { #ifdef USE_OPENGL if (videoGetRenderMode() == REND_POLYMOST) { if ((tspriteptr[k]->cstat & 48) != (tspriteptr[l]->cstat & 48)) return (tspriteptr[k]->cstat & 48) - (tspriteptr[l]->cstat & 48); if ((tspriteptr[k]->cstat & 48) == 16 && tspriteptr[k]->ang != tspriteptr[l]->ang) return tspriteptr[k]->ang - tspriteptr[l]->ang; } #endif if (tspriteptr[k]->statnum != tspriteptr[l]->statnum) return tspriteptr[k]->statnum - tspriteptr[l]->statnum; if (tspriteptr[k]->x == tspriteptr[l]->x && tspriteptr[k]->y == tspriteptr[l]->y && tspriteptr[k]->z == tspriteptr[l]->z && (tspriteptr[k]->cstat & 48) == (tspriteptr[l]->cstat & 48) && tspriteptr[k]->owner != tspriteptr[l]->owner) return tspriteptr[k]->owner - tspriteptr[l]->owner; if (klabs(spritesxyz[k].z-globalposz) != klabs(spritesxyz[l].z-globalposz)) return klabs(spritesxyz[k].z-globalposz)-klabs(spritesxyz[l].z-globalposz); return 0; } static void sortsprites(int const start, int const end) { int32_t i, gap, y, ys; if (start >= end) return; gap = 1; while (gap < end - start) gap = (gap<<1)+1; for (gap>>=1; gap>0; gap>>=1) //Sort sprite list for (i=start; i=start; l-=gap) { if (spritesxyz[l].y <= spritesxyz[l+gap].y) break; swapptr(&tspriteptr[l],&tspriteptr[l+gap]); swaplong(&spritesxyz[l].x,&spritesxyz[l+gap].x); swaplong(&spritesxyz[l].y,&spritesxyz[l+gap].y); } ys = spritesxyz[start].y; i = start; for (bssize_t j=start+1; j<=end; j++) { if (j < end) { y = spritesxyz[j].y; if (y == ys) continue; ys = y; } if (j > i+1) { for (bssize_t k=i; kz; if ((s->cstat&48) != 32) { int32_t yoff = picanm[s->picnum].yofs + s->yoffset; int32_t yspan = (tilesiz[s->picnum].y*s->yrepeat<<2); spritesxyz[k].z -= (yoff*s->yrepeat)<<2; if (!(s->cstat&128)) spritesxyz[k].z -= (yspan>>1); if (klabs(spritesxyz[k].z-globalposz) < (yspan>>1)) spritesxyz[k].z = globalposz; } } for (bssize_t k=i+1; k= 0; --i) { if (polymost_spriteHasTranslucency(&tsprite[i])) { tspriteptr[spritesortcnt] = &tsprite[i]; ++spritesortcnt; } else { tspriteptr[back] = &tsprite[i]; --back; } } } else #endif { for (; i >= 0; --i) { tspriteptr[i] = &tsprite[i]; } } for (i=numSprites-1; i>=0; --i) { const int32_t xs = tspriteptr[i]->x-globalposx, ys = tspriteptr[i]->y-globalposy; const int32_t yp = dmulscale6(xs,cosviewingrangeglobalang,ys,sinviewingrangeglobalang); #ifdef USE_OPENGL const int32_t modelp = polymost_spriteIsModelOrVoxel(tspriteptr[i]); #endif if (yp > (4<<8)) { const int32_t xp = dmulscale6(ys,cosglobalang,-xs,singlobalang); if (mulscale24(labs(xp+yp),xdimen) >= yp) goto killsprite; spritesxyz[i].x = scale(xp+yp,xdimen<<7,yp); } else if ((tspriteptr[i]->cstat&48) == 0) { killsprite: #ifdef USE_OPENGL if (!modelp) #endif { //Delete face sprite if on wrong side! if (i >= spritesortcnt) { --numSprites; if (i != numSprites) { tspriteptr[i] = tspriteptr[numSprites]; spritesxyz[i].x = spritesxyz[numSprites].x; spritesxyz[i].y = spritesxyz[numSprites].y; } } else { --numSprites; --spritesortcnt; if (i != numSprites) { tspriteptr[i] = tspriteptr[spritesortcnt]; spritesxyz[i].x = spritesxyz[spritesortcnt].x; spritesxyz[i].y = spritesxyz[spritesortcnt].y; tspriteptr[spritesortcnt] = tspriteptr[numSprites]; spritesxyz[spritesortcnt].x = spritesxyz[numSprites].x; spritesxyz[spritesortcnt].y = spritesxyz[numSprites].y; } } continue; } } spritesxyz[i].y = yp; } sortsprites(0, spritesortcnt); sortsprites(spritesortcnt, numSprites); #ifdef USE_OPENGL if (videoGetRenderMode() == REND_POLYMOST) { GLInterface.EnableBlend(false); GLInterface.EnableAlphaTest(true); GLInterface.SetClamp(1+2); GLInterface.SetDepthBias(-2, -256); if (spritesortcnt < numSprites) { int32_t py = spritesxyz[spritesortcnt].y; int32_t pcstat = tspriteptr[spritesortcnt]->cstat & 48; int32_t pangle = tspriteptr[spritesortcnt]->ang; i = spritesortcnt; for (bssize_t j = spritesortcnt + 1; j <= numSprites; j++) { if (j < numSprites) { if (py == spritesxyz[j].y && pcstat == (tspriteptr[j]->cstat & 48) && (pcstat != 16 || pangle == tspriteptr[j]->ang) && !polymost_spriteIsModelOrVoxel(tspriteptr[j])) continue; py = spritesxyz[j].y; pcstat = (tspriteptr[j]->cstat & 48); pangle = tspriteptr[j]->ang; } if (j - i == 1) { debugmask_add(i | 32768, tspriteptr[i]->owner); renderDrawSprite(i); tspriteptr[i] = NULL; } else { GLInterface.SetDepthMask(false); for (bssize_t k = j-1; k >= i; k--) { debugmask_add(k | 32768, tspriteptr[k]->owner); renderDrawSprite(k); } GLInterface.SetDepthMask(true); GLInterface.SetColorMask(false); for (bssize_t k = j-1; k >= i; k--) { renderDrawSprite(k); tspriteptr[k] = NULL; } GLInterface.SetColorMask(true); } i = j; } } GLInterface.SetClamp(0); int32_t numMaskWalls = maskwallcnt; maskwallcnt = 0; for (i = 0; i < numMaskWalls; i++) { if (polymost_maskWallHasTranslucency((uwalltype *) &wall[thewall[maskwall[i]]])) { maskwall[maskwallcnt] = maskwall[i]; maskwallcnt++; } else renderDrawMaskedWall(i); } GLInterface.EnableBlend(true); GLInterface.EnableAlphaTest(true); GLInterface.SetDepthMask(false); } #endif vec2f_t pos; pos.x = fglobalposx; pos.y = fglobalposy; // CAUTION: maskwallcnt and spritesortcnt may be zero! // Writing e.g. "while (maskwallcnt--)" is wrong! while (maskwallcnt) { // PLAG: sorting stuff const int32_t w = (videoGetRenderMode()==REND_POLYMER) ? maskwall[maskwallcnt-1] : thewall[maskwall[maskwallcnt-1]]; maskwallcnt--; vec2f_t dot = { (float)wall[w].x, (float)wall[w].y }; vec2f_t dot2 = { (float)wall[wall[w].point2].x, (float)wall[wall[w].point2].y }; vec2f_t middle = { (dot.x + dot2.x) * .5f, (dot.y + dot2.y) * .5f }; _equation maskeq = equation(dot.x, dot.y, dot2.x, dot2.y); _equation p1eq = equation(pos.x, pos.y, dot.x, dot.y); _equation p2eq = equation(pos.x, pos.y, dot2.x, dot2.y); #ifdef USE_OPENGL if (videoGetRenderMode() == REND_POLYMOST) GLInterface.SetClamp(1+2); #endif i = spritesortcnt; while (i) { i--; if (tspriteptr[i] != NULL) { vec2f_t spr; auto const tspr = tspriteptr[i]; spr.x = (float)tspr->x; spr.y = (float)tspr->y; if (!sameside(&maskeq, &spr, &pos)) { // Sprite and camera are on different sides of the // masked wall. // Check if the sprite is inside the 'cone' given by // the rays from the camera to the two wall-points. const int32_t inleft = sameside(&p1eq, &middle, &spr); const int32_t inright = sameside(&p2eq, &middle, &spr); int32_t ok = (inleft && inright); if (!ok) { // If not, check if any of the border points are... int32_t xx[4] = { tspr->x }; int32_t yy[4] = { tspr->y }; int32_t numpts, jj; const _equation pineq = inleft ? p1eq : p2eq; if ((tspr->cstat & 48) == 32) { numpts = 4; get_floorspr_points(tspr, 0, 0, &xx[0], &xx[1], &xx[2], &xx[3], &yy[0], &yy[1], &yy[2], &yy[3]); } else { const int32_t oang = tspr->ang; numpts = 2; // Consider face sprites as wall sprites with camera ang. // XXX: factor 4/5 needed? if ((tspr->cstat & 48) != 16) tspriteptr[i]->ang = globalang; get_wallspr_points(tspr, &xx[0], &xx[1], &yy[0], &yy[1]); if ((tspr->cstat & 48) != 16) tspriteptr[i]->ang = oang; } for (jj=0; jjowner); renderDrawSprite(i); tspriteptr[i] = NULL; } } } } debugmask_add(maskwall[maskwallcnt], thewall[maskwall[maskwallcnt]]); #ifdef USE_OPENGL if (videoGetRenderMode() == REND_POLYMOST) GLInterface.SetClamp(0); #endif renderDrawMaskedWall(maskwallcnt); } #ifdef USE_OPENGL if (videoGetRenderMode() == REND_POLYMOST) GLInterface.SetClamp(1+2); #endif while (spritesortcnt) { --spritesortcnt; if (tspriteptr[spritesortcnt] != NULL) { debugmask_add(i | 32768, tspriteptr[i]->owner); renderDrawSprite(spritesortcnt); tspriteptr[spritesortcnt] = NULL; } } #ifdef USE_OPENGL if (videoGetRenderMode() == REND_POLYMOST) { GLInterface.SetDepthMask(true); GLInterface.SetClamp(0); GLInterface.SetDepthBias(0, 0); } #endif } // // fillpolygon (internal) // static void renderFillPolygon(int32_t npoints) { // fix for bad next-point (xb1) values... for (int z = 0; z < npoints; z++) if ((unsigned)xb1[z] >= (unsigned)npoints) xb1[z] = 0; FVector2 xtex, ytex, otex; int x1 = mulscale16(globalx1, xyaspect); int y2 = mulscale16(globaly2, xyaspect); xtex.X = ((float)asm1) * (1.f / 4294967296.f); xtex.Y = ((float)asm2) * (1.f / 4294967296.f); ytex.X = ((float)x1) * (1.f / 4294967296.f); ytex.Y = ((float)y2) * (-1.f / 4294967296.f); otex.X = (fxdim * xtex.X + fydim * ytex.X) * -0.5f + fglobalposx * (1.f / 4294967296.f); otex.Y = (fxdim * xtex.Y + fydim * ytex.Y) * -0.5f - fglobalposy * (1.f / 4294967296.f); twod->FillPolygon(rx1, ry1, xb1, npoints, globalpicnum, globalpal, globalshade, globalorientation, xtex, ytex, otex, windowxy1.x, windowxy1.y, windowxy2.x, windowxy2.y); } // // drawmapview // void renderDrawMapView(int32_t dax, int32_t day, int32_t zoome, int16_t ang) { int32_t i, j, k, l; int32_t x, y; int32_t s, ox, oy; int32_t const oyxaspect = yxaspect, oviewingrange = viewingrange; renderSetAspect(65536, divscale16((320*5)/8, 200)); beforedrawrooms = 0; Bmemset(gotsector, 0, sizeof(gotsector)); vec2_t const c1 = { (windowxy1.x<<12), (windowxy1.y<<12) }; vec2_t const c2 = { ((windowxy2.x+1)<<12)-1, ((windowxy2.y+1)<<12)-1 }; zoome <<= 8; vec2_t const bakgvect = { divscale28(sintable[(1536 - ang) & 2047], zoome), divscale28(sintable[(2048 - ang) & 2047], zoome) }; vec2_t const vect = { mulscale8(sintable[(2048 - ang) & 2047], zoome), mulscale8(sintable[(1536 - ang) & 2047], zoome) }; vec2_t const vect2 = { mulscale16(vect.x, yxaspect), mulscale16(vect.y, yxaspect) }; int32_t sortnum = 0; usectorptr_t sec; for (s=0,sec=(usectorptr_t)§or[s]; s= 0 && (sector[s].floorstat&(256+128))==0) continue; #endif int32_t npoints = 0; i = 0; int32_t startwall = sec->wallptr; j = startwall; l = 0; uwallptr_t wal; int32_t w; for (w=sec->wallnum,wal=(uwallptr_t)&wall[startwall]; w>0; w--,wal++,j++) { k = lastwall(j); if ((k > j) && (npoints > 0)) { xb1[npoints-1] = l; l = npoints; } //overwrite point2 //wall[k].x wal->x wall[wal->point2].x //wall[k].y wal->y wall[wal->point2].y if (!dmulscale1(wal->x-wall[k].x,wall[wal->point2].y-wal->y,-(wal->y-wall[k].y),wall[wal->point2].x-wal->x)) continue; ox = wal->x - dax; oy = wal->y - day; x = dmulscale16(ox,vect.x,-oy,vect.y) + (xdim<<11); y = dmulscale16(oy,vect2.x,ox,vect2.y) + (ydim<<11); i |= getclipmask(x-c1.x,c2.x-x,y-c1.y,c2.y-y); rx1[npoints] = x; ry1[npoints] = y; xb1[npoints] = npoints+1; npoints++; } if (npoints > 0) xb1[npoints-1] = l; //overwrite point2 vec2_t bak = { rx1[0], mulscale16(ry1[0]-(ydim<<11),xyaspect)+(ydim<<11) }; //Collect floor sprites to draw for (i=headspritesect[s]; i>=0; i=nextspritesect[i]) { if (sprite[i].cstat & 32768) continue; if ((sprite[i].cstat & 48) == 32) { if ((sprite[i].cstat & (64 + 8)) == (64 + 8)) continue; tsprite[sortnum++].owner = i; } } gotsector[s>>3] |= pow2char[s&7]; globalorientation = (int32_t)sec->floorstat; if ((globalorientation&1) != 0) continue; globalpal = sec->floorpal; if (palookup[sec->floorpal] != globalpalwritten) { globalpalwritten = palookup[sec->floorpal]; if (!globalpalwritten) globalpalwritten = palookup[0]; // JBF: fixes null-pointer crash //setpalookupaddress(globalpalwritten); } globalpicnum = sec->floorpicnum; if ((unsigned)globalpicnum >= (unsigned)MAXTILES) globalpicnum = 0; tileUpdatePicnum(&globalpicnum, s); setgotpic(globalpicnum); if ((tilesiz[globalpicnum].x <= 0) || (tilesiz[globalpicnum].y <= 0)) continue; globalshade = max(min(sec->floorshade, numshades - 1), 0); globvis = globalhisibility; if (sec->visibility != 0) globvis = mulscale4(globvis, (uint8_t)(sec->visibility+16)); if ((globalorientation&64) == 0) { set_globalpos(dax, day, globalposz); globalx1 = bakgvect.x; globaly1 = bakgvect.y; globalx2 = bakgvect.x; globaly2 = bakgvect.y; } else { ox = wall[wall[startwall].point2].x - wall[startwall].x; oy = wall[wall[startwall].point2].y - wall[startwall].y; i = nsqrtasm(uhypsq(ox,oy)); if (i == 0) continue; i = 1048576/i; globalx1 = mulscale10(dmulscale10(ox,bakgvect.x,oy,bakgvect.y),i); globaly1 = mulscale10(dmulscale10(ox,bakgvect.y,-oy,bakgvect.x),i); ox = (bak.x>>4)-(xdim<<7); oy = (bak.y>>4)-(ydim<<7); globalposx = dmulscale28(-oy, globalx1, -ox, globaly1); globalposy = dmulscale28(-ox, globalx1, oy, globaly1); globalx2 = -globalx1; globaly2 = -globaly1; int32_t const daslope = sector[s].floorheinum; i = nsqrtasm(daslope*daslope+16777216); set_globalpos(globalposx, mulscale12(globalposy,i), globalposz); globalx2 = mulscale12(globalx2,i); globaly2 = mulscale12(globaly2,i); } calc_globalshifts(); if ((globalorientation&0x4) > 0) { i = globalposx; globalposx = -globalposy; globalposy = -i; i = globalx2; globalx2 = globaly1; globaly1 = i; i = globalx1; globalx1 = -globaly2; globaly2 = -i; } if ((globalorientation&0x10) > 0) globalx1 = -globalx1, globaly1 = -globaly1, globalposx = -globalposx; if ((globalorientation&0x20) > 0) globalx2 = -globalx2, globaly2 = -globaly2, globalposy = -globalposy; asm1 = (globaly1<floorxpanning)<<24), ((int64_t) globalposy<<(20+globalyshift))-(((uint32_t) sec->floorypanning)<<24), globalposz); renderFillPolygon(npoints); } //Sort sprite list int32_t gap = 1; while (gap < sortnum) gap = (gap << 1) + 1; for (gap>>=1; gap>0; gap>>=1) for (i=0; i=0; j-=gap) { if (sprite[tsprite[j].owner].z <= sprite[tsprite[j+gap].owner].z) break; swapshort(&tsprite[j].owner,&tsprite[j+gap].owner); } for (s=sortnum-1; s>=0; s--) { auto const spr = (uspritetype * )&sprite[tsprite[s].owner]; if ((spr->cstat&48) == 32) { const int32_t xspan = tilesiz[spr->picnum].x; int32_t npoints = 0; vec2_t v1 = { spr->x, spr->y }, v2, v3, v4; get_floorspr_points(spr, 0, 0, &v1.x, &v2.x, &v3.x, &v4.x, &v1.y, &v2.y, &v3.y, &v4.y); xb1[0] = 1; xb1[1] = 2; xb1[2] = 3; xb1[3] = 0; npoints = 4; i = 0; ox = v1.x - dax; oy = v1.y - day; x = dmulscale16(ox,vect.x,-oy,vect.y) + (xdim<<11); y = dmulscale16(oy,vect2.x,ox,vect2.y) + (ydim<<11); i |= getclipmask(x-c1.x,c2.x-x,y-c1.y,c2.y-y); rx1[0] = x; ry1[0] = y; ox = v2.x - dax; oy = v2.y - day; x = dmulscale16(ox,vect.x,-oy,vect.y) + (xdim<<11); y = dmulscale16(oy,vect2.x,ox,vect2.y) + (ydim<<11); i |= getclipmask(x-c1.x,c2.x-x,y-c1.y,c2.y-y); rx1[1] = x; ry1[1] = y; ox = v3.x - dax; oy = v3.y - day; x = dmulscale16(ox,vect.x,-oy,vect.y) + (xdim<<11); y = dmulscale16(oy,vect2.x,ox,vect2.y) + (ydim<<11); i |= getclipmask(x-c1.x,c2.x-x,y-c1.y,c2.y-y); rx1[2] = x; ry1[2] = y; x = rx1[0]+rx1[2]-rx1[1]; y = ry1[0]+ry1[2]-ry1[1]; i |= getclipmask(x-c1.x,c2.x-x,y-c1.y,c2.y-y); rx1[3] = x; ry1[3] = y; vec2_t bak = { rx1[0], mulscale16(ry1[0] - (ydim << 11), xyaspect) + (ydim << 11) }; globalpicnum = spr->picnum; globalpal = spr->pal; // GL needs this, software doesn't if ((unsigned)globalpicnum >= (unsigned)MAXTILES) globalpicnum = 0; tileUpdatePicnum(&globalpicnum, s); setgotpic(globalpicnum); if ((tilesiz[globalpicnum].x <= 0) || (tilesiz[globalpicnum].y <= 0)) continue; if ((sector[spr->sectnum].ceilingstat&1) > 0) globalshade = ((int32_t)sector[spr->sectnum].ceilingshade); else globalshade = ((int32_t)sector[spr->sectnum].floorshade); globalshade = max(min(globalshade+spr->shade+6,numshades-1),0); asm3 = FP_OFF(palookup[spr->pal]+(globalshade<<8)); globvis = globalhisibility; if (sec->visibility != 0) globvis = mulscale4(globvis, (uint8_t)(sec->visibility+16)); //relative alignment stuff ox = v2.x-v1.x; oy = v2.y-v1.y; i = ox*ox+oy*oy; if (i == 0) continue; i = tabledivide32_noinline(65536*16384, i); globalx1 = mulscale10(dmulscale10(ox,bakgvect.x,oy,bakgvect.y),i); globaly1 = mulscale10(dmulscale10(ox,bakgvect.y,-oy,bakgvect.x),i); ox = v1.y-v4.y; oy = v4.x-v1.x; i = ox*ox+oy*oy; if (i == 0) continue; i = tabledivide32_noinline(65536*16384, i); globalx2 = mulscale10(dmulscale10(ox,bakgvect.x,oy,bakgvect.y),i); globaly2 = mulscale10(dmulscale10(ox,bakgvect.y,-oy,bakgvect.x),i); ox = picsiz[globalpicnum]; oy = ((ox>>4)&15); ox &= 15; if (pow2long[ox] != xspan) { ox++; globalx1 = mulscale(globalx1,xspan,ox); globaly1 = mulscale(globaly1,xspan,ox); } bak.x = (bak.x>>4)-(xdim<<7); bak.y = (bak.y>>4)-(ydim<<7); globalposx = dmulscale28(-bak.y,globalx1,-bak.x,globaly1); globalposy = dmulscale28(bak.x,globalx2,-bak.y,globaly2); if ((spr->cstat&0x4) > 0) globalx1 = -globalx1, globaly1 = -globaly1, globalposx = -globalposx; asm1 = (globaly1<<2); globalx1 <<= 2; globalposx <<= (20+2); asm2 = (globalx2<<2); globaly2 <<= 2; globalposy <<= (20+2); set_globalpos(globalposx, globalposy, globalposz); // so polymost can get the translucency. ignored in software mode: globalorientation = ((spr->cstat&2)<<7) | ((spr->cstat&512)>>2); renderFillPolygon(npoints); } } if (r_usenewaspect) renderSetAspect(oviewingrange, oyxaspect); else renderSetAspect(65536, divscale16(ydim*320, xdim*200)); } //////////////////// LOADING AND SAVING ROUTINES //////////////////// static FORCE_INLINE int32_t have_maptext(void) { return (mapversion >= 10); } static void enginePrepareLoadBoard(FileReader & fr, vec3_t *dapos, int16_t *daang, int16_t *dacursectnum) { initspritelists(); show2dsector.Zero(); Bmemset(show2dsprite, 0, sizeof(show2dsprite)); Bmemset(show2dwall, 0, sizeof(show2dwall)); Bmemset(editwall, 0, sizeof(editwall)); #ifdef USE_STRUCT_TRACKERS Bmemset(sectorchanged, 0, sizeof(sectorchanged)); Bmemset(spritechanged, 0, sizeof(spritechanged)); Bmemset(wallchanged, 0, sizeof(wallchanged)); #endif #ifdef USE_OPENGL Polymost_prepare_loadboard(); #endif if (!have_maptext()) { fr.Read(&dapos->x,4); dapos->x = B_LITTLE32(dapos->x); fr.Read(&dapos->y,4); dapos->y = B_LITTLE32(dapos->y); fr.Read(&dapos->z,4); dapos->z = B_LITTLE32(dapos->z); fr.Read(daang,2); *daang = B_LITTLE16(*daang) & 2047; fr.Read(dacursectnum,2); *dacursectnum = B_LITTLE16(*dacursectnum); } } static int32_t engineFinishLoadBoard(const vec3_t *dapos, int16_t *dacursectnum, int16_t numsprites, char myflags) { int32_t i, realnumsprites=numsprites, numremoved; #if !defined USE_OPENGL || !defined POLYMER UNREFERENCED_PARAMETER(myflags); #endif for (i=0; i check_sprite(). Insert it // for now, because we must maintain the sprite numbering. sprite[i].statnum = sprite[i].sectnum = 0; removeit = 1; } insertsprite(sprite[i].sectnum, sprite[i].statnum); if (removeit) { // Flag .statnum==MAXSTATUS, temporarily creating an inconsistency // with sprite list. sprite[i].statnum = MAXSTATUS; realnumsprites--; } } if (numsprites != realnumsprites) { for (i=0; ix, dapos->y, dacursectnum); #ifdef HAVE_CLIPSHAPE_FEATURE if (!quickloadboard) #endif { Bmemset(spriteext, 0, sizeof(spriteext_t)*MAXSPRITES); #ifndef NEW_MAP_FORMAT Bmemset(wallext, 0, sizeof(wallext_t)*MAXWALLS); #endif #ifdef USE_OPENGL Bmemset(spritesmooth, 0, sizeof(spritesmooth_t)*(MAXSPRITES+MAXUNIQHUDID)); # ifdef POLYMER if (videoGetRenderMode() == REND_POLYMER) { if ((myflags&4)==0) polymer_loadboard(); } # endif #endif } guniqhudid = 0; return numremoved; } #define MYMAXSECTORS() (MAXSECTORS==MAXSECTORSV7 || mapversion <= 7 ? MAXSECTORSV7 : MAXSECTORSV8) #define MYMAXWALLS() (MAXSECTORS==MAXSECTORSV7 || mapversion <= 7 ? MAXWALLSV7 : MAXWALLSV8) #define MYMAXSPRITES() (MAXSECTORS==MAXSECTORSV7 || mapversion <= 7 ? MAXSPRITESV7 : MAXSPRITESV8) // Sprite checking static void remove_sprite(int32_t i) { Bmemset(&sprite[i], 0, sizeof(spritetype)); sprite[i].statnum = MAXSTATUS; sprite[i].sectnum = MAXSECTORS; } // This is only to be run after reading the sprite array! static void check_sprite(int32_t i) { if ((unsigned)sprite[i].statnum >= MAXSTATUS) { Printf("Map error: sprite #%d (%d,%d) with illegal statnum (%d) REMOVED.\n", i, TrackerCast(sprite[i].x), TrackerCast(sprite[i].y), TrackerCast(sprite[i].statnum)); remove_sprite(i); } else if ((unsigned)sprite[i].picnum >= MAXTILES) { Printf("Map error: sprite #%d (%d,%d) with illegal picnum (%d) REMOVED.\n", i, TrackerCast(sprite[i].x), TrackerCast(sprite[i].y), TrackerCast(sprite[i].sectnum)); remove_sprite(i); } else if ((unsigned)sprite[i].sectnum >= (unsigned)numsectors) { const int32_t osectnum = sprite[i].sectnum; sprite[i].sectnum = -1; updatesector(sprite[i].x, sprite[i].y, &sprite[i].sectnum); if (sprite[i].sectnum < 0) remove_sprite(i); Printf("Map error: sprite #%d (%d,%d) with illegal sector (%d) ", i, TrackerCast(sprite[i].x), TrackerCast(sprite[i].y), osectnum); if (sprite[i].statnum != MAXSTATUS) Printf("changed to sector %d.\n", TrackerCast(sprite[i].sectnum)); else Printf("REMOVED.\n"); } } #include "md4.h" int32_t(*loadboard_replace)(const char *filename, char flags, vec3_t *dapos, int16_t *daang, int16_t *dacursectnum) = NULL; // flags: 1, 2: former parameter "fromwhere" // 4: don't call polymer_loadboard // 8: don't autoexec .cfg // returns: on success, number of removed sprites // -1: file not found // -2: invalid version // -3: invalid number of sectors, walls or sprites // <= -4: map-text error int32_t engineLoadBoard(const char *filename, char flags, vec3_t *dapos, int16_t *daang, int16_t *dacursectnum) { if (loadboard_replace) return loadboard_replace(filename, flags, dapos, daang, dacursectnum); int32_t i; int16_t numsprites; const char myflags = flags&(~3); flags &= 3; FileReader fr = fileSystem.OpenFileReader(filename); if (!fr.isOpen()) { mapversion = 7; return -1; } if (fr.Read(&mapversion, 4) != 4) { return -2; } { int32_t ok = 0; #ifdef NEW_MAP_FORMAT // Check for map-text first. if (!Bmemcmp(&mapversion, "--ED", 4)) { mapversion = 10; ok = 1; } else #endif { // Not map-text. We expect a little-endian version int now. mapversion = B_LITTLE32(mapversion); #ifdef YAX_ENABLE ok |= (mapversion==9); #endif #if MAXSECTORS==MAXSECTORSV8 // v8 engine ok |= (mapversion==8); #endif ok |= (mapversion==7); } if (!ok) { return -2; } } enginePrepareLoadBoard(fr, dapos, daang, dacursectnum); ////////// Read sectors ////////// fr.Read(&numsectors,2); numsectors = B_LITTLE16(numsectors); if ((unsigned)numsectors >= MYMAXSECTORS() + 1) { error: numsectors = 0; numwalls = 0; numsprites = 0; return -3; } fr.Read(sector, sizeof(sectortypev7)*numsectors); for (i=numsectors-1; i>=0; i--) { sector[i].wallptr = B_LITTLE16(sector[i].wallptr); sector[i].wallnum = B_LITTLE16(sector[i].wallnum); sector[i].ceilingz = B_LITTLE32(sector[i].ceilingz); sector[i].floorz = B_LITTLE32(sector[i].floorz); sector[i].ceilingstat = B_LITTLE16(sector[i].ceilingstat); sector[i].floorstat = B_LITTLE16(sector[i].floorstat); sector[i].ceilingpicnum = B_LITTLE16(sector[i].ceilingpicnum); sector[i].ceilingheinum = B_LITTLE16(sector[i].ceilingheinum); sector[i].floorpicnum = B_LITTLE16(sector[i].floorpicnum); sector[i].floorheinum = B_LITTLE16(sector[i].floorheinum); sector[i].lotag = B_LITTLE16(sector[i].lotag); sector[i].hitag = B_LITTLE16(sector[i].hitag); sector[i].extra = B_LITTLE16(sector[i].extra); } ////////// Read walls ////////// fr.Read(&numwalls,2); numwalls = B_LITTLE16(numwalls); if ((unsigned)numwalls >= MYMAXWALLS()+1) goto error; fr.Read( wall, sizeof(walltypev7)*numwalls); for (i=numwalls-1; i>=0; i--) { wall[i].x = B_LITTLE32(wall[i].x); wall[i].y = B_LITTLE32(wall[i].y); wall[i].point2 = B_LITTLE16(wall[i].point2); wall[i].nextwall = B_LITTLE16(wall[i].nextwall); wall[i].nextsector = B_LITTLE16(wall[i].nextsector); wall[i].cstat = B_LITTLE16(wall[i].cstat); wall[i].picnum = B_LITTLE16(wall[i].picnum); wall[i].overpicnum = B_LITTLE16(wall[i].overpicnum); wall[i].lotag = B_LITTLE16(wall[i].lotag); wall[i].hitag = B_LITTLE16(wall[i].hitag); wall[i].extra = B_LITTLE16(wall[i].extra); } ////////// Read sprites ////////// fr.Read(&numsprites,2); numsprites = B_LITTLE16(numsprites); if ((unsigned)numsprites >= MYMAXSPRITES()+1) goto error; fr.Read( sprite, sizeof(spritetype)*numsprites); fr.Seek(0, FileReader::SeekSet); int32_t boardsize = fr.GetLength(); uint8_t *fullboard = (uint8_t*)Xmalloc(boardsize); fr.Read( fullboard, boardsize); md4once(fullboard, boardsize, g_loadedMapHack.md4); Xfree(fullboard); // Done reading file. if (!have_maptext()) { for (i=numsprites-1; i>=0; i--) { sprite[i].x = B_LITTLE32(sprite[i].x); sprite[i].y = B_LITTLE32(sprite[i].y); sprite[i].z = B_LITTLE32(sprite[i].z); sprite[i].cstat = B_LITTLE16(sprite[i].cstat); sprite[i].picnum = B_LITTLE16(sprite[i].picnum); sprite[i].sectnum = B_LITTLE16(sprite[i].sectnum); sprite[i].statnum = B_LITTLE16(sprite[i].statnum); sprite[i].ang = B_LITTLE16(sprite[i].ang); sprite[i].owner = B_LITTLE16(sprite[i].owner); sprite[i].xvel = B_LITTLE16(sprite[i].xvel); sprite[i].yvel = B_LITTLE16(sprite[i].yvel); sprite[i].zvel = B_LITTLE16(sprite[i].zvel); sprite[i].lotag = B_LITTLE16(sprite[i].lotag); sprite[i].hitag = B_LITTLE16(sprite[i].hitag); sprite[i].extra = B_LITTLE16(sprite[i].extra); check_sprite(i); } } else { for (i=numsprites-1; i>=0; i--) check_sprite(i); } // Back up the map version of the *loaded* map. Must be before yax_update(). g_loadedMapVersion = mapversion; #ifdef YAX_ENABLE yax_update(mapversion<9); #endif if ((myflags&8)==0) { #if 0 // No, no! This is absolutely unacceptable. I won't support mods that require this kind of access. char fn[BMAX_PATH]; Bstrcpy(fn, filename); append_ext_UNSAFE(fn, ".cfg"); OSD_Exec(fn); #endif // Per-map ART artSetupMapArt(filename); } // Printf("Loaded map \"%s\" (md4sum: %08x%08x%08x%08x)\n", filename, B_BIG32(*((int32_t*)&md4out[0])), B_BIG32(*((int32_t*)&md4out[4])), B_BIG32(*((int32_t*)&md4out[8])), B_BIG32(*((int32_t*)&md4out[12]))); return engineFinishLoadBoard(dapos, dacursectnum, numsprites, myflags); } // // loadboardv5/6 // #include "engine_oldmap.h" // Powerslave uses v6 // Witchaven 1 and TekWar and LameDuke use v5 int32_t engineLoadBoardV5V6(const char *filename, char fromwhere, vec3_t *dapos, int16_t *daang, int16_t *dacursectnum) { int32_t i; int16_t numsprites; struct sectortypev5 v5sect; struct walltypev5 v5wall; struct spritetypev5 v5spr; struct sectortypev6 v6sect; struct walltypev6 v6wall; struct spritetypev6 v6spr; FileReader fr = fileSystem.OpenFileReader(filename); if (!fr.isOpen()) { mapversion = 5L; return -1; } fr.Read(&mapversion,4); mapversion = B_LITTLE32(mapversion); if (mapversion != 5L && mapversion != 6L) { return -2; } enginePrepareLoadBoard(fr, dapos, daang, dacursectnum); fr.Read(&numsectors,2); numsectors = B_LITTLE16(numsectors); if (numsectors > MAXSECTORS) { return -1; } for (i=0; i MAXWALLS) { return -1; } for (i=0; i MAXSPRITES) { return -1; } for (i=0; i>7) // // setgamemode // // JBF: davidoption now functions as a windowed-mode flag (0 == windowed, 1 == fullscreen) int32_t videoSetGameMode(char davidoption, int32_t daupscaledxdim, int32_t daupscaledydim, int32_t dabpp, int32_t daupscalefactor) { int32_t j; if (dabpp != 32) return -1; // block software mode. daupscaledxdim = max(320, daupscaledxdim); daupscaledydim = max(200, daupscaledydim); if (in3dmode() && (xres == daupscaledxdim) && (yres == daupscaledydim) && (bpp == dabpp)) return 0; Bstrcpy(kensmessage,"!!!! BUILD engine&tools programmed by Ken Silverman of E.G. RI." " (c) Copyright 1995 Ken Silverman. Summary: BUILD = Ken. !!!!"); j = bpp; g_lastpalettesum = 0; rendmode = REND_POLYMOST; upscalefactor = 1; xdim = daupscaledxdim; ydim = daupscaledydim; V_UpdateModeSize(xdim, ydim); numpages = 1; // We have only one page, no exceptions. #ifdef USE_OPENGL fxdim = (float) xdim; fydim = (float) ydim; #endif j = ydim*4; //Leave room for horizlookup&horizlookup2 //Force drawrooms to call dosetaspect & recalculate stuff oxyaspect = oxdimen = oviewingrange = -1; videoSetViewableArea(0L,0L,xdim-1,ydim-1); videoClearScreen(0L); if (searchx < 0) { searchx = halfxdimen; searchy = (ydimen>>1); } qsetmode = 200; return 0; } // // nextpage // void videoNextPage(void) { static bool recursion; if (!recursion) { // This protection is needed because the menu can call scripts from inside its drawers and the scripts can call the busy-looping Screen_Play script event // which calls videoNextPage for page flipping again. In this loop the UI drawers may not get called again. // Ideally this stuff should be moved out of videoNextPage so that all those busy loops won't call UI overlays at all. recursion = true; M_Drawer(); FStat::PrintStat(); C_DrawConsole(); recursion = false; } if (in3dmode()) { g_beforeSwapTime = timerGetHiTicks(); videoShowFrame(0); } #ifdef USE_OPENGL omdtims = mdtims; mdtims = timerGetTicks(); for (native_t i = 0; i < Numsprites; ++i) if ((mdpause && spriteext[i].mdanimtims) || (spriteext[i].flags & SPREXT_NOMDANIM)) spriteext[i].mdanimtims += mdtims - omdtims; #endif beforedrawrooms = 1; numframes++; } // // qloadkvx // int32_t qloadkvx(int32_t voxindex, const char *filename) { if ((unsigned)voxindex >= MAXVOXELS) return -1; auto fil = fileSystem.OpenFileReader(filename); if (!fil.isOpen()) return -1; int32_t lengcnt = 0; const int32_t lengtot = fil.GetLength(); for (bssize_t i=0; i= lengtot-768) break; } #ifdef USE_OPENGL if (voxmodels[voxindex]) { voxfree(voxmodels[voxindex]); voxmodels[voxindex] = NULL; } Xfree(voxfilenames[voxindex]); voxfilenames[voxindex] = Xstrdup(filename); #endif g_haveVoxels = 1; return 0; } void vox_undefine(int32_t const tile) { ssize_t voxindex = tiletovox[tile]; if (voxindex < 0) return; #ifdef USE_OPENGL if (voxmodels[voxindex]) { voxfree(voxmodels[voxindex]); voxmodels[voxindex] = NULL; } DO_FREE_AND_NULL(voxfilenames[voxindex]); #endif for (ssize_t j = 0; j < MAXVOXMIPS; ++j) { // CACHE1D_FREE voxoff[voxindex][j] = 0; } voxscale[voxindex] = 65536; voxrotate[voxindex>>3] &= ~pow2char[voxindex&7]; tiletovox[tile] = -1; // TODO: nextvoxid } void vox_deinit() { for (auto &vox : voxmodels) { voxfree(vox); vox = nullptr; } } // // inside // // See http://fabiensanglard.net/duke3d/build_engine_internals.php, // "Inside details" for the idea behind the algorithm. int32_t inside_ps(int32_t x, int32_t y, int16_t sectnum) { if (sectnum >= 0 && sectnum < numsectors) { int32_t cnt = 0; auto wal = (uwallptr_t)&wall[sector[sectnum].wallptr]; int wallsleft = sector[sectnum].wallnum; do { vec2_t v1 = { wal->x - x, wal->y - y }; auto const &wal2 = *(uwallptr_t)&wall[wal->point2]; vec2_t v2 = { wal2.x - x, wal2.y - y }; if ((v1.y^v2.y) < 0) cnt ^= (((v1.x^v2.x) < 0) ? (v1.x*v2.y= 0)); wal++; } while (--wallsleft); return cnt; } return -1; } int32_t inside_old(int32_t x, int32_t y, int16_t sectnum) { if (sectnum >= 0 && sectnum < numsectors) { uint32_t cnt = 0; auto wal = (uwallptr_t)&wall[sector[sectnum].wallptr]; int wallsleft = sector[sectnum].wallnum; do { // Get the x and y components of the [tested point]-->[wall // point{1,2}] vectors. vec2_t v1 = { wal->x - x, wal->y - y }; auto const &wal2 = *(uwallptr_t)&wall[wal->point2]; vec2_t v2 = { wal2.x - x, wal2.y - y }; // If their signs differ[*], ... // // [*] where '-' corresponds to <0 and '+' corresponds to >=0. // Equivalently, the branch is taken iff // y1 != y2 AND y_m <= y < y_M, // where y_m := min(y1, y2) and y_M := max(y1, y2). if ((v1.y^v2.y) < 0) cnt ^= (((v1.x^v2.x) >= 0) ? v1.x : (v1.x*v2.y-v2.x*v1.y)^v2.y); wal++; } while (--wallsleft); return cnt>>31; } return -1; } int32_t inside(int32_t x, int32_t y, int16_t sectnum) { switch (enginecompatibility_mode) { case ENGINECOMPATIBILITY_NONE: break; case ENGINECOMPATIBILITY_19950829: return inside_ps(x, y, sectnum); default: return inside_old(x, y, sectnum); } if ((unsigned)sectnum < (unsigned)numsectors) { uint32_t cnt1 = 0, cnt2 = 0; auto wal = (uwallptr_t)&wall[sector[sectnum].wallptr]; int wallsleft = sector[sectnum].wallnum; do { // Get the x and y components of the [tested point]-->[wall // point{1,2}] vectors. vec2_t v1 = { wal->x - x, wal->y - y }; auto const &wal2 = *(uwallptr_t)&wall[wal->point2]; vec2_t v2 = { wal2.x - x, wal2.y - y }; // First, test if the point is EXACTLY_ON_WALL_POINT. if ((v1.x|v1.y) == 0 || (v2.x|v2.y)==0) return 1; // If their signs differ[*], ... // // [*] where '-' corresponds to <0 and '+' corresponds to >=0. // Equivalently, the branch is taken iff // y1 != y2 AND y_m <= y < y_M, // where y_m := min(y1, y2) and y_M := max(y1, y2). if ((v1.y^v2.y) < 0) cnt1 ^= (((v1.x^v2.x) >= 0) ? v1.x : (v1.x*v2.y-v2.x*v1.y)^v2.y); v1.y--; v2.y--; // Now, do the same comparisons, but with the interval half-open on // the other side! That is, take the branch iff // y1 != y2 AND y_m < y <= y_M, // For a rectangular sector, without EXACTLY_ON_WALL_POINT, this // would still leave the lower left and upper right points // "outside" the sector. if ((v1.y^v2.y) < 0) { v1.x--; v2.x--; cnt2 ^= (((v1.x^v2.x) >= 0) ? v1.x : (v1.x*v2.y-v2.x*v1.y)^v2.y); } wal++; } while (--wallsleft); return (cnt1|cnt2)>>31; } return -1; } int32_t LUNATIC_FASTCALL getangle(int32_t xvect, int32_t yvect) { int32_t rv; if ((xvect | yvect) == 0) rv = 0; else if (xvect == 0) rv = 512 + ((yvect < 0) << 10); else if (yvect == 0) rv = ((xvect < 0) << 10); else if (xvect == yvect) rv = 256 + ((xvect < 0) << 10); else if (xvect == -yvect) rv = 768 + ((xvect > 0) << 10); else if (klabs(xvect) > klabs(yvect)) rv = ((radarang[640 + scale(160, yvect, xvect)] >> 6) + ((xvect < 0) << 10)) & 2047; else rv = ((radarang[640 - scale(160, xvect, yvect)] >> 6) + 512 + ((yvect < 0) << 10)) & 2047; return rv; } // // ksqrt // int32_t ksqrt(uint32_t num) { if (enginecompatibility_mode == ENGINECOMPATIBILITY_19950829) return ksqrtasm_old(num); return nsqrtasm(num); } #ifdef LUNATIC int32_t Mulscale(int32_t a, int32_t b, int32_t sh) { return mulscale(a, b, sh); } #endif // Gets the BUILD unit height and z offset of a sprite. // Returns the z offset, 'height' may be NULL. int32_t spriteheightofsptr(uspriteptr_t spr, int32_t *height, int32_t alsotileyofs) { int32_t hei, zofs=0; const int32_t picnum=spr->picnum, yrepeat=spr->yrepeat; hei = (tilesiz[picnum].y*yrepeat)<<2; if (height != NULL) *height = hei; if (spr->cstat&128) zofs = hei>>1; // NOTE: a positive per-tile yoffset translates the sprite into the // negative world z direction (i.e. upward). if (alsotileyofs) zofs -= picanm[picnum].yofs*yrepeat<<2; return zofs; } // // setsprite // int32_t setsprite(int16_t spritenum, const vec3_t *newpos) { int16_t tempsectnum = sprite[spritenum].sectnum; if ((void const *) newpos != (void *) &sprite[spritenum]) sprite[spritenum].pos = *newpos; updatesector(newpos->x,newpos->y,&tempsectnum); if (tempsectnum < 0) return -1; if (tempsectnum != sprite[spritenum].sectnum) changespritesect(spritenum,tempsectnum); return 0; } int32_t setspritez(int16_t spritenum, const vec3_t *newpos) { int16_t tempsectnum = sprite[spritenum].sectnum; if ((void const *)newpos != (void *)&sprite[spritenum]) sprite[spritenum].pos = *newpos; updatesectorz(newpos->x,newpos->y,newpos->z,&tempsectnum); if (tempsectnum < 0) return -1; if (tempsectnum != sprite[spritenum].sectnum) changespritesect(spritenum,tempsectnum); return 0; } // // nextsectorneighborz // // -1: ceiling or up // 1: floor or down int32_t nextsectorneighborz(int16_t sectnum, int32_t refz, int16_t topbottom, int16_t direction) { int32_t nextz = (direction==1) ? INT32_MAX : INT32_MIN; int32_t sectortouse = -1; auto wal = (uwallptr_t)&wall[sector[sectnum].wallptr]; int32_t i = sector[sectnum].wallnum; do { const int32_t ns = wal->nextsector; if (ns >= 0) { const int32_t testz = (topbottom == 1) ? sector[ns].floorz : sector[ns].ceilingz; const int32_t update = (direction == 1) ? (nextz > testz && testz > refz) : (nextz < testz && testz < refz); if (update) { nextz = testz; sectortouse = ns; } } wal++; i--; } while (i != 0); return sectortouse; } // // cansee // int32_t cansee_old(int32_t xs, int32_t ys, int32_t zs, int16_t sectnums, int32_t xe, int32_t ye, int32_t ze, int16_t sectnume) { sectortype *sec, *nsec; walltype *wal, *wal2; int32_t intx, inty, intz, i, cnt, nextsector, dasectnum, dacnt, danum; if ((xs == xe) && (ys == ye) && (sectnums == sectnume)) return 1; clipsectorlist[0] = sectnums; danum = 1; for(dacnt=0;dacntwallnum,wal=&wall[sec->wallptr];cnt>0;cnt--,wal++) { wal2 = &wall[wal->point2]; if (lintersect(xs,ys,zs,xe,ye,ze,wal->x,wal->y,wal2->x,wal2->y,&intx,&inty,&intz) != 0) { nextsector = wal->nextsector; if (nextsector < 0) return 0; if (intz <= sec->ceilingz) return 0; if (intz >= sec->floorz) return 0; nsec = §or[nextsector]; if (intz <= nsec->ceilingz) return 0; if (intz >= nsec->floorz) return 0; for(i=danum-1;i>=0;i--) if (clipsectorlist[i] == nextsector) break; if (i < 0) clipsectorlist[danum++] = nextsector; } } if (clipsectorlist[dacnt] == sectnume) return 1; } return 0; } int32_t cansee(int32_t x1, int32_t y1, int32_t z1, int16_t sect1, int32_t x2, int32_t y2, int32_t z2, int16_t sect2) { if (enginecompatibility_mode == ENGINECOMPATIBILITY_19950829) return cansee_old(x1, y1, z1, sect1, x2, y2, z2, sect2); int32_t dacnt, danum; const int32_t x21 = x2-x1, y21 = y2-y1, z21 = z2-z1; static uint8_t sectbitmap[(MAXSECTORS+7)>>3]; #ifdef YAX_ENABLE int16_t pendingsectnum; vec3_t pendingvec; // Negative sectnums can happen, for example if the player is using noclip. // MAXSECTORS can happen from C-CON, e.g. canseespr with a sprite not in // the game world. if ((unsigned)sect1 >= MAXSECTORS || (unsigned)sect2 >= MAXSECTORS) return 0; Bmemset(&pendingvec, 0, sizeof(vec3_t)); // compiler-happy #endif Bmemset(sectbitmap, 0, sizeof(sectbitmap)); #ifdef YAX_ENABLE restart_grand: #endif if (x1 == x2 && y1 == y2) return (sect1 == sect2); #ifdef YAX_ENABLE pendingsectnum = -1; #endif sectbitmap[sect1>>3] |= pow2char[sect1&7]; clipsectorlist[0] = sect1; danum = 1; for (dacnt=0; dacntwallnum,wal=(uwallptr_t)&wall[sec->wallptr]; cnt>0; cnt--,wal++) { auto const wal2 = (uwallptr_t)&wall[wal->point2]; const int32_t x31 = wal->x-x1, x34 = wal->x-wal2->x; const int32_t y31 = wal->y-y1, y34 = wal->y-wal2->y; int32_t x, y, z, nexts, t, bot; int32_t cfz[2]; bot = y21*x34-x21*y34; if (bot <= 0) continue; // XXX: OVERFLOW t = y21*x31-x21*y31; if ((unsigned)t >= (unsigned)bot) continue; t = y31*x34-x31*y34; if ((unsigned)t >= (unsigned)bot) { #ifdef YAX_ENABLE if (t >= bot) { int32_t cf, frac, ns; for (cf=0; cf<2; cf++) { if ((cf==0 && bn[0]>=0 && z1 > cfz1[0] && cfz2[0] > z2) || (cf==1 && bn[1]>=0 && z1 < cfz1[1] && cfz2[1] < z2)) { if ((cfz1[cf]-cfz2[cf])-(z1-z2)==0) continue; frac = divscale24(z1-cfz1[cf], (z1-z2)-(cfz1[cf]-cfz2[cf])); if ((unsigned)frac >= (1<<24)) continue; x = x1 + mulscale24(x21,frac); y = y1 + mulscale24(y21,frac); ns = yax_getneighborsect(x, y, dasectnum, cf); if (ns < 0) continue; if (!(sectbitmap[ns>>3] & pow2char[ns&7]) && pendingsectnum==-1) { sectbitmap[ns>>3] |= pow2char[ns&7]; pendingsectnum = ns; pendingvec.x = x; pendingvec.y = y; pendingvec.z = z1 + mulscale24(z21,frac); } } } } #endif continue; } nexts = wal->nextsector; #ifdef YAX_ENABLE if (bn[0]<0 && bn[1]<0) #endif if (nexts < 0 || wal->cstat&32) return 0; t = divscale24(t,bot); x = x1 + mulscale24(x21,t); y = y1 + mulscale24(y21,t); z = z1 + mulscale24(z21,t); getzsofslope(dasectnum, x,y, &cfz[0],&cfz[1]); if (z <= cfz[0] || z >= cfz[1]) { #ifdef YAX_ENABLE int32_t cf, frac; // XXX: Is this any good? for (cf=0; cf<2; cf++) if ((cf==0 && bn[0]>=0 && z <= cfz[0] && z1 >= cfz1[0]) || (cf==1 && bn[1]>=0 && z >= cfz[1] && z1 <= cfz1[1])) { if ((cfz1[cf]-cfz[cf])-(z1-z)==0) continue; frac = divscale24(z1-cfz1[cf], (z1-z)-(cfz1[cf]-cfz[cf])); t = mulscale24(t, frac); if ((unsigned)t < (1<<24)) { x = x1 + mulscale24(x21,t); y = y1 + mulscale24(y21,t); nexts = yax_getneighborsect(x, y, dasectnum, cf); if (nexts >= 0) goto add_nextsector; } } #endif return 0; } #ifdef YAX_ENABLE if (nexts < 0 || (wal->cstat&32)) return 0; #endif getzsofslope(nexts, x,y, &cfz[0],&cfz[1]); if (z <= cfz[0] || z >= cfz[1]) return 0; add_nextsector: if (!(sectbitmap[nexts>>3] & pow2char[nexts&7])) { sectbitmap[nexts>>3] |= pow2char[nexts&7]; clipsectorlist[danum++] = nexts; } } #ifdef YAX_ENABLE if (pendingsectnum>=0) { sect1 = pendingsectnum; x1 = pendingvec.x; y1 = pendingvec.y; z1 = pendingvec.z; goto restart_grand; } #endif } if (sectbitmap[sect2>>3] & pow2char[sect2&7]) return 1; return 0; } // // neartag // void neartag(int32_t xs, int32_t ys, int32_t zs, int16_t sectnum, int16_t ange, int16_t *neartagsector, int16_t *neartagwall, int16_t *neartagsprite, int32_t *neartaghitdist, /* out */ int32_t neartagrange, uint8_t tagsearch, int32_t (*blacklist_sprite_func)(int32_t)) { int16_t tempshortcnt, tempshortnum; const int32_t vx = mulscale14(sintable[(ange+2560)&2047],neartagrange); const int32_t vy = mulscale14(sintable[(ange+2048)&2047],neartagrange); vec3_t hitv = { xs+vx, ys+vy, 0 }; const vec3_t sv = { xs, ys, zs }; *neartagsector = -1; *neartagwall = -1; *neartagsprite = -1; *neartaghitdist = 0; if (sectnum < 0 || (tagsearch & 3) == 0) return; clipsectorlist[0] = sectnum; tempshortcnt = 0; tempshortnum = 1; do { const int32_t dasector = clipsectorlist[tempshortcnt]; const int32_t startwall = sector[dasector].wallptr; const int32_t endwall = startwall + sector[dasector].wallnum - 1; uwallptr_t wal; int32_t z; for (z=startwall,wal=(uwallptr_t)&wall[startwall]; z<=endwall; z++,wal++) { auto const wal2 = (uwallptr_t)&wall[wal->point2]; const int32_t nextsector = wal->nextsector; const int32_t x1=wal->x, y1=wal->y, x2=wal2->x, y2=wal2->y; int32_t intx, inty, intz, good = 0; if (nextsector >= 0) { if ((tagsearch&1) && sector[nextsector].lotag) good |= 1; if ((tagsearch&2) && sector[nextsector].hitag) good |= 1; } if ((tagsearch&1) && wal->lotag) good |= 2; if ((tagsearch&2) && wal->hitag) good |= 2; if ((good == 0) && (nextsector < 0)) continue; if ((coord_t)(x1-xs)*(y2-ys) < (coord_t)(x2-xs)*(y1-ys)) continue; if (lintersect(xs,ys,zs,hitv.x,hitv.y,hitv.z,x1,y1,x2,y2,&intx,&inty,&intz) == 1) { if (good != 0) { if (good&1) *neartagsector = nextsector; if (good&2) *neartagwall = z; *neartaghitdist = dmulscale14(intx-xs,sintable[(ange+2560)&2047],inty-ys,sintable[(ange+2048)&2047]); hitv.x = intx; hitv.y = inty; hitv.z = intz; } if (nextsector >= 0) { int32_t zz; for (zz=tempshortnum-1; zz>=0; zz--) if (clipsectorlist[zz] == nextsector) break; if (zz < 0) clipsectorlist[tempshortnum++] = nextsector; } } } tempshortcnt++; if (tagsearch & 4) continue; // skip sprite search for (z=headspritesect[dasector]; z>=0; z=nextspritesect[z]) { auto const spr = (uspriteptr_t)&sprite[z]; if (blacklist_sprite_func && blacklist_sprite_func(z)) continue; if (((tagsearch&1) && spr->lotag) || ((tagsearch&2) && spr->hitag)) { if (try_facespr_intersect(spr, sv, vx, vy, 0, &hitv, 1)) { *neartagsprite = z; *neartaghitdist = dmulscale14(hitv.x-xs, sintable[(ange+2560)&2047], hitv.y-ys, sintable[(ange+2048)&2047]); } } } } while (tempshortcnt < tempshortnum); } // // dragpoint // // flags: // 1: don't reset walbitmap[] (the bitmap of already dragged vertices) // 2: In the editor, do wall[].cstat |= (1<<14) also for the lastwall(). void dragpoint(int16_t pointhighlight, int32_t dax, int32_t day, uint8_t flags) { int32_t i, numyaxwalls=0; static int16_t yaxwalls[MAXWALLS]; uint8_t *const walbitmap = (uint8_t *)tempbuf; if ((flags&1)==0) Bmemset(walbitmap, 0, (numwalls+7)>>3); yaxwalls[numyaxwalls++] = pointhighlight; for (i=0; i>3] |= pow2char[w&7]; for (YAX_ITER_WALLS(w, j, tmpcf)) { if ((walbitmap[j>>3]&pow2char[j&7])==0) { walbitmap[j>>3] |= pow2char[j&7]; yaxwalls[numyaxwalls++] = j; } } if (!clockwise) //search points CCW { if (wall[w].nextwall >= 0) w = wall[wall[w].nextwall].point2; else { w = tmpstartwall; clockwise = 1; } } cnt--; if (cnt==0) { Printf("dragpoint %d: infloop!\n", pointhighlight); i = numyaxwalls; break; } if (clockwise) { int32_t thelastwall = lastwall(w); if (wall[thelastwall].nextwall >= 0) w = wall[thelastwall].nextwall; else break; } if ((walbitmap[w>>3] & pow2char[w&7])) { if (clockwise) break; w = tmpstartwall; clockwise = 1; continue; } } } if (editstatus) { int32_t w; // TODO: extern a separate bitmap instead? for (w=0; w>3] & pow2char[w&7]) { editwall[w>>3] |= 1<<(w&7); if (flags&2) { int wn = lastwall(w); editwall[wn>>3] |= 1<<(wn&7); } } } } // // lastwall // int32_t lastwall(int16_t point) { if (point > 0 && wall[point-1].point2 == point) return point-1; int i = point, cnt = numwalls; do { int const j = wall[i].point2; if (j == point) { point = i; break; } i = j; } while (--cnt); return point; } ////////// UPDATESECTOR* FAMILY OF FUNCTIONS ////////// /* Different "is inside" predicates. * NOTE: The redundant bound checks are expected to be optimized away in the * inlined code. */ static FORCE_INLINE CONSTEXPR int inside_exclude_p(int32_t const x, int32_t const y, int const sectnum, const uint8_t *excludesectbitmap) { return (sectnum>=0 && !bitmap_test(excludesectbitmap, sectnum) && inside_p(x, y, sectnum)); } /* NOTE: no bound check */ static inline int inside_z_p(int32_t const x, int32_t const y, int32_t const z, int const sectnum) { int32_t cz, fz; getzsofslope(sectnum, x, y, &cz, &fz); return (z >= cz && z <= fz && inside_p(x, y, sectnum)); } int32_t getwalldist(vec2_t const in, int const wallnum) { vec2_t closest; getclosestpointonwall_internal(in, wallnum, &closest); return klabs(closest.x - in.x) + klabs(closest.y - in.y); } int32_t getwalldist(vec2_t const in, int const wallnum, vec2_t * const out) { getclosestpointonwall_internal(in, wallnum, out); return klabs(out->x - in.x) + klabs(out->y - in.y); } int32_t getsectordist(vec2_t const in, int const sectnum, vec2_t * const out /*= nullptr*/) { if (inside_p(in.x, in.y, sectnum)) { if (out) *out = in; return 0; } int32_t distance = INT32_MAX; auto const sec = (usectorptr_t)§or[sectnum]; int const startwall = sec->wallptr; int const endwall = sec->wallptr + sec->wallnum; auto uwal = (uwallptr_t)&wall[startwall]; vec2_t closest = {}; for (int j = startwall; j < endwall; j++, uwal++) { vec2_t p; int32_t const walldist = getwalldist(in, j, &p); if (walldist < distance) { distance = walldist; closest = p; } } if (out) *out = closest; return distance; } int findwallbetweensectors(int sect1, int sect2) { if (sector[sect1].wallnum > sector[sect2].wallnum) swaplong(§1, §2); auto const sec = (usectorptr_t)§or[sect1]; int const last = sec->wallptr + sec->wallnum; for (int i = sec->wallptr; i < last; i++) if (wall[i].nextsector == sect2) return i; return -1; } // // updatesector[z] // void updatesector(int32_t const x, int32_t const y, int16_t * const sectnum) { if (enginecompatibility_mode != ENGINECOMPATIBILITY_NONE) { if (inside_p(x, y, *sectnum)) return; if ((unsigned)*sectnum < (unsigned)numsectors) { const uwalltype *wal = (uwalltype *)&wall[sector[*sectnum].wallptr]; int wallsleft = sector[*sectnum].wallnum; do { int const next = wal->nextsector; if (inside_p(x, y, next)) SET_AND_RETURN(*sectnum, next); wal++; } while (--wallsleft); } } else { int16_t sect = *sectnum; updatesectorneighbor(x, y, §, INITIALUPDATESECTORDIST, MAXUPDATESECTORDIST); if (sect != -1) SET_AND_RETURN(*sectnum, sect); } // we need to support passing in a sectnum of -1, unfortunately for (int i = numsectors - 1; i >= 0; --i) if (inside_p(x, y, i)) SET_AND_RETURN(*sectnum, i); *sectnum = -1; } void updatesectorexclude(int32_t const x, int32_t const y, int16_t * const sectnum, const uint8_t * const excludesectbitmap) { if (inside_exclude_p(x, y, *sectnum, excludesectbitmap)) return; if (*sectnum >= 0 && *sectnum < numsectors) { auto wal = (uwallptr_t)&wall[sector[*sectnum].wallptr]; int wallsleft = sector[*sectnum].wallnum; do { int const next = wal->nextsector; if (inside_exclude_p(x, y, next, excludesectbitmap)) SET_AND_RETURN(*sectnum, next); wal++; } while (--wallsleft); } for (bssize_t i=numsectors-1; i>=0; --i) if (inside_exclude_p(x, y, i, excludesectbitmap)) SET_AND_RETURN(*sectnum, i); *sectnum = -1; } // new: if *sectnum >= MAXSECTORS, *sectnum-=MAXSECTORS is considered instead // as starting sector and the 'initial' z check is skipped // (not initial anymore because it follows the sector updating due to TROR) void updatesectorz(int32_t const x, int32_t const y, int32_t const z, int16_t * const sectnum) { if (enginecompatibility_mode != ENGINECOMPATIBILITY_NONE) { if ((uint32_t)(*sectnum) < 2*MAXSECTORS) { int32_t nofirstzcheck = 0; if (*sectnum >= MAXSECTORS) { *sectnum -= MAXSECTORS; nofirstzcheck = 1; } // this block used to be outside the "if" and caused crashes in Polymost Mapster32 int32_t cz, fz; getzsofslope(*sectnum, x, y, &cz, &fz); #ifdef YAX_ENABLE if (z < cz) { int const next = yax_getneighborsect(x, y, *sectnum, YAX_CEILING); if (next >= 0 && z >= getceilzofslope(next, x, y)) SET_AND_RETURN(*sectnum, next); } if (z > fz) { int const next = yax_getneighborsect(x, y, *sectnum, YAX_FLOOR); if (next >= 0 && z <= getflorzofslope(next, x, y)) SET_AND_RETURN(*sectnum, next); } #endif if (nofirstzcheck || (z >= cz && z <= fz)) if (inside_p(x, y, *sectnum)) return; uwalltype const * wal = (uwalltype *)&wall[sector[*sectnum].wallptr]; int wallsleft = sector[*sectnum].wallnum; do { // YAX: TODO: check neighboring sectors here too? int const next = wal->nextsector; if (next>=0 && inside_z_p(x,y,z, next)) SET_AND_RETURN(*sectnum, next); wal++; } while (--wallsleft); } } else { int16_t sect = *sectnum; updatesectorneighborz(x, y, z, §, INITIALUPDATESECTORDIST, MAXUPDATESECTORDIST); if (sect != -1) SET_AND_RETURN(*sectnum, sect); } // we need to support passing in a sectnum of -1, unfortunately for (int i = numsectors - 1; i >= 0; --i) if (inside_z_p(x, y, z, i)) SET_AND_RETURN(*sectnum, i); *sectnum = -1; } void updatesectorneighbor(int32_t const x, int32_t const y, int16_t * const sectnum, int32_t initialMaxDistance /*= INITIALUPDATESECTORDIST*/, int32_t maxDistance /*= MAXUPDATESECTORDIST*/) { int const initialsectnum = *sectnum; if ((unsigned)initialsectnum < (unsigned)numsectors && getsectordist({x, y}, initialsectnum) <= initialMaxDistance) { if (inside_p(x, y, initialsectnum)) return; static int16_t sectlist[MAXSECTORS]; static uint8_t sectbitmap[(MAXSECTORS+7)>>3]; int16_t nsecs; bfirst_search_init(sectlist, sectbitmap, &nsecs, MAXSECTORS, initialsectnum); for (int sectcnt=0; sectcntwallptr; int const endwall = sec->wallptr + sec->wallnum; auto uwal = (uwallptr_t)&wall[startwall]; for (int j=startwall; jnextsector >= 0 && getsectordist({x, y}, uwal->nextsector) <= maxDistance) bfirst_search_try(sectlist, sectbitmap, &nsecs, uwal->nextsector); } } *sectnum = -1; } void updatesectorneighborz(int32_t const x, int32_t const y, int32_t const z, int16_t * const sectnum, int32_t initialMaxDistance /*= 0*/, int32_t maxDistance /*= 0*/) { bool nofirstzcheck = false; if (*sectnum >= MAXSECTORS && *sectnum - MAXSECTORS < numsectors) { *sectnum -= MAXSECTORS; nofirstzcheck = true; } uint32_t const correctedsectnum = (unsigned)*sectnum; if (correctedsectnum < (unsigned)numsectors && getsectordist({x, y}, correctedsectnum) <= initialMaxDistance) { int32_t cz, fz; getzsofslope(correctedsectnum, x, y, &cz, &fz); #ifdef YAX_ENABLE if (z < cz) { int const next = yax_getneighborsect(x, y, correctedsectnum, YAX_CEILING); if (next >= 0 && z >= getceilzofslope(next, x, y)) SET_AND_RETURN(*sectnum, next); } if (z > fz) { int const next = yax_getneighborsect(x, y, correctedsectnum, YAX_FLOOR); if (next >= 0 && z <= getflorzofslope(next, x, y)) SET_AND_RETURN(*sectnum, next); } #endif if ((nofirstzcheck || (z >= cz && z <= fz)) && inside_p(x, y, *sectnum)) return; static int16_t sectlist[MAXSECTORS]; static uint8_t sectbitmap[(MAXSECTORS+7)>>3]; int16_t nsecs; bfirst_search_init(sectlist, sectbitmap, &nsecs, MAXSECTORS, correctedsectnum); for (int sectcnt=0; sectcntwallptr; int const endwall = sec->wallptr + sec->wallnum; auto uwal = (uwallptr_t)&wall[startwall]; for (int j=startwall; jnextsector >= 0 && getsectordist({x, y}, uwal->nextsector) <= maxDistance) bfirst_search_try(sectlist, sectbitmap, &nsecs, uwal->nextsector); } } *sectnum = -1; } // // rotatepoint // void rotatepoint(vec2_t const pivot, vec2_t p, int16_t const daang, vec2_t * const p2) { int const dacos = sintable[(daang+2560)&2047]; int const dasin = sintable[(daang+2048)&2047]; p.x -= pivot.x; p.y -= pivot.y; p2->x = dmulscale14(p.x, dacos, -p.y, dasin) + pivot.x; p2->y = dmulscale14(p.y, dacos, p.x, dasin) + pivot.y; } int32_t setaspect_new_use_dimen = 0; void videoSetCorrectedAspect() { if (r_usenewaspect && newaspect_enable && videoGetRenderMode() != REND_POLYMER) { // In DOS the game world is displayed with an aspect of 1.28 instead 1.333, // meaning we have to stretch it by a factor of 1.25 instead of 1.2 // to get perfect squares int32_t yx = (65536 * 5) / 4; int32_t vr, y, x; const int32_t xd = setaspect_new_use_dimen ? xdimen : xdim; const int32_t yd = setaspect_new_use_dimen ? ydimen : ydim; x = xd; y = yd; vr = divscale16(x*3, y*4); renderSetAspect(vr, yx); } else renderSetAspect(65536, divscale16(ydim*320, xdim*200)); } // // setview // void videoSetViewableArea(int32_t x1, int32_t y1, int32_t x2, int32_t y2) { windowxy1.x = x1; windowxy1.y = y1; windowxy2.x = x2; windowxy2.y = y2; xdimen = (x2-x1)+1; halfxdimen = (xdimen>>1); xdimenrecip = divscale32(1L,xdimen); ydimen = (y2-y1)+1; fxdimen = (float) xdimen; #ifdef USE_OPENGL fydimen = (float) ydimen; #endif videoSetCorrectedAspect(); } // // setaspect // void renderSetAspect(int32_t daxrange, int32_t daaspect) { if (daxrange == 65536) daxrange--; // This doesn't work correctly with 65536. All other values are fine. No idea where this is evaluated wrong. viewingrange = daxrange; viewingrangerecip = divscale32(1,daxrange); #ifdef USE_OPENGL fviewingrange = (float) daxrange; #endif yxaspect = daaspect; xyaspect = divscale32(1,yxaspect); xdimenscale = scale(xdimen,yxaspect,320); xdimscale = scale(320,xyaspect,xdimen); } #include "v_2ddrawer.h" // // rotatesprite // void rotatesprite_(int32_t sx, int32_t sy, int32_t z, int16_t a, int16_t picnum, int8_t dashade, uint8_t dapalnum, int32_t dastat, uint8_t daalpha, uint8_t dablend, int32_t cx1, int32_t cy1, int32_t cx2, int32_t cy2) { if ((unsigned)picnum >= MAXTILES) return; if ((cx1 > cx2) || (cy1 > cy2)) return; if (z <= 16) return; tileUpdatePicnum(&picnum, (int16_t)0xc000); if ((tilesiz[picnum].x <= 0) || (tilesiz[picnum].y <= 0)) return; if (r_rotatespritenowidescreen) { dastat |= RS_STRETCH; dastat &= ~RS_ALIGN_MASK; } if (hw_models && tile2model[picnum].hudmem[(dastat & 4) >> 2]) { polymost_dorotatespritemodel(sx, sy, z, a, picnum, dashade, dapalnum, dastat, daalpha, dablend, guniqhudid); return; } // We must store all calls in the 2D drawer so that the backend can operate on a clean 3D view. twod->rotatesprite(sx, sy, z, a, picnum, dashade, dapalnum, dastat, daalpha, dablend, cx1, cy1, cx2, cy2); // RS_PERM code was removed because the current backend supports only one page that needs to be redrawn each frame in which case the perm list was skipped anyway. } // // clearview // void videoClearViewableArea(int32_t dacol) { if (!in3dmode() && dacol != -1) return; if (dacol == -1) dacol = 0; palette_t const p = paletteGetColor(dacol); GLInterface.ClearScreen(PalEntry(p.r, p.g, p.b), false); } // // clearallviews // void videoClearScreen(int32_t dacol) { if (!in3dmode()) return; palette_t const p = paletteGetColor(dacol); GLInterface.ClearScreen(PalEntry(255, p.r, p.g, p.b)); } //MUST USE RESTOREFORDRAWROOMS AFTER DRAWING static int32_t setviewcnt = 0; // interface layers use this now static int32_t bakxsiz, bakysiz; static vec2_t bakwindowxy1, bakwindowxy2; // // setviewtotile // void renderSetTarget(int16_t tilenume, int32_t xsiz, int32_t ysiz) { if (setviewcnt > 0) return; if (xsiz <= 0 || ysiz <= 0) return; OpenGLRenderer::GLRenderer->StartOffscreen(); OpenGLRenderer::GLRenderer->BindToFrameBuffer(TileFiles.tiles[tilenume]); //DRAWROOMS TO TILE BACKUP&SET CODE bakxsiz = xdim; bakysiz = ydim; bakwindowxy1 = windowxy1; bakwindowxy2 = windowxy2; setviewcnt++; xdim = ysiz*4; ydim = xsiz*4; videoSetViewableArea(0,0,ysiz*4-1,xsiz*4-1); renderSetAspect(65536,65536); } // // setviewback // void renderRestoreTarget() { if (setviewcnt <= 0) return; setviewcnt--; OpenGLRenderer::GLRenderer->EndOffscreen(); xdim = bakxsiz; ydim = bakysiz; videoSetViewableArea(bakwindowxy1.x,bakwindowxy1.y, bakwindowxy2.x,bakwindowxy2.y); } // // preparemirror // void renderPrepareMirror(int32_t dax, int32_t day, int32_t daz, fix16_t daang, fix16_t dahoriz, int16_t dawall, int32_t *tposx, int32_t *tposy, fix16_t *tang) { const int32_t x = wall[dawall].x, dx = wall[wall[dawall].point2].x-x; const int32_t y = wall[dawall].y, dy = wall[wall[dawall].point2].y-y; const int32_t j = dx*dx + dy*dy; if (j == 0) return; int i = ((dax-x)*dx + (day-y)*dy)<<1; *tposx = (x<<1) + scale(dx,i,j) - dax; *tposy = (y<<1) + scale(dy,i,j) - day; *tang = (fix16_from_int(getangle(dx, dy) << 1) - daang) & 0x7FFFFFF; inpreparemirror = 1; #ifdef USE_OPENGL if (videoGetRenderMode() == REND_POLYMOST) polymost_prepareMirror(dax, day, daz, daang, dahoriz, dawall); #endif } // // completemirror // void renderCompleteMirror(void) { polymost_completeMirror(); inpreparemirror = 0; } // // sectorofwall // static int32_t sectorofwall_internal(int16_t wallNum) { native_t gap = numsectors>>1, sectNum = gap; while (gap > 1) { gap >>= 1; native_t const n = !!(sector[sectNum].wallptr < wallNum); sectNum += (n ^ (n - 1)) * gap; } while (sector[sectNum].wallptr > wallNum) sectNum--; while (sector[sectNum].wallptr + sector[sectNum].wallnum <= wallNum) sectNum++; return sectNum; } int32_t sectorofwall(int16_t wallNum) { if (EDUKE32_PREDICT_FALSE((unsigned)wallNum >= (unsigned)numwalls)) return -1; native_t const w = wall[wallNum].nextwall; return ((unsigned)w < MAXWALLS) ? wall[w].nextsector : sectorofwall_internal(wallNum); } int32_t sectorofwall_noquick(int16_t wallNum) { if (EDUKE32_PREDICT_FALSE((unsigned) wallNum >= (unsigned) numwalls)) return -1; return sectorofwall_internal(wallNum); } int32_t getceilzofslopeptr(usectorptr_t sec, int32_t dax, int32_t day) { if (!(sec->ceilingstat&2)) return sec->ceilingz; auto const wal = (uwallptr_t)&wall[sec->wallptr]; auto const wal2 = (uwallptr_t)&wall[wal->point2]; vec2_t const w = *(vec2_t const *)wal; vec2_t const d = { wal2->x - w.x, wal2->y - w.y }; int const i = nsqrtasm(uhypsq(d.x,d.y))<<5; if (i == 0) return sec->ceilingz; int const j = dmulscale3(d.x, day-w.y, -d.y, dax-w.x); int const shift = enginecompatibility_mode != ENGINECOMPATIBILITY_NONE ? 0 : 1; return sec->ceilingz + (scale(sec->ceilingheinum,j>>shift,i)<floorstat&2)) return sec->floorz; auto const wal = (uwallptr_t)&wall[sec->wallptr]; auto const wal2 = (uwallptr_t)&wall[wal->point2]; vec2_t const w = *(vec2_t const *)wal; vec2_t const d = { wal2->x - w.x, wal2->y - w.y }; int const i = nsqrtasm(uhypsq(d.x,d.y))<<5; if (i == 0) return sec->floorz; int const j = dmulscale3(d.x, day-w.y, -d.y, dax-w.x); int const shift = enginecompatibility_mode != ENGINECOMPATIBILITY_NONE ? 0 : 1; return sec->floorz + (scale(sec->floorheinum,j>>shift,i)<ceilingz; *florz = sec->floorz; if (((sec->ceilingstat|sec->floorstat)&2) != 2) return; auto const wal = (uwallptr_t)&wall[sec->wallptr]; auto const wal2 = (uwallptr_t)&wall[wal->point2]; vec2_t const d = { wal2->x - wal->x, wal2->y - wal->y }; int const i = nsqrtasm(uhypsq(d.x,d.y))<<5; if (i == 0) return; int const j = dmulscale3(d.x,day-wal->y, -d.y,dax-wal->x); int const shift = enginecompatibility_mode != ENGINECOMPATIBILITY_NONE ? 0 : 1; if (sec->ceilingstat&2) *ceilz += scale(sec->ceilingheinum,j>>shift,i)<floorstat&2) *florz += scale(sec->floorheinum,j>>shift,i)<= 0) { ceiling: *pCeilZ = getcorrectceilzofslope(testSector, playerX, playerY); didCeiling = 1; } } if ((sector[sectNum].floorstat & 512) == 0) { testSector = yax_getneighborsect(playerX, playerY, sectNum, YAX_FLOOR); if (testSector >= 0) { floor: *pFloorZ = getcorrectflorzofslope(testSector, playerX, playerY); didFloor = 1; } } testSector = sectNum; if (!didCeiling) goto ceiling; else if (!didFloor) goto floor; } #endif // // alignceilslope // void alignceilslope(int16_t dasect, int32_t x, int32_t y, int32_t z) { auto const wal = (uwallptr_t)&wall[sector[dasect].wallptr]; const int32_t dax = wall[wal->point2].x-wal->x; const int32_t day = wall[wal->point2].y-wal->y; const int32_t i = (y-wal->y)*dax - (x-wal->x)*day; if (i == 0) return; sector[dasect].ceilingheinum = scale((z-sector[dasect].ceilingz)<<8, nsqrtasm(uhypsq(dax,day)), i); if (sector[dasect].ceilingheinum == 0) sector[dasect].ceilingstat &= ~2; else sector[dasect].ceilingstat |= 2; } // // alignflorslope // void alignflorslope(int16_t dasect, int32_t x, int32_t y, int32_t z) { auto const wal = (uwallptr_t)&wall[sector[dasect].wallptr]; const int32_t dax = wall[wal->point2].x-wal->x; const int32_t day = wall[wal->point2].y-wal->y; const int32_t i = (y-wal->y)*dax - (x-wal->x)*day; if (i == 0) return; sector[dasect].floorheinum = scale((z-sector[dasect].floorz)<<8, nsqrtasm(uhypsq(dax,day)), i); if (sector[dasect].floorheinum == 0) sector[dasect].floorstat &= ~2; else sector[dasect].floorstat |= 2; } // // loopnumofsector // int32_t loopnumofsector(int16_t sectnum, int16_t wallnum) { int32_t numloops = 0; const int32_t startwall = sector[sectnum].wallptr; const int32_t endwall = startwall + sector[sectnum].wallnum; for (bssize_t i=startwall; i= startwall+danumwalls) return; tmpwall = (uwalltype *)Xmalloc(danumwalls * sizeof(walltype)); Bmemcpy(tmpwall, &wall[startwall], danumwalls*sizeof(walltype)); numwallsofloop = 0; i = newfirstwall; do { numwallsofloop++; i = wall[i].point2; } while (i != newfirstwall); //Put correct loop at beginning dagoalloop = loopnumofsector(sectnum,newfirstwall); if (dagoalloop > 0) { j = 0; while (loopnumofsector(sectnum,j+startwall) != dagoalloop) j++; for (i=0; i= danumwalls) k -= danumwalls; Bmemcpy(&wall[startwall+i], &tmpwall[k], sizeof(walltype)); wall[startwall+i].point2 += danumwalls-startwall-j; if (wall[startwall+i].point2 >= danumwalls) wall[startwall+i].point2 -= danumwalls; wall[startwall+i].point2 += startwall; } newfirstwall += danumwalls-j; if (newfirstwall >= startwall+danumwalls) newfirstwall -= danumwalls; } for (i=0; i= numwallsofloop) k -= numwallsofloop; Bmemcpy(&wall[startwall+i], &tmpwall[k], sizeof(walltype)); wall[startwall+i].point2 += numwallsofloop-newfirstwall; if (wall[startwall+i].point2 >= numwallsofloop) wall[startwall+i].point2 -= numwallsofloop; wall[startwall+i].point2 += startwall; } for (i=startwall; i= 0) wall[wall[i].nextwall].nextwall = i; #ifdef YAX_ENABLE int16_t cb, fb; yax_getbunches(sectnum, &cb, &fb); if (cb>=0 || fb>=0) { for (i=startwall; i= 0) yax_setnextwall(j, YAX_FLOOR, i); j = yax_getnextwall(i, YAX_FLOOR); if (j >= 0) yax_setnextwall(j, YAX_CEILING, i); } } #endif Xfree(tmpwall); } // // setrendermode // int32_t videoSetRenderMode(int32_t renderer) { UNREFERENCED_PARAMETER(renderer); #ifdef USE_OPENGL renderer = REND_POLYMOST; basepalreset = 1; rendmode = renderer; if (videoGetRenderMode() >= REND_POLYMOST) glrendmode = rendmode; #endif return 0; } // // setrollangle // #ifdef USE_OPENGL void renderSetRollAngle(int32_t rolla) { gtang = (float)rolla * (fPI * (1.f/1024.f)); } #endif