// "Build Engine & Tools" Copyright (c) 1993-1997 Ken Silverman // "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 "build.h" #include "automap.h" #include "imagehelpers.h" #include "compat.h" #include "engine_priv.h" #include "palette.h" #include "gamecvars.h" #include "c_console.h" #include "v_2ddrawer.h" #include "v_draw.h" #include "stats.h" #include "razemenu.h" #include "version.h" #include "earcut.hpp" #include "gamestate.h" #include "inputstate.h" #include "printf.h" #include "gamecontrol.h" #include "render.h" #include "gamefuncs.h" #include "hw_voxels.h" #ifdef USE_OPENGL # include "mdsprite.h" # include "polymost.h" #include "v_video.h" #include "../../glbackend/glbackend.h" #include "gl_renderer.h" #endif spriteext_t spriteext[MAXSPRITES]; spritesmooth_t spritesmooth[MAXSPRITES]; sectortype sector[MAXSECTORS]; walltype wall[MAXWALLS]; spritetype sprite[MAXSPRITES]; int32_t r_rortexture = 0; int32_t r_rortexturerange = 0; int32_t r_rorphase = 0; int32_t mdtims, omdtims; float fcosglobalang, fsinglobalang; float fydimen, fviewingrange; uint8_t globalr = 255, globalg = 255, globalb = 255; int16_t pskybits_override = -1; static int32_t beforedrawrooms = 1; static int8_t tempbuf[MAXWALLS]; static int32_t no_radarang2 = 0; static int16_t radarang[1280]; const char *engineerrstr = "No error"; int32_t showfirstwall=0; int32_t showheightindicators=1; int32_t circlewall=-1; fixed_t global100horiz; // (-100..300)-scale horiz (the one passed to drawrooms) static FString printcoords(void) { FString str; str.Format( "pos.x: %d\n" "pos.y: %d\n" "pos.z: %d\n" "ang : %d\n" "horiz: %d\n", globalposx, globalposy, globalposz, globalang, FixedToInt(global100horiz) ); return str; } CCMD(printcoords) { Printf("%s", printcoords().GetChars()); } ADD_STAT(printcoords) { return printcoords(); } // 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; *closest = { (int32_t)(w.x + ((d.x * i) >> 30)), (int32_t)(w.y + ((d.y * i) >> 30)) }; } int32_t xdimen = -1, xdimenscale, xdimscale; float fxdimen = -1.f; int32_t ydimen; int32_t globalposx, globalposy, globalposz; fixed_t qglobalhoriz; float fglobalposx, fglobalposy, fglobalposz; int16_t globalang, globalcursectnum; fixed_t qglobalang; int32_t globalpal, globalfloorpal, cosglobalang, singlobalang; int32_t cosviewingrangeglobalang, sinviewingrangeglobalang; int32_t viewingrangerecip; static int32_t globalxpanning, globalypanning; int32_t globalshade, globalorientation; int16_t globalpicnum; static int32_t globaly1, globalx2; int16_t pointhighlight=-1, linehighlight=-1, highlightcnt=0; static int16_t numhits; char inpreparemirror = 0; // // Internal Engine Functions // // // 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) I_GetBuildTime() >> (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 int32_t engineLoadTables(void) { static char tablesloaded = 0; if (tablesloaded == 0) { int32_t i; for (i=0; i<=512; i++) sintable[i] = bsinf(i); 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] = atan((639.5 - i) / 160.) * (-64. / BAngRadian); for (i=0; i<640; i++) radarang[1279-i] = -radarang[i]; tablesloaded = 1; } return 0; } ////////// SPRITE LIST MANIPULATION FUNCTIONS ////////// ///// sector lists of sprites ///// // insert sprite at the head of sector list, change .sectnum 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 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 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) 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 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) { assert((unsigned)sectnum < MAXSECTORS); do_insertsprite_at_headofsect(newspritenum, sectnum); Numsprites++; } sprite[newspritenum].time = leveltimer++; return newspritenum; } // // deletesprite // int32_t (*deletesprite_replace)(int16_t spritenum) = NULL; int32_t deletesprite(int16_t spritenum) { Polymost::polymost_deletesprite(spritenum); if (deletesprite_replace) return deletesprite_replace(spritenum); assert((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 = (t << 24) / rayLengthSquared; *intersectionX = originX + MulScale(ray.x, t, 24); *intersectionY = originY + MulScale(ray.y, t, 24); *intersectionZ = originZ + MulScale(destZ-originZ, t, 24); 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 abs(originDiffCrossLineVec) > abs(rayCrossLineVec) // u is > 1 if abs(originDiffCrossRay) > abs(rayCrossLineVec) // where int32_t u = tabledivide64(((int64_t) originDiffCrossRay) << 24L, rayCrossLineVec); if (((originDiffCrossLineVec^rayCrossLineVec) & signBit) || ((originDiffCrossRay^rayCrossLineVec) & signBit) || abs(originDiffCrossLineVec) > abs(rayCrossLineVec) || abs(originDiffCrossRay) > abs(rayCrossLineVec)) { // line segments do not overlap return 0; } int64_t t = (int64_t(originDiffCrossLineVec) << 24) / rayCrossLineVec; // For sake of completeness/readability, alternative to the above approach for an early out & avoidance of an extra division: *intersectionX = originX + MulScale(ray.x, t, 24); *intersectionY = originY + MulScale(ray.y, t, 24); *intersectionZ = originZ + MulScale(destZ-originZ, t, 24); return 1; } // // rintersect (internal) // // returns: -1 if didn't intersect, coefficient IntToFixed(x3--x4 fraction) 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 = DivScale(topt, bot, 16); *intx = x1 + MulScale(vx, t, 16); *inty = y1 + MulScale(vy, t, 16); *intz = z1 + MulScale(vz, t, 16); t = DivScale(topu, bot, 16); 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 = (topt << 16) / bot; *intx = x1 + ((vx*t) >> 16); *inty = y1 + ((vy*t) >> 16); *intz = z1 + ((vz*t) >> 16); t = (topu << 16) / bot; assert((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 (auto& sky : multipskies) if (tilenum == sky.tilenum) { return &sky; } multipskies.Reserve(1); multipskies.Last() = {}; multipskies.Last().tilenum = tilenum; multipskies.Last().yscale = 65536; return &multipskies.Last(); } psky_t * defineSky(int32_t const tilenum, int horiz, int lognumtiles, const uint16_t *tileofs, int yoff, int yoff2) { auto sky = tileSetupSky(tilenum); sky->horizfrac = horiz; sky->lognumtiles = lognumtiles; sky->yoffs = yoff; sky->yoffs2 = yoff2 == 0x7fffffff ? yoff : yoff2; memcpy(sky->tileofs, tileofs, 2 << lognumtiles); return sky; } // Get properties of parallaxed sky to draw. // Returns: pointer to tile offset array. Sets-by-pointer the other three. const int16_t* getpsky(int32_t picnum, int32_t* dapyscale, int32_t* dapskybits, int32_t* dapyoffs, int32_t* daptileyscale, bool alt) { psky_t const* const psky = getpskyidx(picnum); if (dapskybits) *dapskybits = (pskybits_override == -1 ? psky->lognumtiles : pskybits_override); if (dapyscale) *dapyscale = (parallaxyscale_override == 0 ? psky->horizfrac : parallaxyscale_override); if (dapyoffs) *dapyoffs = (alt? psky->yoffs2 : psky->yoffs) + parallaxyoffs_override; if (daptileyscale) *daptileyscale = psky->yscale; return psky->tileofs; } // // initengine // int32_t engineInit(void) { engineLoadTables(); g_visibility = 512; if (!mdinited) mdinit(); return 0; } // // initspritelists // void (*initspritelists_replace)(void) = NULL; void initspritelists(void) { leveltimer = 0; if (initspritelists_replace) { initspritelists_replace(); return; } int32_t i; // initial list state for statnum lists: // // statnum 0: nil // statnum 1: nil // . . . // statnum MAXSTATUS-1: nil // "statnum MAXSTATUS": nil <- 0 <-> 1 <-> 2 <-> ... <-> MAXSPRITES-1 -> nil // // That is, the dummy MAXSTATUS statnum has all sprites. for (i=0; i= 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, int 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 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 (abs(xvect) > abs(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; } // 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 = (tileHeight(picnum)*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 -= tileTopOffset(picnum) *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]; memset(sectbitmap, 0, sizeof(sectbitmap)); if (x1 == x2 && y1 == y2) return (sect1 == sect2); sectbitmap[sect1>>3] |= (1 << (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) { continue; } nexts = wal->nextsector; if (nexts < 0 || wal->cstat&32) return 0; t = DivScale(t,bot, 24); x = x1 + MulScale(x21,t, 24); y = y1 + MulScale(y21,t, 24); z = z1 + MulScale(z21,t, 24); getzsofslope(dasectnum, x,y, &cfz[0],&cfz[1]); if (z <= cfz[0] || z >= cfz[1]) { return 0; } getzsofslope(nexts, x,y, &cfz[0],&cfz[1]); if (z <= cfz[0] || z >= cfz[1]) return 0; if (!(sectbitmap[nexts>>3] & (1 << (nexts&7)))) { sectbitmap[nexts>>3] |= (1 << (nexts&7)); clipsectorlist[danum++] = nexts; } } } if (sectbitmap[sect2>>3] & (1<<(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 = MulScale(bcos(ange), neartagrange, 14); const int32_t vy = MulScale(bsin(ange), neartagrange, 14); 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 = DMulScale(intx-xs, bcos(ange), inty-ys, bsin(ange), 14); 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 SectIterator it(dasector); while ((z = it.NextIndex()) >= 0) { auto const spr = (uspriteptr_t)&sprite[z]; if (spr->cstat & CSTAT_SPRITE_NOFIND) continue; 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 = DMulScale(hitv.x-xs, bcos(ange), hitv.y-ys, bsin(ange), 14); } } } } 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) memset(walbitmap, 0, (numwalls+7)>>3); yaxwalls[numyaxwalls++] = pointhighlight; for (i=0; i>3] |= (1<<(w&7)); 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] & (1<<(w&7)))) { if (clockwise) break; w = tmpstartwall; clockwise = 1; continue; } } } } // // 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 inline 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 abs(closest.x - in.x) + abs(closest.y - in.y); } int32_t getwalldist(vec2_t const in, int const wallnum, vec2_t * const out) { getclosestpointonwall_internal(in, wallnum, out); return abs(out->x - in.x) + abs(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) std::swap(sect1, sect2); 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) { 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; } // 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); if (nofirstzcheck || (z >= cz && z <= fz)) if (inside_p(x, y, *sectnum)) return; walltype const * wal = &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); 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 = bcos(daang); int const dasin = bsin(daang); p.x -= pivot.x; p.y -= pivot.y; p2->x = DMulScale(p.x, dacos, -p.y, dasin, 14) + pivot.x; p2->y = DMulScale(p.y, dacos, p.x, dasin, 14) + pivot.y; } // // 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; ydimen = (y2-y1)+1; fxdimen = (float) xdimen; fydimen = (float) ydimen; videoSetCorrectedAspect(); } #include "v_2ddrawer.h" //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 // FCanvasTexture* renderSetTarget(int16_t tilenume) { auto tex = tileGetTexture(tilenume); if (!tex || !tex->isHardwareCanvas()) return nullptr; auto canvas = static_cast(tex->GetTexture()); if (!canvas) return nullptr; int xsiz = tex->GetTexelWidth(), ysiz = tex->GetTexelHeight(); if (setviewcnt > 0 || xsiz <= 0 || ysiz <= 0) return nullptr; //DRAWROOMS TO TILE BACKUP&SET CODE bakxsiz = xdim; bakysiz = ydim; bakwindowxy1 = windowxy1; bakwindowxy2 = windowxy2; setviewcnt++; xdim = ysiz; ydim = xsiz; videoSetViewableArea(0,0,ysiz-1,xsiz-1); renderSetAspect(65536,65536); return canvas; } // // setviewback // void renderRestoreTarget() { if (setviewcnt <= 0) return; setviewcnt--; xdim = bakxsiz; ydim = bakysiz; videoSetViewableArea(bakwindowxy1.x,bakwindowxy1.y, bakwindowxy2.x,bakwindowxy2.y); } 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 = ksqrt(uhypsq(d.x,d.y))<<5; if (i == 0) return sec->ceilingz; int const j = DMulScale(d.x, day-w.y, -d.y, dax-w.x, 3); 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 = ksqrt(uhypsq(d.x,d.y))<<5; if (i == 0) return sec->floorz; int const j = DMulScale(d.x, day-w.y, -d.y, dax-w.x, 3); 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 = ksqrt(uhypsq(d.x,d.y))<<5; if (i == 0) return; int const j = DMulScale(d.x,day-wal->y, -d.y,dax-wal->x, 3); 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)<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, ksqrt(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, ksqrt(uhypsq(dax,day)), i); if (sector[dasect].floorheinum == 0) sector[dasect].floorstat &= ~2; else sector[dasect].floorstat |= 2; } int tilehasmodelorvoxel(int const tilenume, int pal) { return (mdinited && hw_models && tile2model[Ptile2tile(tilenume, pal)].modelid != -1) || (r_voxels && tiletovox[tilenume] != -1); }