   //11/23/2016 5:20p: ported vsp to structs. Last touch was:
   //10/31/2008 9:43p  16,955 polymost.kc (original port from WORKON\POLYMOST.BAS)
   
enum {VSPMAX=2048} //<- careful!
struct { x, n, p, tag, cy0, cy1, ctag, fy0, fy1, ftag; } vsp_t;
static vsp_t vsp[VSPMAX]; static vcnt, gtag;
static hx, hy, hz, hang, vang, tang, zoom;

()
{
   static px[8], py[8], pz[8], px2[8], py2[8], pz2[8], sx[8], sy[8];
   static whitepix = 0xffffff; glsettex(whitepix,1,1);
   
   //x: atan2(z,x)             [0]
   //y: atan2(y,sqrt(x^2+z^2)) [4],[5],[7],[8]
   
   f = 0.00; g = 1-f;
   px[0] = xres*f; py[0] = yres*f;
   px[1] = xres*g; py[1] = yres*f;
   px[2] = xres*g; py[2] = yres*g;
   px[3] = xres*f; py[3] = yres*g;
   initmosts(px,py,4);
      
   obstatus = 0; zoom = 1;
   
   tim = 0;
   while (1)
   {
      cls(0); clz(1e32);
      otim = tim; tim = klock(); dtim = tim-otim;
      hx = xres/2; hy = yres/2; hz = hx*zoom;

      setfont(8,12);
      
      if (0)
      for(oz=0,z=vsp[0].n;vsp[z].n;oz=z,z=nz)
      {
         nz = vsp[z].n;

            //Ceiling
         if (vsp[z].ctag != vsp[oz].ctag) { cx0 = vsp[z].x; cy0 = vsp[z].cy0; }
         if (vsp[z].ctag != vsp[nz].ctag)
         {
            drawquad(cx0,0,vsp[nz].x,0,vsp[nz].x,vsp[z].cy1,cx0,cy0,0x606060);
            if ((hang == 0) && (vang == 0) && (tang == 0) && (zoom == 1))
               { setcol(0xc0c0c0); moveto((cx0+vsp[nz].x)/2-8,      0); printf("%g",vsp[z].ctag); }
         }

            //Floor
         if (vsp[z].ftag != vsp[oz].ftag) { fx0 = vsp[z].x; fy0 = vsp[z].fy0; }
         if (vsp[z].ftag != vsp[nz].ftag)
         {
            drawquad(fx0,fy0,vsp[nz].x,vsp[z].fy1,vsp[nz].x,yres,fx0,yres,0x606060);
            if ((hang == 0) && (vang == 0) && (tang == 0) && (zoom == 1))
               { setcol(0xc0c0c0); moveto((fx0+vsp[nz].x)/2-8,yres-12); printf("%g",vsp[z].ftag); }
         }
      }
      
         //Wall
      for(z=vsp[0].n;vsp[z].n;z=nz)
      {
         nz = vsp[z].n; if (vsp[z].ctag < 0) continue;
         drawquad(vsp[z].x,vsp[z].cy0,vsp[nz].x,vsp[z].cy1,vsp[nz].x,vsp[z].fy1,vsp[z].x,vsp[z].fy0,0xffc080);
      }

      if ((vsp[vsp[vsp[0].n].n].n == 0) && (vsp[vsp[0].n].ftag < 0))
      {
         setcol(0xffffff); moveto(0,yres/4);
         setfont(xres/5,yres/2,1); printf("Done!");
      }
      
      x = 0; y = 0; z = 0;
      
      if (bstatus%2)
      {
         mx = 0; my = 0; mz = 0;
         xformbac(mousx-hx,mousy-hy,hz,&mx,&my,&mz);
         mx = mx/mz*hz + hx;
         my = my/mz*hz + hy;
         
         if ((obstatus%2) == 0) { x0 = mx; y0 = my; }
         x1 = mx; y1 = my;
         
            //Draw vertices of wedge (should match mouse)
         setcol(0xffffff);
         xformfor(x0-hx,y0-hy,hz,&px2[0],&py2[0],&pz2[0]); drawsph(px2[0],py2[0],pz2[0],4);
         xformfor(x1-hx,y1-hy,hz,&px2[1],&py2[1],&pz2[1]); drawsph(px2[1],py2[1],pz2[1],4);
         
            //Draw borders of wedge
         xformfor(0,sgn(x1-x0),0,&px2[2],&py2[2],&pz2[2]);
         moveto(px2[2],py2[2],pz2[2]); for(i=0;i<3;i++) lineto(px2[i],py2[i],pz2[i]);
         
         gldisable(GL_DEPTH_TEST); domost(x0,y0,x1,y1,0);
         glenable (GL_DEPTH_TEST);
      } else if (obstatus%2) { domost(x0,y0,x1,y1,1); }
      
      if (keystatus[0xcb]) { hang = (hang-dtim)%(PI*2); } //Left
      if (keystatus[0xcd]) { hang = (hang+dtim)%(PI*2); } //Right
      if (keystatus[0xc8]) { vang = max(vang-dtim,-PI/2); } //Up
      if (keystatus[0xd0]) { vang = min(vang+dtim,+PI/2); } //Down
      if (keystatus[0x33]) { tang = (tang-dtim)%(PI*2); } //,
      if (keystatus[0x34]) { tang = (tang+dtim)%(PI*2); } //.
      if (keystatus[0xb5]) { zoom *= pow(1.5,dtim); } //KP/
      if (keystatus[0x37]) { zoom /= pow(1.5,dtim); } //KP*
      if (keystatus[0x39] || keystatus[0x1c] || keystatus[0x9c]) //Space or Enter
      {
         if (keystatus[0x39])
         {
            f = 0.00; g = 1-f;
            xformbac(xres*f-hx,yres*f-hy,hz,&px[0],&py[0],&pz[0]);
            xformbac(xres*g-hx,yres*f-hy,hz,&px[1],&py[1],&pz[1]);
            xformbac(xres*g-hx,yres*g-hy,hz,&px[2],&py[2],&pz[2]);
            xformbac(xres*f-hx,yres*g-hy,hz,&px[3],&py[3],&pz[3]);
         }
         else
         {

            px[0] = -4096; py[0] = -4096; pz[0] = 16;
            px[1] = +4096; py[1] = -4096; pz[1] = 16;
            px[2] = +4096; py[2] = +4096; pz[2] = 16;
            px[3] = -4096; py[3] = +4096; pz[3] = 16;
         }
         
            //Clip screen to front plane
         n = 0; SCISDIST = 0.1;
         for(i=0;i<4;i++)
         {
            j = (i+1)%4;
            if (pz[i] >= SCISDIST) { px2[n] = px[i]; py2[n] = py[i]; pz2[n] = pz[i]; n++; }
            if ((pz[i] >= SCISDIST) != (pz[j] >= SCISDIST))
            {
               t = (SCISDIST-pz[i])/(pz[j]-pz[i]);
               px2[n] = (px[j]-px[i])*t + px[i];
               py2[n] = (py[j]-py[i])*t + py[i];
               pz2[n] = (pz[j]-pz[i])*t + pz[i];
               n++;
            }
         }
         
         for(i=0;i<n;i++)
         {
            sx[i] = px2[i]/pz2[i]*hz + hx;
            sy[i] = py2[i]/pz2[i]*hz + hy;
         }
         initmosts(sx,sy,n);
      }
      
         //Draw screen borders
      setcol(0xffffff);
      for(i=0;i<4;i++) { xformfor(px[i],py[i],pz[i],&x,&y,&z); drawsph(x,y,z,4); }

         //Draw horizon plane / zenith points
      setcol(0xc080c0);
      xformfor(0,-hy,0,&px2[0],&py2[0],&pz2[0]);
      xformfor(+hx,0,0,&px2[1],&py2[1],&pz2[1]);
      xformfor(0,+hy,0,&px2[2],&py2[2],&pz2[2]);
      xformfor(-hx,0,0,&px2[3],&py2[3],&pz2[3]);
      for(i=0;i<4;i++)
      {
         j = (i+1)%4;
         moveto(px2[i],py2[i],pz2[i]);
         lineto(px2[j],py2[j],pz2[j]);
         drawsph(px2[i],py2[i],pz2[i],3);
      }

      obstatus = bstatus;
      refresh();
   }
}

xformfor (sx,sy,sz,&rx,&ry,&rz)
{
   x = sx*cos(hang) - sz*sin(hang);
   y = sy;
   z = sz*cos(hang) + sx*sin(hang);
   
   x2 = x;
   y2 = y*cos(vang) - z*sin(vang);
   z2 = z*cos(vang) + y*sin(vang);
   
   rx = x2*cos(tang) + y2*sin(tang);
   ry = y2*cos(tang) - x2*sin(tang);
   rz = z2;
}

xformbac (rx,ry,rz,&sx,&sy,&sz)
{
   x = rx*cos(tang) - ry*sin(tang);
   y = ry*cos(tang) + rx*sin(tang);
   z = rz;
   
   x2 = x;
   y2 = y*cos(vang) + z*sin(vang);
   z2 = z*cos(vang) - y*sin(vang);
   
   sx = x2*cos(hang) + z2*sin(hang);
   sy = y2;
   sz = z2*cos(hang) - x2*sin(hang);
}

drawpol (sx[4],sy[4],n,col)
{
   static onf, fukcnt;
   static rx[4], ry[4], rz[4];
   
   if (numframes != onf) { onf = numframes; fukcnt = 0; }
   x = 0; y = 0; z = 0;
   for(i=0;i<n;i++)
   {
      xformfor(sx[i]-hx,sy[i]-hy,hz,&x,&y,&z);
      rx[i] = x; ry[i] = y; rz[i] = z;
   }
   
   glbegin(GL_COMPLEX);
   setcol(col-fukcnt*0x1f1f);
   for(i=0;i<n;i++)
   {
      gltexcoord(0,0); //int((i+1)/2),int(i/2));
      glvertex(rx[i],ry[i],rz[i]);
   }
   glend();
   fukcnt = 1-fukcnt;
}

drawtri (x0,y0,x1,y1,x2,y2,col)
{
   static x[3], y[3];
   x[0] = x0; y[0] = y0;
   x[1] = x1; y[1] = y1;
   x[2] = x2; y[2] = y2; drawpol(x,y,3,col);
}

drawquad (x0,y0,x1,y1,x2,y2,x3,y3,col)
{
   static x[4], y[4];
   x[0] = x0; y[0] = y0;
   x[1] = x1; y[1] = y1;
   x[2] = x2; y[2] = y2;
   x[3] = x3; y[3] = y3; drawpol(x,y,4,col);
}

//----------------------------------------------------------------------

   //Init viewport boundary (must be convex loop):
   //      (px[0],py[0]).----.(px[1],py[1])
   //                  /      \
   //                /          \
   // (px[3],py[3]).--------------.(px[2],py[2])
   //
initmosts (px[8],py[8],n)
{
   vcnt = 1; //0 is dummy solid node

   if (n < 3) return;
   imin = (px[1] < px[0]);
   for(i=n-1;i>=2;i--) if (px[i] < px[imin]) imin = i;

      //0:x, 1:n, 2:p, 3:tag;
      //4:cy0, 5:cy1, 6:ctag;
      //7:fy0, 8:fy1, 9:ftag;

   vsp[vcnt].x = px[imin];
   vsp[vcnt].cy0 = py[imin]; vsp[vcnt].fy0 = py[imin];
   vcnt++;
   i = imin+1; if (i >= n) i = 0;
   j = imin-1; if (j < 0) j = n-1;
   do
   {
      if (px[i] < px[j])
      {
         if ((vcnt > 1) && (px[i] <= vsp[vcnt-1].x)) vcnt--;
         vsp[vcnt].x = px[i];
         vsp[vcnt].cy0 = py[i];
         k = j+1; if (k >= n) k = 0;
            //(px[k],py[k])
            //(px[i],?)
            //(px[j],py[j])
         vsp[vcnt].fy0 = (px[i]-px[k])*(py[j]-py[k])/(px[j]-px[k]) + py[k];
         vcnt++;
         i++; if (i >= n) i = 0;
      }
      else if (px[j] < px[i])
      {
         if ((vcnt > 1) && (px[j] <= vsp[vcnt-1].x)) vcnt--;
         vsp[vcnt].x = px[j];
         vsp[vcnt].fy0 = py[j];
         k = i-1; if (k < 0) k = n-1;
            //(px[k],py[k])
            //(px[j],?)
            //(px[i],py[i])
         vsp[vcnt].cy0 = (px[j]-px[k])*(py[i]-py[k])/(px[i]-px[k]) + py[k];
         vcnt++;
         j--; if (j < 0) j = n-1;
      }
      else
      {
         if ((vcnt > 1) && (px[i] <= vsp[vcnt-1].x)) vcnt--;
         vsp[vcnt].x = px[i];
         vsp[vcnt].cy0 = py[i];
         vsp[vcnt].fy0 = py[j];
         vcnt++;
         i++; if (i >= n) i = 0; if (i == j) break;
         j--; if (j < 0) j = n-1;
      }
   } while (i != j);
   if (px[i] > vsp[vcnt-1].x)
   {
      vsp[vcnt].x = px[i];
      vsp[vcnt].cy0 = py[i]; vsp[vcnt].fy0 = py[i];
      vcnt++;
   }

   for(z=0;z<vcnt;z++)
   {
      vsp[z].cy1 = vsp[z+1].cy0; vsp[z].ctag = z;
      vsp[z].fy1 = vsp[z+1].fy0; vsp[z].ftag = z;
      vsp[z].n = z+1; vsp[z].p = z-1;
   }
   vsp[vcnt-1].n = 0; vsp[0].p = vcnt-1;
   gtag = vcnt;

      //VSPMAX-1 is dummy empty node
   for(z=vcnt;z<VSPMAX;z++) { vsp[z].n = z+1; vsp[z].p = z-1; }
   vsp[VSPMAX-1].n = vcnt; vsp[vcnt].p = VSPMAX-1;
}

domost (x0,y0,x1,y1,upd)
{
   static spx[4], spt[4], cy[2], cv[2];

   if (x0 < x1) { dir = 1; y0 -= .01; y1 -= .01; }
   else
   {
      if (x0 == x1) return;
      f = x0; x0 = x1; x1 = f;
      f = y0; y0 = y1; y1 = f;
      dir = 0; //clip umost (ceiling)
      y0 += .01; y1 += .01; //necessary?
   }

   slop = (y1-y0) / (x1-x0);
   for(i=vsp[0].n;i;i=newi)
   {
      newi = vsp[i].n; if (vsp[i].ctag <= 0) continue;
      nx0 = vsp[   i].x; if (nx0 >= x1) continue;
      nx1 = vsp[newi].x; if (x0 >= nx1) continue;
      
      dx = nx1-nx0;
      cy[0] = vsp[i].cy0; cv[0] = vsp[i].cy1-cy[0];
      cy[1] = vsp[i].fy0; cv[1] = vsp[i].fy1-cy[1];

      scnt = 0;

         //Test if left edge requires split (x0,y0) (nx0,cy(0)),<dx,cv(0)>
      if ((x0 > nx0) && (x0 < nx1))
      {
         t = (x0-nx0)*cv[dir] - (y0-cy[dir])*dx;
         if (((dir == 0) && (t < 0)) || ((dir) && (t > 0)))
            { spx[scnt] = x0; spt[scnt] = -1; scnt++; }
      }

         //Test for intersection on umost (j == 0) and dmost (j == 1)
      for(j=0;j<2;j++)
      {
         d = (y0-   y1)*dx - (x0- x1)*cv[j];
         n = (y0-cy[j])*dx - (x0-nx0)*cv[j];
         if ((abs(n) <= abs(d)) && (d*n >= 0) && (d != 0))
         {
            t = n/d; nx = (x1-x0)*t + x0;
            if ((nx > nx0) && (nx < nx1))
               { spx[scnt] = nx; spt[scnt] = j; scnt++; }
         }
      }

         //Nice hack to avoid full sort later :)
      if ((scnt >= 2) && (spx[scnt-1] < spx[scnt-2]))
      {
         f = spx[scnt-1]; spx[scnt-1] = spx[scnt-2]; spx[scnt-2] = f;
         j = spt[scnt-1]; spt[scnt-1] = spt[scnt-2]; spt[scnt-2] = j;
      }

         //Test if right edge requires split
      if ((x1 > nx0) && (x1 < nx1))
      {
         t = (x1-nx0)*cv[dir] - (y1-cy[dir])*dx;
         if (((dir == 0) && (t < 0)) || ((dir) && (t > 0)))
            { spx[scnt] = x1; spt[scnt] = -1; scnt++; }
      }

      vsp[i].tag = -1; vsp[newi].tag = -1;

      if (upd == 0) { oi = i; ocy1 = vsp[i].cy1; ofy1 = vsp[i].fy1; }
      for(z=0;z<=scnt;z++,i=vcnt)
      {
         if (z < scnt)
         {
            vcnt = vsinsaft(i);
            t = (spx[z]-nx0)/dx;
            vsp[i].cy1 = t*cv[0] + cy[0];
            vsp[i].fy1 = t*cv[1] + cy[1];
            vsp[vcnt].x = spx[z];
            vsp[vcnt].cy0 = vsp[i].cy1;
            vsp[vcnt].fy0 = vsp[i].fy1;
            vsp[vcnt].tag = spt[z];
            if (upd == 0) spt[z] = vcnt; //save vcnt for deletion
         }

         ni = vsp[i].n; if (ni == 0) continue; //this if fixes many bugs!
         dx0 = vsp[ i].x; if (x0 > dx0) continue;
         dx1 = vsp[ni].x; if (x1 < dx1) continue;
         ny0 = (dx0-x0)*slop + y0;
         ny1 = (dx1-x0)*slop + y0;

            //     dx0           dx1
            //      ³             ³
            //----------------------------
            //     t+=0          t+=0
            //  vsp[i].cy0    vsp[i].cy1
            //============================
            //     t+=1          t+=3
            //============================
            //  vsp[i].fy0    vsp[i].fy1
            //     t+=2          t+=6
            //
            //     ny0 ?         ny1 ?

         t = 1+3;
         if ((vsp[ i].tag == 0) || (ny0 <= vsp[i].cy0)) t--;
         if ((vsp[ i].tag == 1) || (ny0 >= vsp[i].fy0)) t++;
         if ((vsp[ni].tag == 0) || (ny1 <= vsp[i].cy1)) t -= 3;
         if ((vsp[ni].tag == 1) || (ny1 >= vsp[i].fy1)) t += 3;

         ncol = 0xffffff;
         
         if (dir == 0)
         {
            if ((t == 1) || (t == 2))
            {
               drawtri(dx0,vsp[i].cy0,dx1,vsp[i].cy1,dx0,ny0,ncol);
               if (upd) { vsp[i].cy0 = ny0; vsp[i].ctag = gtag; }
            }
            else if ((t == 3) || (t == 6))
            {
               drawtri(dx0,vsp[i].cy0,dx1,vsp[i].cy1,dx1,ny1,ncol);
               if (upd) { vsp[i].cy1 = ny1; vsp[i].ctag = gtag; }
            }
            else if ((t == 4) || (t == 5) || (t == 7))
            {
               drawquad(dx0,vsp[i].cy0,dx1,vsp[i].cy1,dx1,ny1,dx0,ny0,ncol);
               if (upd) { vsp[i].cy0 = ny0; vsp[i].cy1 = ny1; vsp[i].ctag = gtag; }
            }
            else if (t == 8)
            {
               drawquad(dx0,vsp[i].cy0,dx1,vsp[i].cy1,dx1,vsp[i].fy1,dx0,vsp[i].fy0,ncol);
               if (upd)
               {
                  vsp[i].cy0 = yres/2; vsp[i].cy1 = yres/2; vsp[i].ctag = -1;
                  vsp[i].fy0 = yres/2; vsp[i].fy1 = yres/2; vsp[i].ftag = -1;
               }
            }
         }
         else
         {
            if ((t == 7) || (t == 6))
            {
               drawtri(dx0,ny0,dx1,vsp[i].fy1,dx0,vsp[i].fy0,ncol);
               if (upd) { vsp[i].fy0 = ny0; vsp[i].ftag = gtag; }
            }
            else if ((t == 5) || (t == 2))
            {
               drawtri(dx0,vsp[i].fy0,dx1,ny1,dx1,vsp[i].fy1,ncol);
               if (upd) { vsp[i].fy1 = ny1; vsp[i].ftag = gtag; }
            }
            else if ((t == 4) || (t == 3) || (t == 1))
            {
               drawquad(dx0,ny0,dx1,ny1,dx1,vsp[i].fy1,dx0,vsp[i].fy0,ncol);
               if (upd) { vsp[i].fy0 = ny0; vsp[i].fy1 = ny1; vsp[i].ftag = gtag; }
            }
            else if (t == 0)
            {
               drawquad(dx0,vsp[i].cy0,dx1,vsp[i].cy1,dx1,vsp[i].fy1,dx0,vsp[i].fy0,ncol);
               if (upd)
               {
                  vsp[i].cy0 = yres/2; vsp[i].cy1 = yres/2; vsp[i].ctag = -1;
                  vsp[i].fy0 = yres/2; vsp[i].fy1 = yres/2; vsp[i].ftag = -1;
               }
            }
         }
      }

      if (upd == 0)
      {
         for(z=scnt-1;z>=0;z--) vsdel(spt[z]);
         vsp[oi].cy1 = ocy1; vsp[oi].fy1 = ofy1;
      }
   }

   if (upd)
   {
      gtag++;
#if 1
         //Combine neighboring vertical strips with matching collinear top&bottom edges
         //This prevents x-splits from propagating through the entire scan
      i = vsp[0].n;
      while (i)
      {
         ni = vsp[i].n;
         if ((vsp[i].cy0 >= vsp[i].fy0) && (vsp[i].cy1 >= vsp[i].fy1))
            { vsp[i].ctag = -1; vsp[i].ftag = -1; }
         if ((vsp[i].ctag == vsp[ni].ctag) && (vsp[i].ftag == vsp[ni].ftag))
         {
            vsp[i].cy1 = vsp[ni].cy1;
            vsp[i].fy1 = vsp[ni].fy1;
            vsdel(ni);
         } else i = ni;
      }
#endif      
   }
}

vsdel (i)
{
      //Delete i
   qi = vsp[i].p;
   ni = vsp[i].n;
   vsp[ni].p = qi;
   vsp[qi].n = ni;

      //Add i to empty list
   vsp[i].n = vsp[VSPMAX-1].n;
   vsp[i].p = VSPMAX-1;
   vsp[vsp[VSPMAX-1].n].p = i;
   vsp[VSPMAX-1].n = i;
}

vsinsaft (i)
{
      //i = next element from empty list
   r = vsp[VSPMAX-1].n;
   vsp[vsp[r].n].p = VSPMAX-1;
   vsp[VSPMAX-1].n = vsp[r].n;

   vsp[r] = vsp[i]; //copy i to r

      //insert r after i
   vsp[r].p = i; vsp[r].n = vsp[i].n;
   vsp[vsp[i].n].p = r; vsp[i].n = r;

   return(r);
}

//----------------------------------------------------------------------