//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;