#include <windows.h>
#include <math.h>

#include "expdef.h"
#include "explib.h"
#include "display.h"
#include "misc.h"

#include <shape.h>

rcode setPalette(HDC hdc,ushort ncolors, ushort* palette);

ushort greyPalette[16] = {
    0x000, 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0x700,
    0x800, 0x900, 0xa00, 0xb00, 0xc00, 0xd00, 0xe00, 0xf00
};

#define FAST

bool bGlobalAllowRMatSave = 0;

//  _____________________________________________________________
// /                                                             \
// | device independent bitmap
// \_____________________________________________________________/

DIBitmap::DIBitmap(char *fname)
 :  ifilename(fname),
    pdib(0)
{
    idead=1;

    if(load())
        return;

    idead=0;
}

DIBitmap::DIBitmap(DIBitmap &src)
 :  ifilename("<n/a>"),
    pdib(0)
{
    idead=1;

    if(src.isdead())
        return;

    if(!(pdib = CreateCompatibleDIB(src.dib())))
        return;

    idead=0;
}

DIBitmap::~DIBitmap()
{
    deletedib();
}

rcode DIBitmap::load()
{
    pdib=0;

    if(pdib = LoadDIB(ifilename))
        return OK;

    return FAILED;
}

void DIBitmap::deletedib()
{
    if(pdib)  DeleteDIB(pdib);
    pdib=0;
}

uchar DIBitmap::getpixvalue(cpix x,cpix y)
{
    return GetDIBPixelValue(pdib,x,y);
}

rcode DIBitmap::setpixvalue(cpix x,cpix y,uchar val)
{
    return SetDIBPixelValue(pdib,x,y,val);
}

COLORREF DIBitmap::getpixcolor(cpix x,cpix y)
{
    return GetDIBPixelColor(pdib,x,y);
}

//  ==================================================================
//  === RotationMatrix
//  ==================================================================

#define mapentry(curstep,x,y,xy) data[curstep][(x)*size*2+(y)*2+xy]

static FastTrig ftrig;

RotationMatrix::RotationMatrix(ushort xsize, ushort xsteps)
 :  size    (xsize),
    steps   (xsteps)
{
    idead   = 1;

    if(size > SHAPE_MAXSIZE)
    {   perr("RotMatrix: shape too large"); return; }

        sizeperstep = (ulong)size*size*2;   // max.: 128*128*2 = 32768
    memset(data, 0, sizeof(data));
    for(ushort i=0; i<steps; i++)
        if(!(data[i] = new char[sizeperstep]))
            {   MemErr(1811951339); return; }

    // if steps == 1, there is no rotation. in that case,
    // the data[] map is a dummy and will not be used (see srcxy method)
    if(steps == 1)
    {
        idead = 0;
        return;
    }

    // rotation data available as file?
    FILE *f;
    char fname[FILENAME_MAX+2];
    sprintf(fname, "rm%02x%02x.dat", size, steps);
    if (f=fopen(fname, "rb"))
    {
        for(ushort rcur=0; rcur<steps; rcur++)
            if(fread(data[rcur], 1, sizeperstep, f) < sizeperstep)
                {
                   perr("Error while reading %s : illegal file size", fname);
                   fclose(f);
                   return;
                }

        fclose(f);
        idead   = 0;
        dmsg("loaded %s", fname);
        return;
    }

    dmsg("generating %s in memory", fname);

    for(ushort rcur=0; rcur<steps; rcur++)
    {
        short  iw=size, ih=size;

        short  w2 = iw >> 1;
        short  h2 = ih >> 1;

        long   x,y,sx,sy,zx,zy;
        double xd,yd,sxd,syd;
        double atarg,asrc,PI=3.141;
        double PI2 = PI / 2.0;
        double rstep = 2.0 * PI / (double)steps;
        double dsteps = rcur;
        double r;

        for(x=-w2; x<w2; x++)
        {
            for(y=-h2; y<h2; y++)
            {
                zx = x+w2;    // zero-based x
                zy = y+h2;    // and y for array access

                xd  = x;
                yd  = y;

                if(xd >= 1.0)
                    atarg   = atan(yd / xd);
                else
                if(xd <= -1.0)
                    atarg   = PI - atan(yd / (-1.0 * xd));
                else
                if(yd >= 0.0)
                    atarg   = PI2;
                else
                    atarg   = -1.0 * PI2;

                r   = sqrt(xd * xd + yd * yd);

                asrc = atarg - rstep * dsteps;

                sxd = cos(asrc) * r;
                syd = sin(asrc) * r;

                sx  = sxd;
                sy  = syd;

                mapentry(rcur,zx,zy,0) = sx;
                mapentry(rcur,zx,zy,1) = sy;
            }
        }
    }

    if (bGlobalAllowRMatSave)
    {
        // save rotation data:
        if (f=fopen(fname, "wb"))
        {
            for(rcur=0; rcur<steps; rcur++)
                if(fwrite(data[rcur], 1, sizeperstep, f) < sizeperstep)
                    {
                       perr("Error while writing to %s", fname);
                       break;
                    }
       
            fclose(f);
            dmsg("saved %s", fname);
        }
        else
            perr("cannot write %s", fname);
    }

    idead = 0;
}

RotationMatrix::~RotationMatrix()
{
    for(ushort i=0; i<steps; i++)
        if(data[i])
            delete data[i];
}

rcode RotationMatrix::srcxy(short tx, short ty, short &sx, short &sy, ushort step)
{
    if(!this)   {SysErr(212951427); step=0;}

    if(!step)
    {
        sx = tx - (size>>1);
        sy = ty - (size>>1);
    }
    else
    {
        sx = mapentry(step,tx,ty,0);
        sy = mapentry(step,tx,ty,1);
    }

    return OK;
}

//  ==================================================================
//  === ShapeClass
//  ==================================================================

ShapeClass::ShapeClass(
    PixelDisplay &xpd,
    char    *firstnamebase,
    ushort  xnfaces,
    cpix    xmaxsize,
    ushort  nrot,
    ushort  nzoom,
    ulong   xflags)
 :  pd(xpd),
    nfaces(xnfaces),
    maxsize(xmaxsize),
    hpolds(0),
    hpoldd(0)
{
 ushort i;

    idead = 1;

    if(!(face = (ShapeFace**)new char[sizeof(void*)*nfaces]))
    {       MemErr(1811951756); return;     }

    memset(face,0,sizeof(ShapeFace*)*nfaces);

    prm   = 0;

    HDC hdcorg  = pd.getfronthdc();

    hdcsrc  = CreateCompatibleDC(hdcorg);
    hdcdst  = CreateCompatibleDC(hdcorg);

    if(!hdcsrc)     {perr("can't crt hdcsrc"); return;}
    if(!hdcdst)     {perr("can't crt hdcdst"); return;}

    // hpolds  = SelectPalette(hdcsrc, pd.logpal(), FALSE);
    // hpoldd  = SelectPalette(hdcdst, pd.logpal(), FALSE);

    // setPalette(hdcsrc, sizeof(greyPalette), greyPalette);
    // setPalette(hdcdst, sizeof(greyPalette), greyPalette);

    // alloc temporar rotation matrix:
    if(!(prm = new RotationMatrix(maxsize, nrot)))
    {   MemErr(1811951404); return; }
    if(prm->isdead())
        return;

    char namebuf[FILENAME_MAX+2];

    // create faces:
    for(short fcur=0; fcur<nfaces; fcur++)
    {
        sprintf(namebuf,"%s%d.bmp",firstnamebase,fcur);

        if(!(face[fcur] =
           new ShapeFace(*this,
                         namebuf,
                         maxsize,
                         nrot,
                         nzoom,
                         xflags)))
        {   MemErr(1811951408); return; }

        if(face[fcur]->isdead())
            return;
    }

    // rotations were done in ShapeFace ctr's;
    // rotation maxtrix is of no use any more.
    if(prm) delete prm;
    prm = 0;

    // coord frame defaults to pd.cfrm:
    setcfrm(pd.cfrm(0),pd.cfrm(1),pd.cfrm(2),pd.cfrm(3));

    idead   = 0;
}

ShapeClass::~ShapeClass()
{
    // if (hdcsrc) SelectPalette(hdcsrc, hpolds, FALSE);
    // if (hdcdst) SelectPalette(hdcdst, hpoldd, FALSE);

    if(prm) delete prm;

    for(short fcur=0; fcur<nfaces; fcur++)
        if(face[fcur])
            delete face[fcur];

    if(hdcdst)  DeleteDC(hdcdst);
    if(hdcsrc)  DeleteDC(hdcsrc);

    if(face)    delete face;
}

rcode ShapeClass::setcfrm(cpix x1,cpix y1,cpix x2,cpix y2)
{
        if(             x1 < pd.cfrm(0)
        ||      y1 < pd.cfrm(1)
                ||      x2 > pd.cfrm(2)
                ||      y2 > pd.cfrm(3) )
                return INDATA;

    hwfrm[0] = x1;
    hwfrm[1] = y1;
    hwfrm[2] = x2;
    hwfrm[3] = y2;

    return OK;
}

//  ==================================================================
//  === ShapeFace
//  ==================================================================

#define XTR     xtr(rcur,zcur)
#define YTR     ytr(rcur,zcur)

ShapeFace::ShapeFace(
    ShapeClass  &xscl,
    char   *filename,
    cpix    xsize,
    ushort  xnrot,
    ushort  xnzoom,
    ulong   xflags)
 :  scl     (xscl),
    size    (xsize),
    nrot    (xnrot),
    nzoom   (xnzoom),
    flags   (xflags),
    dib     (filename),
    dib2    (dib)
{
 ushort i;
 bool   failed = 0;
 HBITMAP hold1,hold2;

    idead = 1;

    tr.hbmp     = 0;
    hbmpstamp   = 0;
    tr.hbmpmask = 0;
    tr.hbmpmono = 0;
    bytemap.adr = 0;
    mbb.adr     = 0;
    mbb.hbmpimg = 0;
    linfo.adr   = 0;

    if(dib.isdead()||dib2.isdead())
        return;

    for (i=3; i<8; i++)
      if((1<<i) == size)
        {   sizebits = i; break;    }

    if(i==8)
    {   perr("size is no power of 2 and not in range 8 to 128"); return;   }

    HDC hdcorg  = scl.pd.getfronthdc();

    tr.width    = size * nzoom;
    tr.height   = size * nrot;
    tr.hbmp     = CreateCompatibleBitmap(hdcorg,tr.width,tr.height);
    hbmpstamp   = CreateCompatibleBitmap(hdcorg,size,size);
    tr.hbmpmask = CreateBitmap(tr.width,tr.height,1,1,0);
    tr.hbmpmono = CreateBitmap(tr.width,tr.height,1,1,0);
    bytemap.size= size * size;
    bytemap.adr = new char[bytemap.size];
    mbb.hbmpimg = CreateBitmap(size,size,1,1,0);
    mbb.size    = size * size / 8;
    mbb.adr     = new char[mbb.size];
    linfo.size  = (size*2)*nrot*nzoom;
    linfo.adr   = new char[linfo.size];

    if(!tr.hbmp    ) {perr("can't crt tr.hbmp for %s",filename);     return;}
    if(!hbmpstamp  ) {perr("can't crt hbmpstamp for %s",filename);   return;}
    if(!tr.hbmpmask) {perr("can't crt tr.hbmpmask for %s",filename); return;}
    if(!tr.hbmpmono) {perr("can't crt tr.hbmpmono for %s",filename); return;}
    if(!bytemap.adr) {perr("can't crt bytemap.adr for %s",filename); return;}
    if(!mbb.hbmpimg) {perr("can't crt mbb.hbmpimg for %s",filename); return;}
    if(!mbb.adr    ) {perr("can't crt mbb.adr     for %s",filename); return;}
    if(!linfo.adr  ) {perr("can't crt linfo.adr   for %s",filename); return;}

    // create transformed images:
    void globalMessage(char *txt, ...);
    globalMessage("calculating %s", filename);
    ushort zcur = 0;

    HPALETTE hpal1 = SelectPalette(scl.hdcdst, scl.pd.logpal(), FALSE);
    HPALETTE hpal2 = SelectPalette(scl.hdcsrc, scl.pd.logpal(), FALSE);

    for(ushort rcur=0; rcur<nrot; rcur++)
    {
        globalMessage("\t(%d / %d)", rcur+1, nrot);

        // rotate loaded -> tr.hbmp (color)
        hold2   = SelectObject(scl.hdcdst, tr.hbmp);
         if(rotate(scl.hdcdst, XTR,YTR, rcur))
            failed = 1;
        SelectObject(scl.hdcdst, hold2);
        if(failed)
            break;

/*
        // create mono mask of rotated bitmap
        // 1. create negative
        SetBkColor  (scl.hdcsrc, RGB(0,0,0));
        hold1 = SelectObject(scl.hdcsrc, tr.hbmp);
        hold2 = SelectObject(scl.hdcdst, tr.hbmpmask);
         BitBlt(scl.hdcdst,XTR,YTR,size,size,scl.hdcsrc,XTR,YTR,SRCCOPY);
        SelectObject(scl.hdcsrc, hold1);
        SelectObject(scl.hdcdst, hold2);

        // 2. create positive
        SetBkColor  (scl.hdcsrc, RGB(0,0,0));
        hold1 = SelectObject(scl.hdcsrc, tr.hbmp);
        hold2 = SelectObject(scl.hdcdst, tr.hbmpmono);
         BitBlt(scl.hdcdst,XTR,YTR,size,size,scl.hdcsrc,XTR,YTR,NOTSRCCOPY);
        SelectObject(scl.hdcsrc, hold1);
        SelectObject(scl.hdcdst, hold2);
*/

        // 3. create line profile of positive mono mask
        decodebitmap(rcur,zcur); // to bytemask
        makelinfo(li_start(rcur,zcur));
    }

    SelectPalette(scl.hdcdst, hpal1, FALSE);
    SelectPalette(scl.hdcsrc, hpal2, FALSE);

    if(failed)
    {
        globalMessage("calculating %s : FAILED\n", filename);
        return;
    }

    globalMessage("calculated %s, OK\n", filename);

    // determine front- and backsize of shape. this is the number of pixels
    // which are really used in front and back direction.
    uchar   *p = li_start(0,0);
    short   h2 = size >> 1;
    ifrontsize = ibacksize = 0;
    for(short y=0; y<h2; y++)
    {
        if(p[((h2-1-y)<<1)+1])  // if lineinfo.length is set to anything
            ifrontsize = y + 1;     // shape elongates that far to front

        if(p[((h2 + y)<<1)+1])
            ibacksize  = y + 1;     // and to back
    }

    idead   = 0;
}

ShapeFace::~ShapeFace()
{
    if(tr.hbmp    ) DeleteObject(tr.hbmp);
    if(hbmpstamp  ) DeleteObject(hbmpstamp);
    if(tr.hbmpmask) DeleteObject(tr.hbmpmask);
    if(tr.hbmpmono) DeleteObject(tr.hbmpmono);
    if(bytemap.adr) delete bytemap.adr;
    if(mbb.adr    ) delete mbb.adr;
    if(mbb.hbmpimg) DeleteObject(mbb.hbmpimg);
    if(linfo.adr  ) delete linfo.adr;
}

rcode ShapeFace::rotate(
    HDC     hdst,
    ushort  xdst,
    ushort  ydst,
    ushort  steps
    )
{
    short  ix1=0, iy1=0;
    short  ix2=0, iy2=0;
    short  iw=size, ih=size;

    short  w2 = iw >> 1;
    short  h2 = ih >> 1;

    // zero-centered src'n target:
    long   x1 = ix1 + w2;
    long   y1 = iy1 + h2;
    long   x2 = ix2 + w2;
    long   y2 = iy2 + h2;

    short  x,y,sx,sy,zx,zy,szx,szy;

    COLORREF cref;
    pcol     cidx;
    ulong cr,cg,cb;

    for(x=-w2; x<w2; x++)
    for(y=-h2; y<h2; y++)
    {
        zx = x+w2;    // zero-based x
        zy = y+h2;    // and y for array access

        if(scl.prm->srcxy(zx,zy,sx,sy,steps))
        {
            perr("scl.prm->srcxy failed");
            return FAILED;
        }

        szx = x1+sx;
        szy = y1+sy;

        if(szx<0 || szx>=size || szy<0 || szy >=size)
        {
            // source pixel lies outside source bitmap area - clip it.
//          cref = RGB(0,0,0);
            cidx = 0;
        }
        else
//      if((cref = dib.getpixcolor(szx,szy)) == 0xFFFFFFFF)
        if((cidx = dib.getpixvalue(szx,szy)) < 0)
        {
            perr("dib.gpc(%d,%d) failed",szx,szy);
            return FAILED;
        }

        if(dib2.setpixvalue(zx,zy,cidx))
            return FAILED;

//      cr   = (cref >> 16)&0xff;
//      cg   = (cref >>  8)&0xff;
//      cb   = (cref >>  0)&0xff;

//      SetPixel(hdst,xdst+x2+x,ydst+y2+y,(cb<<16)|(cg<<8)|cr);
//      SetPixel(hdst,xdst+x2+x,ydst+y2+y,PALETTEINDEX(cidx));
//      SetPixel(scl.pd.gethdc(),xdst+10+zx,ydst+10+zy,(cb<<16)|(cg<<8)|cr);
    }

    SetDIBits(  hdst,
                hbmpstamp,
                0,
                size,
                DIB_PBITS(dib2.dib()),
                dib2.dib(),
                DIB_RGB_COLORS  );

    HBITMAP  hbo = SelectObject(scl.hdcsrc, hbmpstamp);
      BitBlt(hdst,xdst,ydst,size,size,scl.hdcsrc,0,0,SRCCOPY);
    SelectObject(scl.hdcsrc, hbo);

    // create mono mask of rotated bitmap

#ifdef SLOWPIXELWISE
    SetTextColor(scl.hdcsrc, RGB(255,255,255));
    SetBkColor  (scl.hdcsrc, RGB(0,0,0));

    HDC hold1 = SelectObject(scl.hdcsrc, tr.hbmpmask);
    HDC hold2 = SelectObject(scl.hdcdst, tr.hbmpmono);

    ushort rcur = steps;
    ushort zcur = 0;

    for(x=0; x<size; x++)
    for(y=0; y<size; y++)
    {
        if(dib2.getpixvalue(x,y))
        {
            SetPixel(scl.hdcdst,XTR+x,YTR+y,RGB(255,255,255));
            SetPixel(scl.hdcsrc,XTR+x,YTR+y,RGB(0,0,0));
        }
        else
        {
            SetPixel(scl.hdcdst,XTR+x,YTR+y,RGB(0,0,0));
            SetPixel(scl.hdcsrc,XTR+x,YTR+y,RGB(255,255,255));
        }
    }

    SelectObject(scl.hdcsrc, hold1);
    SelectObject(scl.hdcdst, hold2);

    // blot out backgrnd bits in target:
    for(x=0; x<size; x++)
    for(y=0; y<size; y++)
        if(!dib2.getpixvalue(x,y))
            SetPixel(hdst,x+xdst,y+ydst,RGB(0,0,0));
#else
    SetBkColor(hdst, RGB(0,0,0));

    HDC hold1 = SelectObject(scl.hdcsrc, tr.hbmpmask);
    SetTextColor(scl.hdcsrc, RGB(255,255,255));
    BitBlt(scl.hdcsrc,xdst,ydst,size,size,hdst,xdst,ydst,SRCCOPY);
    SelectObject(scl.hdcsrc, hold1);

    hold1 = SelectObject(scl.hdcsrc, tr.hbmpmono);
    SetTextColor(scl.hdcsrc, RGB(255,255,255));
    BitBlt(scl.hdcsrc,xdst,ydst,size,size,hdst,xdst,ydst,NOTSRCCOPY);
    SelectObject(scl.hdcsrc, hold1);
#endif

    return OK;
}

void ShapeFace::decodebitmap(ushort rcur,ushort zcur)
{
    HBITMAP hold1,hold2;

    hold1 = SelectObject(scl.hdcsrc, tr.hbmpmono);
    hold2 = SelectObject(scl.hdcdst, mbb.hbmpimg);
      BitBlt(scl.hdcdst,0,0,size,size,scl.hdcsrc,XTR,YTR,SRCCOPY);
    SelectObject(scl.hdcsrc, hold1);
    SelectObject(scl.hdcdst, hold2);

    GetBitmapBits(mbb.hbmpimg, mbb.size, mbb.adr);

    ushort mbb_yshift = sizebits - 3;

    ushort x,y;
    for(y=0; y<size; y++)
        for(x=0; x<size; x++)
            bytemap.adr[(y<<sizebits)+x]
                = (mbb.adr[(y<<mbb_yshift)+(x>>3)] & (128>>(x&7))) ? 1 : 0;
}

void ShapeFace::makelinfo(uchar *lprof)
{
    uchar *pline;
    ushort y,idx,i1st;
    // ushort readnull=0;

    // create profile for every line of bytemask
    for(y=0; y<size; y++)
    {
        pline = bytemap.adr + y * size;

        // how many bytes in left zero area?
        for(idx=0; idx<size; idx++)
            if(pline[idx])
                break;

        if(idx < size)
        {
            i1st = idx;
                        lprof[y<<1] = i1st;
                }
                else
                {
                lprof[y<<1] = 0;
                        lprof[(y<<1)+1] = 0;
            continue;
                }

        // how many bytes NOT belonging to right zero area?
        for(; idx<size; idx++)
            if(pline[idx])  // whenever reading a non-zero byte
            {
                lprof[(y<<1)+1] = idx - i1st + 1; // adapt size of middle area

                //if(!readnull)
                //    lprof[i].full = 1;  // is (yet) continuous area
            }
            //else
            //  readnull = 1;
    }
}

//  ==================================================================
//  === Shape
//  ==================================================================

Shape::Shape(ShapeClass &r, pcol icolor)
 :  scl(r)
{
    HDC hdctarg = scl.pd.getfronthdc();

    idead   = 1;

    hbmpback= 0;

    if(scl.isdead()) return;

    curx    = 0;
    cury    = 0;
    curface = 0;
    currot  = 0;
    curzoom = 0;
    curftangle  = 0;

    oldx    = 0;
    oldy    = 0;
    oldface = 0;
    oldrot  = 0;
    oldzoom = 0;
    curcol  = icolor;

    obstfn      = 0;
    iuserdata   = 0;
    plotted     = 0;

    hbmpback    = CreateCompatibleBitmap(hdctarg,scl.maxsize,scl.maxsize);
    if(!hbmpback)   {perr("can't crt backgrnd bitmap"); return;}

    idead   = 0;
}

Shape::~Shape()
{
    if(hbmpback)     DeleteObject(hbmpback);
}

rcode Shape::moveto(cpix x, cpix y)
{
    curx    = x;
    cury    = y;
    return  OK;
}

#define CURFACEX face.xtr(currot,curzoom)
#define CURFACEY face.ytr(currot,curzoom)
#define OLDFACEX face.xtr(oldrot,oldzoom)
#define OLDFACEY face.ytr(oldrot,oldzoom)

#if 1

rcode Shape::plot(void)
{
    HDC hdctarg = scl.pd.getcurhdc();
    HDC hdcback = hdctarg;

    if(scl.pd.getbackhdc())
       hdcback = scl.pd.getbackhdc();

    ShapeFace  &face(*scl.face[curface]);

    HBITMAP hold1,hold2;

    cpix    size = face.size;

    short   bltx = curx - (size>>1);
    short   blty = cury - (size>>1);

    oldx    = bltx;
    oldy    = blty;
    oldface = curface;
    oldrot  = currot;
    oldzoom = curzoom;

    // --- video display plot ---

#ifdef FAST
    cpix    fx = bltx;
    cpix    fy = blty;
    cpix    bx = CURFACEX;
    cpix    by = CURFACEY;
    cpix    w  = size;
    cpix    h  = size;
    cpix    d;

    // if at least one of the target corners lies inside frame:
    if(     scl.checkbnd(fx  ,fy  ) == OK
        ||  scl.checkbnd(fx+w,fy  ) == OK
        ||  scl.checkbnd(fx  ,fy+h) == OK
        ||  scl.checkbnd(fx+w,fy+h) == OK
      )
    {
        // clip it:
        if(fx   < scl.cfrm(0))  { d=scl.cfrm(0)-fx;     fx+=d;  w-=d; bx+=d; }
        if(fx+w > scl.cfrm(2))  { d=fx+w-scl.cfrm(2);           w-=d;        }
        if(fy   < scl.cfrm(1))  { d=scl.cfrm(1)-fy;     fy+=d;  h-=d; by+=d; }
        if(fy+h > scl.cfrm(3))  { d=fy+h-scl.cfrm(3);           h-=d;        }

//      HPALETTE hpal1 = SelectPalette(scl.hdcsrc, scl.pd.logpal(), FALSE);

        // 1. blot out target area
        SetTextColor(hdctarg, RGB(0,0,0));
        SetBkColor  (hdctarg, RGB(255,255,255));
        hold1 = SelectObject(scl.hdcsrc, face.tr.hbmpmask);
            BitBlt(hdctarg,fx,fy,w,h,scl.hdcsrc,bx,by,SRCAND);
        SelectObject(scl.hdcsrc, hold1);

        // 2. OR color source with target
        hold1 = SelectObject(scl.hdcsrc, face.tr.hbmp);
            BitBlt(hdctarg,fx,fy,w,h,scl.hdcsrc,bx,by,SRCPAINT);
        SelectObject(scl.hdcsrc, hold1);

//      SelectPalette(scl.hdcsrc, hpal1, FALSE);
    }
#else
    // A. COPY FRONT TO BACKSTAMP

    // 1. Save background of target area:
    hold1 = SelectObject(scl.hdcdst, hbmpback);
     BitBlt(scl.hdcdst,0,0,size,size,hdcback,bltx,blty,SRCCOPY);
    SelectObject(scl.hdcdst, hold1);

    // 2. Copy background to backstamp
    hold1 = SelectObject(scl.hdcsrc, hbmpback);
    hold2 = SelectObject(scl.hdcdst, face.hbmpstamp);
     BitBlt(scl.hdcdst,0,0,size,size,scl.hdcsrc,0,0,SRCCOPY);
    SelectObject(scl.hdcsrc, hold1);
    SelectObject(scl.hdcdst, hold2);

    // B. IN THE BACKSTAMP...

    // blit color shape w/o changing surroundings:
    // 1. blot out target area
    SetTextColor(scl.hdcdst, RGB(0,0,0));
    SetBkColor  (scl.hdcdst, RGB(255,255,255));
    hold1 = SelectObject(scl.hdcsrc, face.tr.hbmpmask);
    hold2 = SelectObject(scl.hdcdst, face.hbmpstamp);
        BitBlt(scl.hdcdst,0,0,size,size,scl.hdcsrc,CURFACEX,CURFACEY,SRCAND);
    SelectObject(scl.hdcsrc, hold1);
    SelectObject(scl.hdcdst, hold2);

    // 2. OR color source with target
    hold1 = SelectObject(scl.hdcsrc, face.tr.hbmp);
    hold2 = SelectObject(scl.hdcdst, face.hbmpstamp);
        BitBlt(scl.hdcdst,0,0,size,size,scl.hdcsrc,CURFACEX,CURFACEY,SRCPAINT);
    SelectObject(scl.hdcsrc, hold1);
    SelectObject(scl.hdcdst, hold2);

    // C. COPY BACKSTAMP TO FRONT

    hold1 = SelectObject(scl.hdcsrc, face.hbmpstamp);
        BitBlt(hdctarg,bltx,blty,size,size,scl.hdcsrc,0,0,SRCCOPY);
    SelectObject(scl.hdcsrc, hold1);
#endif

    plotted = 1;

    // --- PixelDisplay framebuffer plot ---

    if(face.useframebuf())
    {
        FrameBuffer &fbuf(scl.pd.fbuf);

        cpix fbxstart, fbystart, bmxstart;
        fbxstart = bltx;
        fbystart = blty;

        bool clipx = 0;

        if(     fbxstart < scl.cfrm(0)
            ||  fbxstart > scl.cfrm(2) - size)
            clipx  = 1;

        cpix   idx = 0, trans = 0;
        cpix   cf0 = scl.cfrm(0);
        cpix   cf2 = scl.cfrm(2);

        uchar *pdst,*psrc;
        uchar  i1st,iln;
        ushort x,y;
        uchar *linfo = face.li_start(currot, curzoom);
        cpix   framey;
        cpix   cfm1 = scl.cfrm(1);
        cpix   cfm3 = scl.cfrm(3);
        for(y=0; y<size; y++)
        {
            if(!(iln = linfo[(y<<1)+1]))
                continue;

            if(        ((framey = y + fbystart) < cfm1)
                ||       framey > cfm3  )
                continue;   // clip line

            i1st = linfo[y<<1];
            pdst = fbuf.ladr[framey & fbuf.iadrmsk];

            if(clipx)
            {
                idx   = fbxstart + i1st;
                trans = iln;

                if(idx < cf0)
                {
                    if((trans -= (cf0 - idx)) > 0)
                        memset(pdst, curcol, trans);
                }
                else
                if(idx + trans > cf2)
                {
                    if((trans -= (idx + trans - cf2)) > 0)
                        memset(pdst + idx, curcol, trans);
                }
                else 
                    memset(pdst + idx, curcol, trans);
            }
            else
                memset(pdst + fbxstart + i1st, curcol, iln);
        }
    }

    return OK;
}

rcode Shape::unplot(void)
{
    if(!plotted)
        return OK;  // nothing to unplot

    HDC hdctarg = scl.pd.getcurhdc();
    HDC hdcback = hdctarg;
    ShapeFace  &face(*scl.face[oldface]);
    HBITMAP hold1, hold2;
    cpix    size = face.size;
    if(scl.pd.getbackhdc())
       hdcback = scl.pd.getbackhdc();

#ifdef FAST
    cpix    fx = oldx;
    cpix    fy = oldy;
    cpix    w  = size;
    cpix    h  = size;
    cpix    d;

    // if at least one of the target corners lies inside frame:
    if(     scl.checkbnd(fx  ,fy  ) == OK
        ||  scl.checkbnd(fx+w,fy  ) == OK
        ||  scl.checkbnd(fx  ,fy+h) == OK
        ||  scl.checkbnd(fx+w,fy+h) == OK
      )
    {
        // clip it:
        if(fx   < scl.cfrm(0))  { d=scl.cfrm(0)-fx;     fx+=d;  w-=d; }
        if(fx+w > scl.cfrm(2))  { d=fx+w-scl.cfrm(2);           w-=d; }
        if(fy   < scl.cfrm(1))  { d=scl.cfrm(1)-fy;     fy+=d;  h-=d; }
        if(fy+h > scl.cfrm(3))  { d=fy+h-scl.cfrm(3);           h-=d; }

        BitBlt(hdctarg,fx,fy,w,h,hdcback,fx,fy,SRCCOPY);
    }
#else
    // UNPLOT target area by restoring ONLY CHANGED pixels:

    // 0. COPY FRONT TO BACKSTAMP
    hold1 = SelectObject(scl.hdcdst, face.hbmpstamp);
     BitBlt(scl.hdcdst,0,0,size,size,hdctarg,oldx,oldy,SRCCOPY);
    SelectObject(scl.hdcdst, hold1);

    // 1. The trouble is, there might be lots of garbage in the
    //    saved background. All pixels NOT belonging to the hot area
    //    are considered hostile, and must be blotted out:
    hold1 = SelectObject(scl.hdcsrc, face.tr.hbmpmask);
    hold2 = SelectObject(scl.hdcdst, hbmpback);
     SetTextColor(scl.hdcdst, RGB(255,255,255));
     SetBkColor  (scl.hdcdst, RGB(0,0,0));
     BitBlt(scl.hdcdst,0,0,size,size,scl.hdcsrc,OLDFACEX,OLDFACEY,SRCAND);
    SelectObject(scl.hdcsrc, hold1);
    SelectObject(scl.hdcdst, hold2);

    // ... now, only the hot area is left in saved background.
    // 2. blot out hot target area in backstamp
    hold1 = SelectObject(scl.hdcsrc , face.tr.hbmpmask);
    hold2 = SelectObject(scl.hdcdst , face.hbmpstamp);
     SetTextColor(scl.hdcdst, RGB(0,0,0));
     SetBkColor  (scl.hdcdst, RGB(255,255,255));
     BitBlt(scl.hdcdst,0,0,size,size,scl.hdcsrc,OLDFACEX,OLDFACEY,SRCAND);
    SelectObject(scl.hdcsrc , hold1);
    SelectObject(scl.hdcdst , hold2);

    // 3. OR backstamp with hot background
    hold1 = SelectObject(scl.hdcsrc , hbmpback);
    hold2 = SelectObject(scl.hdcdst , face.hbmpstamp);
     BitBlt(scl.hdcdst,0,0,size,size,scl.hdcsrc,0,0,SRCPAINT);
    SelectObject(scl.hdcsrc , hold1);
    SelectObject(scl.hdcdst , hold2);

    // 4. COPY BACKSTAMP TO FRONT
    hold1 = SelectObject(scl.hdcsrc , face.hbmpstamp);
     BitBlt(hdctarg,oldx,oldy,size,size,scl.hdcsrc,0,0,SRCCOPY);
    SelectObject(scl.hdcsrc , hold1);
#endif

    plotted = 0;

    // --- PixelDisplay framebuffer unplot ---

    if(face.useframebuf())
    {
        FrameBuffer &fbuf(scl.pd.fbuf);

        cpix fbxstart, fbystart, bmxstart;
        fbxstart = oldx;
        fbystart = oldy;

        bool clipx = 0;

        if(     fbxstart < scl.cfrm(0)
            ||  fbxstart > scl.cfrm(2) - size)
            clipx  = 1;

        cpix   idx = 0, trans = 0;
        cpix   cf0 = scl.cfrm(0);
        cpix   cf2 = scl.cfrm(2);

        uchar *pdst,*psrc;
        uchar  i1st,iln;
        ushort x,y;
        uchar *linfo = face.li_start(oldrot, curzoom);
        cpix   framey;
        cpix   cfm1 = scl.cfrm(1);
        cpix   cfm3 = scl.cfrm(3);
        for(y=0; y<size; y++)
        {
            if(!(iln = linfo[(y<<1)+1]))
                continue;

            if(        ((framey = y + fbystart) < cfm1)
                ||       framey > cfm3  )
                continue;   // clip line

            i1st = linfo[y<<1];
            pdst = fbuf.ladr[framey & fbuf.iadrmsk];

            if(clipx)
            {
                idx   = fbxstart + i1st;
                trans = iln;

                if(idx < cf0)
                {
                    if((trans -= (cf0 - idx)) > 0)
                        memset(pdst, 0, trans);
                }
                else
                if(idx + trans > cf2)
                {
                    if((trans -= (idx + trans - cf2)) > 0)
                        memset(pdst + idx, 0, trans);
                }
                else 
                    memset(pdst + idx, 0, trans);
            }
            else
                memset(pdst + fbxstart + i1st, 0, iln);
        }
    }

    return OK;
}

#else

rcode Shape::plot(void)
{
    HDC hdctarg = scl.pd.getcurhdc();
    HDC hdcback = hdctarg;

    if(scl.pd.getbackhdc())
       hdcback = scl.pd.getbackhdc();

    ShapeFace  &face(*scl.face[curface]);

    HBITMAP hold1,hold2;

    cpix    size = face.size;

    short   bltx = curx - (size>>1);
    short   blty = cury - (size>>1);

    oldx    = bltx;
    oldy    = blty;
    oldface = curface;
    oldrot  = currot;
    oldzoom = curzoom;

    // --- video display plot ---

#ifdef FAST
    cpix    fx = bltx;
    cpix    fy = blty;
    cpix    bx = CURFACEX;
    cpix    by = CURFACEY;
    cpix    w  = size;
    cpix    h  = size;
    cpix    d;

    // if at least one of the target corners lies inside frame:
    if(     scl.checkbnd(fx  ,fy  ) == OK
        ||  scl.checkbnd(fx+w,fy  ) == OK
        ||  scl.checkbnd(fx  ,fy+h) == OK
        ||  scl.checkbnd(fx+w,fy+h) == OK
      )
    {
        // clip it:
        if(fx   < scl.cfrm(0))  { d=scl.cfrm(0)-fx;     fx+=d;  w-=d; bx+=d; }
        if(fx+w > scl.cfrm(2))  { d=fx+w-scl.cfrm(2);           w-=d;        }
        if(fy   < scl.cfrm(1))  { d=scl.cfrm(1)-fy;     fy+=d;  h-=d; by+=d; }
        if(fy+h > scl.cfrm(3))  { d=fy+h-scl.cfrm(3);           h-=d;        }

//      HPALETTE hpal1 = SelectPalette(scl.hdcsrc, scl.pd.logpal(), FALSE);

        // 1. blot out target area
        SetTextColor(hdctarg, RGB(0,0,0));
        SetBkColor  (hdctarg, RGB(255,255,255));
        hold1 = SelectObject(scl.hdcsrc, face.tr.hbmpmask);
            BitBlt(hdctarg,fx,fy,w,h,scl.hdcsrc,bx,by,SRCAND);
        SelectObject(scl.hdcsrc, hold1);

        // 2. OR color source with target
        hold1 = SelectObject(scl.hdcsrc, face.tr.hbmp);
            BitBlt(hdctarg,fx,fy,w,h,scl.hdcsrc,bx,by,SRCPAINT);
        SelectObject(scl.hdcsrc, hold1);

//      SelectPalette(scl.hdcsrc, hpal1, FALSE);
    }
#else
    // A. COPY FRONT TO BACKSTAMP

    // 1. Save background of target area:
    hold1 = SelectObject(scl.hdcdst, hbmpback);
     BitBlt(scl.hdcdst,0,0,size,size,hdcback,bltx,blty,SRCCOPY);
    SelectObject(scl.hdcdst, hold1);

    // 2. Copy background to backstamp
    hold1 = SelectObject(scl.hdcsrc, hbmpback);
    hold2 = SelectObject(scl.hdcdst, face.hbmpstamp);
     BitBlt(scl.hdcdst,0,0,size,size,scl.hdcsrc,0,0,SRCCOPY);
    SelectObject(scl.hdcsrc, hold1);
    SelectObject(scl.hdcdst, hold2);

    // B. IN THE BACKSTAMP...

    // blit color shape w/o changing surroundings:
    // 1. blot out target area
    SetTextColor(scl.hdcdst, RGB(0,0,0));
    SetBkColor  (scl.hdcdst, RGB(255,255,255));
    hold1 = SelectObject(scl.hdcsrc, face.tr.hbmpmask);
    hold2 = SelectObject(scl.hdcdst, face.hbmpstamp);
        BitBlt(scl.hdcdst,0,0,size,size,scl.hdcsrc,CURFACEX,CURFACEY,SRCAND);
    SelectObject(scl.hdcsrc, hold1);
    SelectObject(scl.hdcdst, hold2);

    // 2. OR color source with target
    hold1 = SelectObject(scl.hdcsrc, face.tr.hbmp);
    hold2 = SelectObject(scl.hdcdst, face.hbmpstamp);
        BitBlt(scl.hdcdst,0,0,size,size,scl.hdcsrc,CURFACEX,CURFACEY,SRCPAINT);
    SelectObject(scl.hdcsrc, hold1);
    SelectObject(scl.hdcdst, hold2);

    // C. COPY BACKSTAMP TO FRONT

    hold1 = SelectObject(scl.hdcsrc, face.hbmpstamp);
        BitBlt(hdctarg,bltx,blty,size,size,scl.hdcsrc,0,0,SRCCOPY);
    SelectObject(scl.hdcsrc, hold1);
#endif

    plotted = 1;

    // --- PixelDisplay framebuffer plot ---

    if(face.useframebuf())
    {
        FrameBuffer &fbuf(scl.pd.fbuf);

        cpix fbxstart, fbystart, bmxstart;
        fbxstart = bltx;
        fbystart = blty;

        if(     fbxstart < scl.cfrm(0)
            ||  fbxstart > scl.cfrm(2) - size)
            return OK;  // no clipping!

        uchar *pdst,*psrc;
        uchar  i1st,iln;
        ushort x,y;
        uchar *linfo = face.li_start(currot, curzoom);
        cpix   framey;
        cpix   cfm1 = scl.cfrm(1);
        cpix   cfm3 = scl.cfrm(3);
        for(y=0; y<size; y++)
        {
            if(!(iln = linfo[(y<<1)+1]))
                continue;

            if(        ((framey = y + fbystart) < cfm1)
                ||       framey > cfm3  )
                continue;   // clip line

            i1st = linfo[y<<1];
            pdst = fbuf.ladr[framey & fbuf.iadrmsk];
            memset(pdst + fbxstart + i1st, curcol, iln);
        }
    }

    return OK;
}

rcode Shape::unplot(void)
{
    if(!plotted)
        return OK;  // nothing to unplot

    HDC hdctarg = scl.pd.getcurhdc();
    HDC hdcback = hdctarg;
    ShapeFace  &face(*scl.face[oldface]);
    HBITMAP hold1, hold2;
    cpix    size = face.size;
    if(scl.pd.getbackhdc())
       hdcback = scl.pd.getbackhdc();

#ifdef FAST
    cpix    fx = oldx;
    cpix    fy = oldy;
    cpix    w  = size;
    cpix    h  = size;
    cpix    d;

    // if at least one of the target corners lies inside frame:
    if(     scl.checkbnd(fx  ,fy  ) == OK
        ||  scl.checkbnd(fx+w,fy  ) == OK
        ||  scl.checkbnd(fx  ,fy+h) == OK
        ||  scl.checkbnd(fx+w,fy+h) == OK
      )
    {
        // clip it:
        if(fx   < scl.cfrm(0))  { d=scl.cfrm(0)-fx;     fx+=d;  w-=d; }
        if(fx+w > scl.cfrm(2))  { d=fx+w-scl.cfrm(2);           w-=d; }
        if(fy   < scl.cfrm(1))  { d=scl.cfrm(1)-fy;     fy+=d;  h-=d; }
        if(fy+h > scl.cfrm(3))  { d=fy+h-scl.cfrm(3);           h-=d; }

        BitBlt(hdctarg,fx,fy,w,h,hdcback,fx,fy,SRCCOPY);
    }
#else
    // UNPLOT target area by restoring ONLY CHANGED pixels:

    // 0. COPY FRONT TO BACKSTAMP
    hold1 = SelectObject(scl.hdcdst, face.hbmpstamp);
     BitBlt(scl.hdcdst,0,0,size,size,hdctarg,oldx,oldy,SRCCOPY);
    SelectObject(scl.hdcdst, hold1);

    // 1. The trouble is, there might be lots of garbage in the
    //    saved background. All pixels NOT belonging to the hot area
    //    are considered hostile, and must be blotted out:
    hold1 = SelectObject(scl.hdcsrc, face.tr.hbmpmask);
    hold2 = SelectObject(scl.hdcdst, hbmpback);
     SetTextColor(scl.hdcdst, RGB(255,255,255));
     SetBkColor  (scl.hdcdst, RGB(0,0,0));
     BitBlt(scl.hdcdst,0,0,size,size,scl.hdcsrc,OLDFACEX,OLDFACEY,SRCAND);
    SelectObject(scl.hdcsrc, hold1);
    SelectObject(scl.hdcdst, hold2);

    // ... now, only the hot area is left in saved background.
    // 2. blot out hot target area in backstamp
    hold1 = SelectObject(scl.hdcsrc , face.tr.hbmpmask);
    hold2 = SelectObject(scl.hdcdst , face.hbmpstamp);
     SetTextColor(scl.hdcdst, RGB(0,0,0));
     SetBkColor  (scl.hdcdst, RGB(255,255,255));
     BitBlt(scl.hdcdst,0,0,size,size,scl.hdcsrc,OLDFACEX,OLDFACEY,SRCAND);
    SelectObject(scl.hdcsrc , hold1);
    SelectObject(scl.hdcdst , hold2);

    // 3. OR backstamp with hot background
    hold1 = SelectObject(scl.hdcsrc , hbmpback);
    hold2 = SelectObject(scl.hdcdst , face.hbmpstamp);
     BitBlt(scl.hdcdst,0,0,size,size,scl.hdcsrc,0,0,SRCPAINT);
    SelectObject(scl.hdcsrc , hold1);
    SelectObject(scl.hdcdst , hold2);

    // 4. COPY BACKSTAMP TO FRONT
    hold1 = SelectObject(scl.hdcsrc , face.hbmpstamp);
     BitBlt(hdctarg,oldx,oldy,size,size,scl.hdcsrc,0,0,SRCCOPY);
    SelectObject(scl.hdcsrc , hold1);
#endif

    plotted = 0;

    // --- PixelDisplay framebuffer unplot ---

    if(face.useframebuf())
    {
        FrameBuffer &fbuf(scl.pd.fbuf);

        cpix fbxstart, fbystart, bmxstart;
        fbxstart = oldx;
        fbystart = oldy;

        if(     fbxstart < scl.cfrm(0)
                ||  fbxstart > scl.cfrm(2) - size)
            return OK;  // no clipping!

        uchar *pdst,*psrc;
        uchar  i1st,iln;
        ushort x,y;
        uchar *linfo = face.li_start(oldrot, curzoom);
        cpix   framey;
        cpix   cfm1 = scl.cfrm(1);
        cpix   cfm3 = scl.cfrm(3);
        for(y=0; y<size; y++)
        {
            if(!(iln = linfo[(y<<1)+1]))
                continue;

            if(     ((framey = y + fbystart) < cfm1)
               ||   framey > cfm3  )
                continue;   // clip line

            i1st = linfo[y<<1];
            pdst = fbuf.ladr[framey & fbuf.iadrmsk];
            memset(pdst + fbxstart + i1st, 0, iln);
        }
    }

    return OK;
}

#endif

rcode Shape::turnto(ushort angle)
{
    ShapeFace  &face(*scl.face[curface]);

    curftangle  = angle;
    currot      = (long)angle * face.nrot / scl.ft.degmax();
    currot     &= face.nrot - 1;
    return OK;
}

ushort Shape::truncangle(void)
{
    ShapeFace  &face(*scl.face[curface]);

    return currot * ftrig.degmax() / max(1, face.nrot);
}

bool Shape::ispixset(cpix x,cpix y)
{
    ShapeFace  &face(*scl.face[curface]);
    cpix size = face.size;
    cpix zx   = x + (size >> 1); // zero-based x
    cpix zy   = y + (size >> 1); // zero-based y

    if(zx < 0 || zy < 0 || zx >= size || zy >= size)
    return 0;

    uchar *linfo = face.li_start(currot, curzoom);
    uchar  i1st  = linfo[zy<<1];
    uchar  iln   = linfo[(zy<<1)+1];

    if(!iln)    return 0;
    if(zx < i1st) return 0;
    if(zx > i1st + iln - 1) return 0;
    return 1;
}

rcode Shape::prescan(cpix &obstx, cpix &obsty)
{
    ShapeFace  &face(*scl.face[curface]);
    cpix size = face.size;

    short   bltx = curx - (size>>1);
    short   blty = cury - (size>>1);

    // scan PixelDisplay's framebuffer:
    FrameBuffer &fbuf(scl.pd.fbuf);

    cpix fbxstart, fbystart, bmxstart;
    fbxstart = bltx;
    fbystart = blty;

    if(     fbxstart < scl.cfrm(0)
            ||  fbxstart > scl.cfrm(2) - size)
        return OK;  // no clipping!

    uchar *pdst,*linfo;
    uchar  i1st,iln;
    ushort x,y,x2;
    pcol   c;

    uchar zeroline[SHAPE_MAXSIZE];
    memset(zeroline,0,sizeof(zeroline));

    cpix   framey;
    cpix   cfm1 = scl.cfrm(1);
    cpix   cfm3 = scl.cfrm(3);

    for(y=0; y<size; y++)
    {
        linfo = face.li_start(currot, curzoom);
        i1st  = linfo[y<<1];
        iln   = linfo[(y<<1)+1];
                if(!iln) continue;

        if(        ((framey = y + fbystart) < cfm1)
           ||       framey > cfm3  )
            continue;   // clip line

        pdst = fbuf.ladr[framey & fbuf.iadrmsk];

        if(!memcmp(pdst + fbxstart + i1st, zeroline, iln))
            continue;   // whole target line area is zero, scan next

        x    = i1st;
        x2   = x + iln; // exclusive
        char *pcur = pdst + fbxstart + x;

        for(; x<x2; x++)
        {
            if(!(c = (*pcur++)))
                continue;

            if(             (!obstfn && c)
               ||      ( obstfn && obstfn(c,iuserdata))        )
            {
                obstx   = fbxstart + x;
                obsty   = fbystart + y;
                return  SPECIAL;    // found obstacle pixel
            }
        }
    }

    return OK;  // no obstacles
}

void Shape::pdplot(cpix bx,cpix by)
{
    ShapeFace  &face(*scl.face[curface]);
    face.decodebitmap(currot,curzoom);

    ushort x,y;
    for(x=0; x<face.size; x++)
    for(y=0; y<face.size; y++)
        scl.pd.plot(x+bx,y+by,
        face.bytemap.adr[y*face.size+x]);
}

cpix Shape::frontsize(void)
{
    ShapeFace  &face(*scl.face[curface]);
    return face.frontsize();
}

cpix Shape::backsize(void)
{
    ShapeFace  &face(*scl.face[curface]);
    return face.backsize();
}

void Shape::dirpos(cpix &x, cpix &y,cpix dist)
{
    x = curx + convif(ftrig.sin(curftangle) * dist);
    y = cury - convif(ftrig.cos(curftangle) * dist);
}

void Shape::frontpos(cpix &x, cpix &y,cpix offs)
{   dirpos(x,y,frontsize()+offs);   }

void Shape::backpos(cpix &x, cpix &y,cpix offs)
{   dirpos(x,y,0-backsize()-offs);  }

rcode Shape::setface(ushort fidx)
{
    if(fidx >= scl.nfaces)  {SysErr(1911951830); return INDATA;}
    curface = fidx;
    return OK;
}

void Shape::setcolcode(pcol colcode)
{
    curcol = colcode;
}

void splitrgb(ushort rgb, ushort& r,ushort& g,ushort& b)
{
    r = (rgb >> 8) & 0xFU;
    g = (rgb >> 4) & 0xFU;
    b = (rgb     ) & 0xFU;
}

rcode setPalette(HDC hdc,ushort ncolors, ushort* palette)
{
    LOGPALETTE  *psrcpal;

    ifn(psrcpal = (LPLOGPALETTE)(new char[sizeof(LOGPALETTE)
            + sizeof(PALETTEENTRY) * ncolors]))
    {   MemErr(2909940939); return MEMORY;  }

    psrcpal->palVersion     = 0x300;
    psrcpal->palNumEntries  = ncolors;

    ushort r,g,b;

    // first, reset ALL system palette colors to a defined state
    for(ushort isys=0; isys<ncolors; isys++)
    {
        psrcpal->palPalEntry[isys].peRed    = 0;
        psrcpal->palPalEntry[isys].peGreen  = 0;
        psrcpal->palPalEntry[isys].peBlue   = 0;
        psrcpal->palPalEntry[isys].peFlags  = PC_NOCOLLAPSE;
    }

    // second, initially load the already defined colors
    for(ushort lpi=0; lpi<ncolors; lpi++)
    {
        splitrgb(palette[lpi],r,g,b);
        psrcpal->palPalEntry[isys].peRed    = r;
        psrcpal->palPalEntry[isys].peGreen  = g;
        psrcpal->palPalEntry[isys].peBlue   = b;
        psrcpal->palPalEntry[isys].peFlags  = PC_NOCOLLAPSE;
    }

    HPALETTE hlogpal,horgpal;

    // create logical palette from source palette
    ifn(hlogpal = CreatePalette(psrcpal))
    {   perr("can't create palette"); return FAILED;    }

    delete [] (char*)psrcpal;

    ifn(horgpal = SelectPalette(hdc, hlogpal, FALSE))
    {   perr("can't select palette"); return FAILED;    }
    RealizePalette(hdc);
    SelectPalette(hdc, horgpal, FALSE);
    DeleteObject(hlogpal);

    return OK;
}
