//This is a whimsical program which demonstrates some techniques.

#define BORDER                  20

#define PTY_PT                  0
#define PTY_CIRCLE              1
#define PTY_LINE                2
#define PTY_SPRITE              3
#define PTY_NUM                 4

extern class PObj;

class PPt
{
        CD3I32 p;
};

class PCircle
{
        PObj    *p;
        I64      radius;
};

class PLine
{
        PObj    *p1, *p2;
};

class PCSprite
{
        PObj    *p;
        U8              *img;
        I64     *r, 
                        *dr; //Rounding error might eventually screw this up
}

class PObj
{
        PObj    *next, *last;
        I64      type, color;
        union
        {
                PPt             p;
                PCircle         c;
                PLine           l;
                PCSprite        g;
        };
};

class PickFrame
{
        PObj    o_head;
        I64     o_counts[PTY_NUM];
        I64     cx, cy;
};

#define IMGS_NUM        3



        <1>/* Graphics Not Rendered in HTML */



        <2>/* Graphics Not Rendered in HTML */




        <3>/* Graphics Not Rendered in HTML */


U8 *imgs[IMGS_NUM] = {<1>, <2>, <3>};

U0 DrawIt(CTask *task, CDC *dc)
{
        I64                     *r, *old_r;
        PickFrame       *pf = FramePtr("PickFrame", task);
        PObj            *tmpo = pf->o_head.next;

        pf->cx = task->pix_width  >> 1;
        pf->cy = task->pix_height >> 1;

        DCDepthBufAlloc(dc);

        dc->color = LTRED;
        dc->thick = 3;
        GrBorder(dc, BORDER, BORDER, 2 * pf->cx-BORDER, 2 * pf->cy-BORDER);

        while (tmpo != &pf->o_head)
        {
                dc->color = tmpo->color;
                switch (tmpo->type)
                {
                        case PTY_PT:
                                GrLine(dc,      pf->cx + tmpo->p.p.x + 2, pf->cy + tmpo->p.p.y + 2, 
                                                        pf->cx + tmpo->p.p.x - 2, pf->cy + tmpo->p.p.y - 2);
                                GrLine(dc,      pf->cx + tmpo->p.p.x - 2, pf->cy + tmpo->p.p.y + 2, 
                                                        pf->cx + tmpo->p.p.x + 2, pf->cy + tmpo->p.p.y - 2);
                                break;

                        case PTY_CIRCLE:
                                GrCircle(dc, pf->cx + tmpo->c.p->p.p.x, pf->cy + tmpo->c.p->p.p.y, tmpo->c.radius);
                                break;

                        case PTY_LINE:
                                GrLine(dc,      pf->cx + tmpo->l.p1->p.p.x, pf->cy + tmpo->l.p1->p.p.y, 
                                                        pf->cx + tmpo->l.p2->p.p.x, pf->cy + tmpo->l.p2->p.p.y);
                                break;

                        case PTY_SPRITE:
                                old_r = dc->r;
                                dc->r = tmpo->g.r;
                                dc->x = pf->cx + tmpo->g.p->p.p.x;
                                dc->y = pf->cy + tmpo->g.p->p.p.y;
                                dc->z = GR_Z_ALL;
                                dc->flags |= DCF_TRANSFORMATION;
                                Sprite3(dc, 0, 0, 0, tmpo->g.img);
                                dc->flags &= ~DCF_TRANSFORMATION;
                                dc->r = old_r;

                                //Updated each refresh, not guarenteed to be uniform.
                                //Rounding error might corrupt, as well.
                                r = Mat4x4MulMat4x4New(tmpo->g.dr, tmpo->g.r, task);
                                Free(tmpo->g.r);
                                tmpo->g.r = r;

                                break;
                }
                tmpo = tmpo->next;
        }
}

PObj *PObjNew(PickFrame *pf, I64 type, I64 color)
{
        PObj *tmpo = CAlloc(sizeof(PObj));

        tmpo->type  = type;
        tmpo->color = color;
        pf->o_counts[type]++;
        QueueInsert(tmpo, pf->o_head.last);

        return tmpo;
}

U0 PObjDel(PickFrame *pf, PObj *tmpo)
{
        QueueRemove(tmpo);
        switch (tmpo->type)
        {
                case PTY_SPRITE:
                        Free(tmpo->g.r);
                        Free(tmpo->g.dr);
                        break;
        }
        pf->o_counts[tmpo->type]--;
        Free(tmpo);
}

PObj *PPtNew(PickFrame *pf, I64 x, I64 y)
{
        PObj *tmpo = PObjNew(pf, PTY_PT, BLACK);

        tmpo->p.p.x = x;
        tmpo->p.p.y = y;

        return tmpo;
}

PObj *PPtNum(PickFrame *pf, I64 num)
{
        PObj *tmpo = pf->o_head.next;

        while (tmpo != &pf->o_head)
        {
                if (tmpo->type == PTY_PT && !num--)
                        return tmpo;
                tmpo = tmpo->next;
        }

        return NULL;
}

PObj *PPtFind(PickFrame *pf, I64 x, I64 y)
{
        I64   dd, best_dd=I64_MAX;
        PObj *tmpo = pf->o_head.next, *res = NULL;

        while (tmpo != &pf->o_head)
        {
                if (tmpo->type == PTY_PT)
                {
                        dd = SqrI64(tmpo->p.p.x - x) + SqrI64(tmpo->p.p.y - y);
                        if (dd < best_dd)
                        {
                                best_dd = dd;
                                res = tmpo;
                        }
                }
                tmpo = tmpo->next;
        }

        return res;
}

PObj *PCircleNew(PickFrame *pf, I64 p_num, I64 r)
{
        PObj *tmpo = PObjNew(pf, PTY_CIRCLE, RED);

        tmpo->c.p               = PPtNum(pf, p_num);
        tmpo->c.radius  = r;

        return tmpo;
}

PObj *PLineNew(PickFrame *pf, I64 p1_num, I64 p2_num)
{
        PObj *tmpo = PObjNew(pf, PTY_LINE, GREEN);

        tmpo->l.p1 = PPtNum(pf, p1_num);
        tmpo->l.p2 = PPtNum(pf, p2_num);

        return tmpo;
}

PObj *PCSpriteNew(PickFrame *pf, U8 *img, I64 p_num, I64 *r, I64 *dr)
{
        PObj *tmpo = PObjNew(pf, PTY_SPRITE, BLACK);

        tmpo->g.p       = PPtNum(pf, p_num);
        tmpo->g.img     = img;
        tmpo->g.r       = r;
        tmpo->g.dr      = dr;

        return tmpo;
}

PickFrame *Init()
{
        PickFrame  *pf = CAlloc(sizeof(PickFrame));
        I64                     i, *r, *dr;

        pf->cx = Fs->pix_width  >> 1;
        pf->cy = Fs->pix_height >> 1;

        pf->o_head.next = pf->o_head.last = &pf->o_head;
        for (i = 0; i < 50; i++)
                PPtNew(pf, RandI32 % (pf->cx - BORDER), RandI32 % (pf->cy - BORDER));
        for (i = 0; i < 20; i++)
                PCircleNew(pf, pf->o_counts[PTY_PT] * RandU16 / U16_MAX, 6);
        for (i = 0; i < 20; i++)
                PLineNew(pf, pf->o_counts[PTY_PT] * RandU16 / U16_MAX, pf->o_counts[PTY_PT] * RandU16 / U16_MAX);
        for (i = 0; i < 10; i++)
        {
                r = Mat4x4IdentNew;
                dr = Mat4x4IdentNew;
                Mat4x4RotZ(dr, 0.05 * 2 * (Rand - 0.5));
                Mat4x4RotY(dr, 0.05 * 2 * (Rand - 0.5));
                Mat4x4RotX(dr, 0.05 * 2 * (Rand - 0.5));
                PCSpriteNew(pf, imgs[IMGS_NUM * RandU16 / U16_MAX], pf->o_counts[PTY_PT] * RandU16 / U16_MAX, r, dr);
        }
        FramePtrSet("PickFrame", pf);

        return pf;
}

U0 CleanUp(PickFrame *pf)
{
        PObj *tmpo = pf->o_head.next, *tmpo1;

        while (tmpo != &pf->o_head)
        {
                tmpo1 = tmpo->next;
                PObjDel(pf, tmpo);
                tmpo = tmpo1;
        }
        Free(pf);
}

U0 Pick3D()
{
        I64                      message_code, arg1, arg2;
        PObj            *tmpo;
        PickFrame       *pf = NULL;

        FramePtrAdd("PickFrame");

        MenuPush(       "File {"
                                "  Abort(,CH_SHIFT_ESC);"
                                "  Exit(,CH_ESC);"
                                "}"
                                "Play {"
                                "  Restart(,'\n');"
                                "}"
                                );
        SettingsPush; //See SettingsPush
        AutoComplete;
        WinBorder;
        WinMax;
        DocClear;
        "$BK,1$Move things around.$BK,0$\n";
        pf = Init;
        tmpo = NULL;
        Fs->win_inhibit = WIG_TASK_DEFAULT - WIF_SELF_FOCUS - WIF_SELF_CTRLS - WIF_FOCUS_TASK_MENU;
        Fs->draw_it             = &DrawIt;
        try
        {
                while (TRUE)
                {
                        switch (message_code = MessageGet(&arg1, &arg2, 1 << MESSAGE_KEY_DOWN | 1 << MESSAGE_MS_L_DOWN |
                                                                                                                        1 << MESSAGE_MS_L_UP  | 1 << MESSAGE_MS_MOVE))
                        {
                                case MESSAGE_KEY_DOWN:
                                        switch (arg1)
                                        {
                                                case '\n':
                                                        CleanUp(pf);
                                                        pf = Init;
                                                        tmpo = NULL;
                                                        break;

                                                case CH_SHIFT_ESC:
                                                case CH_ESC:
                                                        goto pd_done;
                                        }
                                        break;

                                case MESSAGE_MS_L_DOWN:
                                        tmpo = PPtFind(pf, arg1 - pf->cx, arg2 - pf->cy);
                                        break;

                                case MESSAGE_MS_L_UP:
                                        if (tmpo)
                                        {
                                                tmpo->p.p.x = arg1 - pf->cx;
                                                tmpo->p.p.y = arg2 - pf->cy;
                                                tmpo = NULL;
                                        }
                                        break;

                                case MESSAGE_MS_MOVE:
                                        if (tmpo)
                                        {
                                                tmpo->p.p.x = arg1 - pf->cx;
                                                tmpo->p.p.y = arg2 - pf->cy;
                                        }
                                        break;
                        }
                }
pd_done:
                MessageGet(,, 1 << MESSAGE_KEY_UP);
        }
        catch
                PutExcept;
        SettingsPop;
        MenuPop;
        CleanUp(pf);
        FramePtrDel("PickFrame");
}

Pick3D;