raze-gles/source/build/src/engine.cpp
Christoph Oelckers 42b87362ee - When keeping around a dummy sprite, make sure that the engine's utilities cannot find it anymore.
This was causing issues with the master switch sprites in Duke that have to be kept for sound purposes.
Unfortunately, both hitscan and neartag are far too dumb to analyze sprites they may hit in any way and needed some help skipping such sprites.
2021-04-17 09:37:38 +02:00

3069 lines
88 KiB
C++

// "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 "scriptfile.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"
#ifdef USE_OPENGL
# include "mdsprite.h"
# include "polymost.h"
#include "v_video.h"
#include "../../glbackend/glbackend.h"
#include "gl_renderer.h"
#endif
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;
// This was on the cache but is permanently allocated, so put it into something static. This needs some rethinking anyway
static TArray<TArray<uint8_t>> voxelmemory;
int16_t tiletovox[MAXTILES];
#ifdef USE_OPENGL
char *voxfilenames[MAXVOXELS];
#endif
char g_haveVoxels;
//#define kloadvoxel loadvoxel
int32_t novoxmips = 1;
int32_t voxscale[MAXVOXELS];
static int32_t beforedrawrooms = 1;
int32_t globalflags;
static int8_t tempbuf[MAXWALLS];
// referenced from asm
int32_t reciptable[2048];
intptr_t asm1, asm2;
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];
const char *engineerrstr = "No error";
int32_t showfirstwall=0;
int32_t showheightindicators=1;
int32_t circlewall=-1;
int16_t editstatus = 0;
static 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();
}
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;
*closest = { (int32_t)(w.x + ((d.x * i) >> 30)), (int32_t)(w.y + ((d.y * i) >> 30)) };
}
int32_t xb1[MAXWALLSB]; // Polymost uses this as a temp array
static int32_t xb2[MAXWALLSB];
int32_t rx1[MAXWALLSB], ry1[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;
int32_t rxi[8], ryi[8];
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 xyaspect;
int32_t viewingrangerecip;
static int32_t globalxpanning, globalypanning;
int32_t globalshade, globalorientation;
int16_t globalpicnum;
static int32_t globaly1, globalx2;
int16_t sectorborder[256];
int16_t pointhighlight=-1, linehighlight=-1, highlightcnt=0;
int32_t halfxdim16, midydim16;
static_assert(MAXWALLSB < INT16_MAX);
int16_t numscans, numbunches;
static int16_t numhits;
int16_t searchit;
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
//
// 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];
if (spritesortcnt >= maxspritesonscreen)
return 1;
renderAddTSpriteFromSprite(z);
return 0;
}
//
// 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 = DMulScale(l2vect.x-l1vect.x, d.y, -d.x, l2vect.y-l1vect.y, 2); //p1(l2) vs. l1
int32_t t2 = DMulScale(l2p2vect.x-l1vect.x, d.y, -d.x, l2p2vect.y-l1vect.y, 2); //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 (DMulScale(globalposx-l1vect.x, d.y, -d.x, globalposy-l1vect.y, 2) ^ t1) >= 0;
d.x = l2p2vect.x-l2vect.x;
d.y = l2p2vect.y-l2vect.y;
t1 = DMulScale(l1vect.x-l2vect.x, d.y, -d.x, l1vect.y-l2vect.y, 2); //p1(l1) vs. l2
t2 = DMulScale(l1p2vect.x-l2vect.x, d.y, -d.x, l1p2vect.y-l2vect.y, 2); //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 (DMulScale(globalposx-l2vect.x,d.y,-d.x,globalposy-l2vect.y, 2) ^ t1) < 0;
return -2;
}
//
// 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 void renderDrawSprite(int32_t snum)
{
polymost_drawsprite(snum);
}
//
// drawmaskwall (internal)
//
static void renderDrawMaskedWall(int16_t damaskwallcnt)
{
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 (abs(int32_t(temp-2*root+1)) < abs(temp))
{
temp += 1-int(2*root);
root--;
}
while (abs(int32_t(temp+2*root+1)) < abs(temp))
{
temp += 2*root+1;
root++;
}
sqrtable_old[i] = root;
}
}
static int32_t engineLoadTables(void)
{
static char tablesloaded = 0;
if (tablesloaded == 0)
{
int32_t i;
initksqrt();
for (i=0; i<2048; i++)
reciptable[i] = DivScale(2048, i+2048, 30);
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];
for (i=0; i<5120; i++)
qradarang[i] = FloatToFixed(atan((5119.5 - i) / 1280.) * (-64. / BAngRadian));
for (i=0; i<5120; i++)
qradarang[10239-i] = -qradarang[i];
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)
{
assert((unsigned)sectnum < MAXSECTORS);
do_insertsprite_at_headofsect(newspritenum, sectnum);
Numsprites++;
}
return newspritenum;
}
//
// deletesprite
//
int32_t (*deletesprite_replace)(int16_t spritenum) = NULL;
void polymost_deletesprite(int num);
int32_t deletesprite(int16_t spritenum)
{
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)
{
auto sky = tileSetupSky(tilenum);
sky->horizfrac = horiz;
sky->lognumtiles = lognumtiles;
sky->yoffs = yoff;
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)
{
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 = psky->yoffs + parallaxyoffs_override;
if (daptileyscale)
*daptileyscale = psky->yscale;
return psky->tileofs;
}
//
// preinitengine
//
static spriteext_t spriteext_s[MAXSPRITES+MAXUNIQHUDID];
static spritesmooth_t spritesmooth_s[MAXSPRITES+MAXUNIQHUDID];
static sectortype sector_s[MAXSECTORS];
static walltype wall_s[MAXWALLS];
spritetype sprite_s[MAXSPRITES];
static tspritetype tsprite_s[MAXSPRITESONSCREEN];
int32_t enginePreInit(void)
{
sector = sector_s;
wall = wall_s;
sprite = sprite_s;
tsprite = tsprite_s;
spriteext = spriteext_s;
spritesmooth = spritesmooth_s;
return 0;
}
//
// initengine
//
int32_t engineInit(void)
{
if (engineLoadTables())
return 1;
xyaspect = -1;
voxelmemory.Reset();
for (int i=0; i<MAXTILES; i++)
tiletovox[i] = -1;
for (auto& v : voxscale) v = 65536;
memset(voxrotate, 0, sizeof(voxrotate));
paletteloaded = 0;
searchit = 0; searchstat = -1;
g_visibility = 512;
parallaxvisibility = 512;
maxspritesonscreen = MAXSPRITESONSCREEN;
GPalette.Init(MAXPALOOKUPS + 1); // one slot for each translation, plus a separate one for the base palettes.
gi->loadPalette();
#ifdef USE_OPENGL
if (!mdinited) mdinit();
#endif
return 0;
}
//
// uninitengine
//
void engineUnInit(void)
{
polymost_glreset();
freeallmodels();
# ifdef POLYMER
polymer_uninit();
# endif
TileFiles.CloseAll();
}
//
// initspritelists
//
void (*initspritelists_replace)(void) = NULL;
void initspritelists(void)
{
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<MAXSECTORS; i++) //Init doubly-linked sprite sector lists
headspritesect[i] = -1;
headspritesect[MAXSECTORS] = 0;
for (i=0; i<MAXSPRITES; i++)
{
prevspritesect[i] = i-1;
nextspritesect[i] = i+1;
sprite[i].sectnum = MAXSECTORS;
}
prevspritesect[0] = -1;
nextspritesect[MAXSPRITES-1] = -1;
for (i=0; i<MAXSTATUS; i++) //Init doubly-linked sprite status lists
headspritestat[i] = -1;
headspritestat[MAXSTATUS] = 0;
for (i=0; i<MAXSPRITES; i++)
{
prevspritestat[i] = i-1;
nextspritestat[i] = i+1;
sprite[i].statnum = MAXSTATUS;
}
prevspritestat[0] = -1;
nextspritestat[MAXSPRITES-1] = -1;
tailspritefree = MAXSPRITES-1;
Numsprites = 0;
}
void set_globalang(fixed_t const ang)
{
globalang = FixedToInt(ang)&2047;
qglobalang = ang & 0x7FFFFFF;
float const f_ang = FixedToFloat(ang);
float const fcosang = bcosf(f_ang);
float const fsinang = bsinf(f_ang);
#ifdef USE_OPENGL
fcosglobalang = fcosang;
fsinglobalang = fsinang;
#endif
cosglobalang = (int)fcosang;
singlobalang = (int)fsinang;
cosviewingrangeglobalang = MulScale(cosglobalang,viewingrange, 16);
sinviewingrangeglobalang = MulScale(singlobalang,viewingrange, 16);
}
//
// drawrooms
//
EXTERN_CVAR(Int, gl_fogmode)
int32_t renderDrawRoomsQ16(int32_t daposx, int32_t daposy, int32_t daposz,
fixed_t daang, fixed_t dahoriz, int16_t dacursectnum)
{
int32_t i;
if (gl_fogmode == 1) gl_fogmode = 2; // only radial fog works with Build's screwed up coordinate system.
set_globalpos(daposx, daposy, daposz);
set_globalang(daang);
global100horiz = dahoriz;
// xdimenscale is Scale(xdimen,yxaspect,320);
// normalization by viewingrange so that center-of-aim doesn't depend on it
qglobalhoriz = MulScale(dahoriz, DivScale(xdimenscale, viewingrange, 16), 16)+IntToFixed(ydimen>>1);
globalcursectnum = dacursectnum;
memset(gotsector, 0, sizeof(gotsector));
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);
assert(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)) };
}
}
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;
}
static inline int comparetsprites(int const k, int const l)
{
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;
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 (abs(spritesxyz[k].z-globalposz) != abs(spritesxyz[l].z-globalposz))
return abs(spritesxyz[k].z-globalposz)-abs(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<end-gap; i++)
for (bssize_t l=i; l>=start; l-=gap)
{
if (spritesxyz[l].y <= spritesxyz[l+gap].y) break;
std::swap(tspriteptr[l],tspriteptr[l+gap]);
std::swap(spritesxyz[l].x,spritesxyz[l+gap].x);
std::swap(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; k<j; k++)
{
auto const s = tspriteptr[k];
spritesxyz[k].z = s->z;
if ((s->cstat&48) != 32)
{
int32_t yoff = tileTopOffset(s->picnum) + s->yoffset;
int32_t yspan = (tileHeight(s->picnum) * s->yrepeat << 2);
spritesxyz[k].z -= (yoff*s->yrepeat)<<2;
if (!(s->cstat&128))
spritesxyz[k].z -= (yspan>>1);
if (abs(spritesxyz[k].z-globalposz) < (yspan>>1))
spritesxyz[k].z = globalposz;
}
}
for (bssize_t k=i+1; k<j; k++)
for (bssize_t l=i; l<k; l++)
if (comparetsprites(k, l) < 0)
{
std::swap(tspriteptr[k], tspriteptr[l]);
vec3_t tv3 = spritesxyz[k];
spritesxyz[k] = spritesxyz[l];
spritesxyz[l] = tv3;
}
}
i = j;
}
}
//
// drawmasks
//
void renderDrawMasks(void)
{
# define debugmask_add(dispidx, idx) do {} while (0)
int32_t i = spritesortcnt-1;
int32_t numSprites = spritesortcnt;
spritesortcnt = 0;
int32_t back = i;
for (; i >= 0; --i)
{
if (polymost_spriteHasTranslucency(&tsprite[i]))
{
tspriteptr[spritesortcnt] = &tsprite[i];
++spritesortcnt;
} else
{
tspriteptr[back] = &tsprite[i];
--back;
}
}
for (i=numSprites-1; i>=0; --i)
{
const int32_t xs = tspriteptr[i]->x-globalposx, ys = tspriteptr[i]->y-globalposy;
const int32_t yp = DMulScale(xs,cosviewingrangeglobalang,ys,sinviewingrangeglobalang, 6);
const int32_t modelp = polymost_spriteIsModelOrVoxel(tspriteptr[i]);
if (yp > (4<<8))
{
const int32_t xp = DMulScale(ys,cosglobalang,-xs,singlobalang, 6);
if (MulScale(labs(xp+yp),xdimen, 24) >= yp)
goto killsprite;
spritesxyz[i].x = Scale(xp+yp,xdimen<<7,yp);
}
else if ((tspriteptr[i]->cstat&48) == 0)
{
killsprite:
if (!modelp)
{
//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);
renderBeginScene();
GLInterface.EnableBlend(false);
GLInterface.EnableAlphaTest(true);
GLInterface.SetDepthBias(-2, -256);
if (spritesortcnt < numSprites)
{
i = spritesortcnt;
for (bssize_t i = spritesortcnt; i < numSprites;)
{
int32_t py = spritesxyz[i].y;
int32_t pcstat = tspriteptr[i]->cstat & 48;
int32_t pangle = tspriteptr[i]->ang;
int j = i + 1;
if (!polymost_spriteIsModelOrVoxel(tspriteptr[i]))
{
while (j < numSprites && py == spritesxyz[j].y && pcstat == (tspriteptr[j]->cstat & 48) && (pcstat != 16 || pangle == tspriteptr[j]->ang)
&& !polymost_spriteIsModelOrVoxel(tspriteptr[j]))
{
j++;
}
}
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;
}
}
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);
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 = 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);
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; jj<numpts; jj++)
{
spr.x = (float)xx[jj];
spr.y = (float)yy[jj];
if (!sameside(&maskeq, &spr, &pos)) // behind the maskwall,
if ((sameside(&p1eq, &middle, &spr) && // inside the 'cone',
sameside(&p2eq, &middle, &spr))
|| !sameside(&pineq, &middle, &spr)) // or on the other outside.
{
ok = 1;
break;
}
}
}
if (ok)
{
debugmask_add(i | 32768, tspr->owner);
renderDrawSprite(i);
tspriteptr[i] = NULL;
}
}
}
}
debugmask_add(maskwall[maskwallcnt], thewall[maskwall[maskwallcnt]]);
renderDrawMaskedWall(maskwallcnt);
}
while (spritesortcnt)
{
--spritesortcnt;
if (tspriteptr[spritesortcnt] != NULL)
{
debugmask_add(i | 32768, tspriteptr[i]->owner);
renderDrawSprite(spritesortcnt);
tspriteptr[spritesortcnt] = NULL;
}
}
renderFinishScene();
GLInterface.SetDepthMask(true);
GLInterface.SetDepthBias(0, 0);
}
//==========================================================================
//
//
//
//==========================================================================
void FillPolygon(int* rx1, int* ry1, int* xb1, int32_t npoints, int picnum, int palette, int shade, int props, const FVector2& xtex, const FVector2& ytex, const FVector2& otex,
int clipx1, int clipy1, int clipx2, int clipy2)
{
//Convert int32_t to float (in-place)
TArray<FVector4> points(npoints, true);
using Point = std::pair<float, float>;
std::vector<std::vector<Point>> polygon;
std::vector<Point>* curPoly;
polygon.resize(1);
curPoly = &polygon.back();
for (bssize_t i = 0; i < npoints; ++i)
{
auto X = ((float)rx1[i]) * (1.0f / 4096.f);
auto Y = ((float)ry1[i]) * (1.0f / 4096.f);
curPoly->push_back(std::make_pair(X, Y));
if (xb1[i] < i && i < npoints - 1)
{
polygon.resize(polygon.size() + 1);
curPoly = &polygon.back();
}
}
// Now make sure that the outer boundary is the first polygon by picking a point that's as much to the outside as possible.
int outer = 0;
float minx = FLT_MAX;
float miny = FLT_MAX;
for (size_t a = 0; a < polygon.size(); a++)
{
for (auto& pt : polygon[a])
{
if (pt.first < minx || (pt.first == minx && pt.second < miny))
{
minx = pt.first;
miny = pt.second;
outer = a;
}
}
}
if (outer != 0) std::swap(polygon[0], polygon[outer]);
auto indices = mapbox::earcut(polygon);
int p = 0;
for (size_t a = 0; a < polygon.size(); a++)
{
for (auto& pt : polygon[a])
{
FVector4 point = { pt.first, pt.second, float(pt.first * xtex.X + pt.second * ytex.X + otex.X), float(pt.first * xtex.Y + pt.second * ytex.Y + otex.Y) };
points[p++] = point;
}
}
int maskprops = (props >> 7) & DAMETH_MASKPROPS;
FRenderStyle rs = LegacyRenderStyles[STYLE_Translucent];
double alpha = 1.;
if (maskprops > DAMETH_MASK)
{
rs = GetRenderStyle(0, maskprops == DAMETH_TRANS2);
alpha = GetAlphaFromBlend(maskprops, 0);
}
int translation = TRANSLATION(Translation_Remap + curbasepal, palette);
int light = clamp(Scale((numshades - shade), 255, numshades), 0, 255);
PalEntry pe = PalEntry(uint8_t(alpha*255), light, light, light);
twod->AddPoly(tileGetTexture(picnum), points.Data(), points.Size(), indices.data(), indices.size(), translation, pe, rs, clipx1, clipy1, clipx2, clipy2);
}
//==========================================================================
//
//
//
//==========================================================================
#include "build.h"
#include "../src/engine_priv.h"
//
// fillpolygon (internal)
//
static void renderFillPolygon(int32_t npoints)
{
int width = screen->GetWidth();
int height = screen->GetHeight();
// 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 = MulScale(globalx1, xyaspect, 16);
int y2 = MulScale(globaly2, xyaspect, 16);
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 = (width * xtex.X + height * ytex.X) * -0.5f + fglobalposx * (1.f / 4294967296.f);
otex.Y = (width * xtex.Y + height * ytex.Y) * -0.5f - fglobalposy * (1.f / 4294967296.f);
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;
int width = screen->GetWidth();
int height = screen->GetHeight();
int32_t const oyxaspect = yxaspect, oviewingrange = viewingrange;
renderSetAspect(65536, DivScale((320*5)/8, 200, 16));
memset(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 = { DivScale(-bcos(ang), zoome, 28), DivScale(-bsin(ang), zoome, 28) };
vec2_t const vect = { MulScale(-bsin(ang), zoome, 8), MulScale(-bcos(ang), zoome, 8) };
vec2_t const vect2 = { MulScale(vect.x, yxaspect, 16), MulScale(vect.y, yxaspect, 16) };
int32_t sortnum = 0;
usectorptr_t sec;
for (s=0,sec=(usectorptr_t)&sector[s]; s<numsectors; s++,sec++)
if (gFullMap || show2dsector[s])
{
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 (!DMulScale(wal->x-wall[k].x,wall[wal->point2].y-wal->y,-(wal->y-wall[k].y),wall[wal->point2].x-wal->x, 1)) continue;
ox = wal->x - dax; oy = wal->y - day;
x = DMulScale(ox,vect.x,-oy,vect.y, 16) + (width<<11);
y = DMulScale(oy,vect2.x,ox,vect2.y, 16) + (height<<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], MulScale(ry1[0]-(height<<11),xyaspect, 16)+(height<<11) };
//Collect floor sprites to draw
SectIterator it(s);
while ((i = it.NextIndex()) >= 0)
{
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;
globalfloorpal = globalpal = sec->floorpal;
globalpicnum = sec->floorpicnum;
if ((unsigned)globalpicnum >= (unsigned)MAXTILES) globalpicnum = 0;
tileUpdatePicnum(&globalpicnum, s);
setgotpic(globalpicnum);
if ((tileWidth(globalpicnum) <= 0) || (tileHeight(globalpicnum) <= 0)) continue;
globalshade = max(min<int>(sec->floorshade, numshades - 1), 0);
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 = MulScale(DMulScale(ox,bakgvect.x,oy,bakgvect.y, 10),i, 10);
globaly1 = MulScale(DMulScale(ox,bakgvect.y,-oy,bakgvect.x, 10),i, 10);
ox = (bak.x>>4)-(width<<7); oy = (bak.y>>4)-(height<<7);
globalposx = DMulScale(-oy, globalx1, -ox, globaly1, 28);
globalposy = DMulScale(-ox, globalx1, oy, globaly1, 28);
globalx2 = -globalx1;
globaly2 = -globaly1;
int32_t const daslope = sector[s].floorheinum;
i = nsqrtasm(daslope*daslope+16777216);
set_globalpos(globalposx, MulScale(globalposy,i, 12), globalposz);
globalx2 = MulScale(globalx2,i, 12);
globaly2 = MulScale(globaly2,i, 12);
}
int globalxshift = (8 - widthBits(globalpicnum));
int globalyshift = (8 - heightBits(globalpicnum));
if (globalorientation & 8) { globalxshift++; globalyshift++; }
// PK: the following can happen for large (>= 512) tile sizes.
if (globalxshift < 0) globalxshift = 0;
if (globalyshift < 0) globalyshift = 0;
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<<globalxshift);
asm2 = (globalx2<<globalyshift);
globalx1 <<= globalxshift;
globaly2 <<= globalyshift;
set_globalpos(((int64_t) globalposx<<(20+globalxshift))+(((uint32_t) sec->floorxpan())<<24),
((int64_t) globalposy<<(20+globalyshift))-(((uint32_t) sec->floorypan())<<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<sortnum-gap; i++)
for (j=i; j>=0; j-=gap)
{
if (sprite[tsprite[j].owner].z <= sprite[tsprite[j+gap].owner].z) break;
std::swap(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 = tileWidth(spr->picnum);
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 = DMulScale(ox,vect.x,-oy,vect.y, 16) + (width<<11);
y = DMulScale(oy,vect2.x,ox,vect2.y, 16) + (height<<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 = DMulScale(ox,vect.x,-oy,vect.y, 16) + (width<<11);
y = DMulScale(oy,vect2.x,ox,vect2.y, 16) + (height<<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 = DMulScale(ox,vect.x,-oy,vect.y, 16) + (width<<11);
y = DMulScale(oy,vect2.x,ox,vect2.y, 16) + (height<<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], MulScale(ry1[0] - (height << 11), xyaspect, 16) + (height << 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 ((tileWidth(globalpicnum) <= 0) || (tileHeight(globalpicnum) <= 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);
//relative alignment stuff
ox = v2.x-v1.x; oy = v2.y-v1.y;
i = ox*ox+oy*oy; if (i == 0) continue; i = 65536*16384 / i;
globalx1 = MulScale(DMulScale(ox,bakgvect.x,oy,bakgvect.y, 10),i, 10);
globaly1 = MulScale(DMulScale(ox,bakgvect.y,-oy,bakgvect.x, 10),i, 10);
ox = v1.y-v4.y; oy = v4.x-v1.x;
i = ox*ox+oy*oy; if (i == 0) continue; i = 65536 * 16384 / i;
globalx2 = MulScale(DMulScale(ox,bakgvect.x,oy,bakgvect.y, 10),i, 10);
globaly2 = MulScale(DMulScale(ox,bakgvect.y,-oy,bakgvect.x, 10),i, 10);
ox = widthBits(globalpicnum);
oy = heightBits(globalpicnum);
if ((1 << ox) != xspan)
{
ox++;
globalx1 = MulScale(globalx1,xspan,ox);
globaly1 = MulScale(globaly1,xspan,ox);
}
bak.x = (bak.x>>4)-(width<<7); bak.y = (bak.y>>4)-(height<<7);
globalposx = DMulScale(-bak.y,globalx1,-bak.x,globaly1, 28);
globalposy = DMulScale(bak.x,globalx2,-bak.y,globaly2, 28);
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);
}
}
}
//
// 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<MAXVOXMIPS; i++)
{
int32_t dasiz = fil.ReadInt32();
voxelmemory.Reserve(1);
voxelmemory.Last() = fil.Read(dasiz);
lengcnt += dasiz+4;
if (lengcnt >= 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
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<v2.x*v1.y)^(v1.y<v2.y) : (v1.x >= 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 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;
}
fixed_t gethiq16angle(int32_t xvect, int32_t yvect)
{
fixed_t rv;
if ((xvect | yvect) == 0)
rv = 0;
else if (xvect == 0)
rv = IntToFixed(512 + ((yvect < 0) << 10));
else if (yvect == 0)
rv = IntToFixed(((xvect < 0) << 10));
else if (xvect == yvect)
rv = IntToFixed(256 + ((xvect < 0) << 10));
else if (xvect == -yvect)
rv = IntToFixed(768 + ((xvect > 0) << 10));
else if (abs(xvect) > abs(yvect))
rv = ((qradarang[5120 + Scale(1280, yvect, xvect)] >> 6) + IntToFixed(((xvect < 0) << 10))) & 0x7FFFFFF;
else rv = ((qradarang[5120 - Scale(1280, xvect, yvect)] >> 6) + IntToFixed(512 + ((yvect < 0) << 10))) & 0x7FFFFFF;
return rv;
}
//
// ksqrt
//
int32_t ksqrt(uint32_t num)
{
if (enginecompatibility_mode == ENGINECOMPATIBILITY_19950829)
return ksqrtasm_old(num);
return nsqrtasm(num);
}
// 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;dacnt<danum;dacnt++)
{
dasectnum = clipsectorlist[dacnt]; sec = &sector[dasectnum];
for(cnt=sec->wallnum,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 = &sector[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] |= pow2char[sect1&7];
clipsectorlist[0] = sect1; danum = 1;
for (dacnt=0; dacnt<danum; dacnt++)
{
const int32_t dasectnum = clipsectorlist[dacnt];
auto const sec = (usectorptr_t)&sector[dasectnum];
uwallptr_t wal;
bssize_t cnt;
for (cnt=sec->wallnum,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] & pow2char[nexts&7]))
{
sectbitmap[nexts>>3] |= pow2char[nexts&7];
clipsectorlist[danum++] = nexts;
}
}
}
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 = 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<numyaxwalls; i++)
{
int32_t clockwise = 0;
int32_t w = yaxwalls[i];
const int32_t tmpstartwall = w;
bssize_t cnt = MAXWALLS;
while (1)
{
wall[w].x = dax;
wall[w].y = day;
walbitmap[w>>3] |= pow2char[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] & pow2char[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)&sector[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)&sector[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 0
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
#endif
{
int16_t sect = *sectnum;
updatesectorneighbor(x, y, &sect, 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)
// NOTE: This comes from Duke, therefore it's GPL!
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;
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, &sect, 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; sectcnt<nsecs; sectcnt++)
{
int const listsectnum = sectlist[sectcnt];
if (inside_p(x, y, listsectnum))
SET_AND_RETURN(*sectnum, listsectnum);
auto const sec = &sector[listsectnum];
int const startwall = sec->wallptr;
int const endwall = sec->wallptr + sec->wallnum;
auto uwal = (uwallptr_t)&wall[startwall];
for (int j=startwall; j<endwall; j++, uwal++)
if (uwal->nextsector >= 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; sectcnt<nsecs; sectcnt++)
{
int const listsectnum = sectlist[sectcnt];
if (inside_z_p(x, y, z, listsectnum))
SET_AND_RETURN(*sectnum, listsectnum);
auto const sec = &sector[listsectnum];
int const startwall = sec->wallptr;
int const endwall = sec->wallptr + sec->wallnum;
auto uwal = (uwallptr_t)&wall[startwall];
for (int j=startwall; j<endwall; j++, uwal++)
if (uwal->nextsector >= 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;
}
void videoSetCorrectedAspect()
{
// 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;
x = xdim;
y = ydim;
vr = DivScale(x*3, y*4, 16);
renderSetAspect(vr, yx);
}
//
// 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 = DivScale(1L,xdimen, 32);
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 = DivScale(1,daxrange, 32);
#ifdef USE_OPENGL
fviewingrange = (float) daxrange;
#endif
yxaspect = daaspect;
xyaspect = DivScale(1,yxaspect, 32);
xdimenscale = Scale(xdimen,yxaspect,320);
xdimscale = Scale(320,xyaspect,xdimen);
}
#include "v_2ddrawer.h"
//
// clearview
//
void videoClearViewableArea(int32_t dacol)
{
GLInterface.ClearScreen(dacol, false);
}
//
// clearallviews
//
void videoClearScreen(int32_t dacol)
{
GLInterface.ClearScreen(dacol | PalEntry(255,0,0,0));
}
//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<FCanvasTexture*>(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);
}
//
// preparemirror
//
void renderPrepareMirror(int32_t dax, int32_t day, int32_t daz, fixed_t daang, fixed_t dahoriz, int16_t dawall,
int32_t *tposx, int32_t *tposy, fixed_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 = ((gethiq16angle(dx, dy) << 1) - daang) & 0x7FFFFFF;
inpreparemirror = 1;
polymost_prepareMirror(dax, day, daz, daang, dahoriz, dawall);
}
//
// 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 ((unsigned)wallNum < (unsigned)numwalls)
{
native_t const w = wall[wallNum].nextwall;
return ((unsigned)w < MAXWALLS) ? wall[w].nextsector : sectorofwall_internal(wallNum);
}
return -1;
}
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 = 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)<<shift);
}
int32_t getflorzofslopeptr(usectorptr_t sec, int32_t dax, int32_t day)
{
if (!(sec->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 = 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)<<shift);
}
void getzsofslopeptr(usectorptr_t sec, int32_t dax, int32_t day, int32_t *ceilz, int32_t *florz)
{
*ceilz = sec->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 = 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)<<shift;
if (sec->floorstat&2)
*florz += Scale(sec->floorheinum,j>>shift,i)<<shift;
}
//
// 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;
}
//
// setrollangle
//
#ifdef USE_OPENGL
void renderSetRollAngle(float rolla)
{
gtang = rolla * BAngRadian;
}
#endif