#help_index "Graphics/Sprite;Sprites"
/*
CSprites are stored as a sequence of variable length
operations with a 1-byte type leading
each operation.  They are stored, one after another,
in a chunk of memory terminated by a zero.
Sprite3() shows how the CSprite unions are used.

SpriteElemSize() will return the size of a single
element, while SpriteSize() will return the size
of an entire list.  Look at sprite_elem_base_sizes.

See ::/Apps/GrModels for an example of
making CSprite by hand.  It uses SPT_MESH,
one of the most complicated.
*/

public U0 Sprite3(CDC *dc=gr.dc, I64 x, I64 y, I64 z, U8 *elems, Bool just_one_elem=FALSE)
{//Plot a sprite into a CDC.
    CSprite         *tmpg = elems - offset(CSprite.start);
    I64              i, j, k, x1, y1, z1, x2, y2, *old_r, *r2, old_flags = dc->flags, old_pen_width = dc->thick;
    I32             *ptr;
    CColorROPU32     old_color = dc->color;
    CDC             *img;
    CD3I32          *p, *p2;
    CGrSym           old_sym;

    MemCopy(&old_sym, &dc->sym, sizeof(CGrSym));
    if (dc->flags & DCF_LOCATE_NEAREST)
        dc->nearest_dist = I64_MAX;
    while (tmpg->type & SPG_TYPE_MASK)
    {
        switch (tmpg->type & SPG_TYPE_MASK)
        {
            case SPT_COLOR:
                dc->color = dc->color & ~(COLORROP_COLORS_MASK | ROPF_DITHER) | tmpg->c.color;
                break;

            case SPT_DITHER_COLOR:
                dc->color = dc->color & ~COLORROP_COLORS_MASK |
                            tmpg->d.dither_color.u8[0] | tmpg->d.dither_color.u8[1] << COLORROP_BITS | ROPF_DITHER;
                break;

            case SPT_THICK:
                dc->thick = tmpg->t.thick;
                DCThickScale(dc);
                break;

            case SPT_TRANSFORM_ON:
                if (!(dc->flags & DCF_TRANSFORMATION))
                {
                    x -= dc->x;
                    y -= dc->y;
                    z -= dc->z;
                }
                dc->flags |= DCF_TRANSFORMATION;
                break;

            case SPT_TRANSFORM_OFF:
                if (dc->flags & DCF_TRANSFORMATION)
                {
                    x += dc->x;
                    y += dc->y;
                    z += dc->z;
                }
                dc->flags &= ~DCF_TRANSFORMATION;
                break;

            case SPT_PT:
                GrPlot3(dc, tmpg->p.x1 + x, tmpg->p.y1 + y, z);
                break;

            case SPT_TEXT:
                GrPrint3(dc, tmpg->ps.x1 + x, tmpg->ps.y1 + y, z, "%s", tmpg->ps.st);
                break;

            case SPT_TEXT_BOX:
                GrTextBox3(dc, tmpg->ps.x1 + x, tmpg->ps.y1 + y, z, tmpg->ps.st);
                break;

            case SPT_TEXT_DIAMOND:
                GrTextDiamond3(dc, tmpg->ps.x1 + x, tmpg->ps.y1 + y, z, tmpg->ps.st);
                break;

            case SPT_FLOOD_FILL:
                GrFloodFill3(dc, tmpg->p.x1 + x, tmpg->p.y1 + y, z, FALSE);
                break;

            case SPT_FLOOD_FILL_NOT:
                i = dc->color;
                dc->color = dc->color.c0;
                GrFloodFill3(dc, tmpg->p.x1 + x, tmpg->p.y1 + y, z, TRUE);
                dc->color = i;
                break;

            case SPT_SHIFT:
                x += tmpg->p.x1;
                y += tmpg->p.y1;
                break;

            case SPT_LINE:
                GrLine3(dc, tmpg->pp.x1 + x, tmpg->pp.y1 + y, z, tmpg->pp.x2 + x, tmpg->pp.y2 + y, z);
                break;

            case SPT_ARROW:
                GrArrow3(dc, tmpg->pp.x1 + x, tmpg->pp.y1 + y, z, tmpg->pp.x2 + x, tmpg->pp.y2 + y, z);
                break;

            case SPT_PLANAR_SYMMETRY:
                if (DCSymmetry3Set(dc,  tmpg->pp.x1 + x, tmpg->pp.y1 + y, z,
                                        tmpg->pp.x2 + x, tmpg->pp.y2 + y, z,
                                        tmpg->pp.x2 + x, tmpg->pp.y2 + y, z + 1))
                    dc->flags |= DCF_SYMMETRY;
                else
                    dc->flags &= ~DCF_SYMMETRY;
                break;

            case SPT_BITMAP:
                img = CAlloc(sizeof(CDC));
                img->width          = tmpg->pwhu.width;
                img->width_internal = (tmpg->pwhu.width + 7) & ~7;
                img->height         = tmpg->pwhu.height;
                img->body           = &tmpg->pwhu.u;
                img->dc_signature   = DCS_SIGNATURE_VAL;
                GrBlot3(dc, tmpg->pwhu.x1 + x, tmpg->pwhu.y1 + y, z, img);
                Free(img);
                break;

            case SPT_RECT:
                GrRect3(dc, tmpg->pp.x1 + x, tmpg->pp.y1 + y, z, tmpg->pp.x2 - tmpg->pp.x1, tmpg->pp.y2 - tmpg->pp.y1);
                break;

            case SPT_ROTATED_RECT:
                x1 = tmpg->ppa.x1 + x;
                y1 = tmpg->ppa.y1 + y;
                z1 = z;
                Mat4x4MulXYZ(dc->r, &x1, &y1, &z1);
                old_r = dc->r;
                dc->flags |= DCF_TRANSFORMATION;
                r2 = Mat4x4IdentNew;
                Mat4x4RotZ(r2, -tmpg->ppa.angle);
                Mat4x4TranslationEqu(r2, x1, y1, z1);
                DCMat4x4Set(dc, Mat4x4MulMat4x4New(old_r, r2));
                GrRect3(dc, 0, 0, 0, tmpg->ppa.x2 - tmpg->ppa.x1, tmpg->ppa.y2 - tmpg->ppa.y1);
                Free(dc->r);
                Free(r2);
                DCMat4x4Set(dc, old_r);
                dc->flags = dc->flags & ~DCF_TRANSFORMATION | old_flags;
                break;

            case SPT_CIRCLE:
                GrCircle3(dc, tmpg->pr.x1 + x, tmpg->pr.y1 + y, z, tmpg->pr.radius);
                break;

            case SPT_ELLIPSE:
                GrEllipse3(dc,
                           tmpg->pwha.x1 + x, tmpg->pwha.y1 + y, z, tmpg->pwha.width, tmpg->pwha.height, tmpg->pwha.angle);
                break;

            case SPT_POLYGON:
                GrRegPoly3(dc,  tmpg->pwhas.x1 + x, tmpg->pwhas.y1 + y, z,
                                tmpg->pwhas.width, tmpg->pwhas.height, tmpg->pwhas.sides, tmpg->pwhas.angle);
                break;

            case SPT_POLYLINE:
                ptr = &tmpg->nu.u;
                x1 = ptr[0];
                y1 = ptr[1];
                for (i = 1; i < tmpg->nu.num; i++)
                {
                    x2 = ptr[i << 1];
                    y2 = ptr[i << 1 + 1];
                    GrLine3(dc, x1 + x, y1 + y, z, x2 + x, y2 + y, z);
                    x1 = x2;
                    y1 = y2;
                }
                break;

            case SPT_POLYPT:
                x1 = tmpg->npu.x;
                y1 = tmpg->npu.y;
                ptr = &tmpg->npu.u;
                k = tmpg->npu.num * 3;
                GrPlot3(dc, x1 + x, y1 + y, z);
                for (i = 0; i < k; i += 3) {
                    j = BFieldExtU32(ptr, i, 3);
                    x1 += gr_x_offsets[j];
                    y1 += gr_y_offsets[j];
                    GrPlot3(dc, x1 + x, y1 + y, z);
                }
                break;
            start:
                p2 = p = MAlloc(tmpg->nu.num * sizeof(CD3I32));
                MemCopy(p, &tmpg->nu.u, tmpg->nu.num * sizeof(CD3I32));
                for (i = 0; i < tmpg->nu.num; i++, p2++)
                {
                    p2->x += x;
                    p2->y += y;
                    p2->z += z;
                }
                case SPT_BSPLINE2:
                    Gr2BSpline3(dc, p, tmpg->nu.num, FALSE);
                    break;

                case SPT_BSPLINE3:
                    Gr3BSpline3(dc, p, tmpg->nu.num, FALSE);
                    break;

                case SPT_BSPLINE2_CLOSED:
                    Gr2BSpline3(dc, p, tmpg->nu.num, TRUE);
                    break;

                case SPT_BSPLINE3_CLOSED:
                    Gr3BSpline3(dc, p, tmpg->nu.num, TRUE);
                    break;
            end:
                Free(p);
                break;

            case SPT_MESH:
                p2 = p = MAlloc(tmpg->mu.vertex_count * sizeof(CD3I32));
                MemCopy(p, &tmpg->mu.u, tmpg->mu.vertex_count * sizeof(CD3I32));
                for (i = 0; i < tmpg->mu.vertex_count; i++, p2++)
                {
                    p2->x += x;
                    p2->y += y;
                    p2->z += z;
                }
                Gr3Mesh(dc, tmpg->mu.vertex_count, p, tmpg->mu.tri_count,
                            (&tmpg->mu.u)(U8 *) + sizeof(CD3I32) * tmpg->mu.vertex_count);
                Free(p);
                break;

            case SPT_SHIFTABLE_MESH:
                if (dc->flags & DCF_TRANSFORMATION)
                {
                    dc->x += tmpg->pmu.x;
                    dc->y += tmpg->pmu.y;
                    dc->z += tmpg->pmu.z;
                    x1 = x;
                    y1 = y;
                    z1 = z;
                }
                else
                {
                    x1 = tmpg->pmu.x + x;
                    y1 = tmpg->pmu.y + y;
                    z1 = tmpg->pmu.z + z;
                }
                p2 = p = MAlloc(tmpg->pmu.vertex_count * sizeof(CD3I32));
                MemCopy(p, &tmpg->pmu.u, tmpg->pmu.vertex_count * sizeof(CD3I32));
                for (i = 0; i < tmpg->pmu.vertex_count; i++, p2++)
                {
                    p2->x += x1;
                    p2->y += y1;
                    p2->z += z1;
                }
                Gr3Mesh(dc, tmpg->pmu.vertex_count, p, tmpg->pmu.tri_count, 
                            (&tmpg->pmu.u)(U8 *) + sizeof(CD3I32) * tmpg->pmu.vertex_count);
                Free(p);
                if (dc->flags & DCF_TRANSFORMATION)
                {
                    dc->x -= tmpg->pmu.x;
                    dc->y -= tmpg->pmu.y;
                    dc->z -= tmpg->pmu.z;
                }
                break;
        }
        if (just_one_elem)
            break;
        tmpg(U8 *) += SpriteElemSize(tmpg);
    }
    MemCopy(&dc->sym, &old_sym, sizeof(CGrSym));
    dc->color = old_color;
    dc->thick = old_pen_width;
    dc->flags = dc->flags & ~(DCF_SYMMETRY | DCF_TRANSFORMATION) | old_flags & (DCF_SYMMETRY | DCF_TRANSFORMATION);
}

public U0 Sprite3B(CDC *dc=gr.dc, I64 x, I64 y, I64 z, U8 *elems)
{//Plot a sprite into a CDC, post transform xyz translation.
    I64 old_x = dc->x, old_y = dc->y, old_z = dc->z, old_flags = dc->flags & DCF_TRANSFORMATION;

    dc->x = x;
    dc->y = y;
    dc->z = z;
    dc->flags |= DCF_TRANSFORMATION;
    Sprite3(dc, 0, 0, 0, elems);
    dc->x       = old_x;
    dc->y       = old_y;
    dc->z       = old_z;
    dc->flags   = dc->flags & ~DCF_TRANSFORMATION | old_flags;
}

public U0 Sprite3Mat4x4B(CDC *dc=gr.dc, I64 x, I64 y, I64 z, U8 *elems, I64 *m)
{//Plot rotated by matrix.
    I64 r[16], *old_r = dc->r, new_m[16], old_flags = dc->flags & DCF_TRANSFORMATION;

    MemCopy(new_m, m, 16 * sizeof(I64));
    dc->flags |= DCF_TRANSFORMATION;
    Mat4x4TranslationAdd(new_m, x, y, z);
    dc->r=Mat4x4MulMat4x4Equ(r, old_r, new_m);
    Sprite3(dc, 0, 0, 0, elems);
    dc->r       = old_r;
    dc->flags   = dc->flags & ~DCF_TRANSFORMATION | old_flags;
}

public U0 Sprite3XB(CDC *dc=gr.dc, I64 x, I64 y, I64 z, U8 *elems, F64 phi=0)
{//Plot rotated around X axis.
    I64 r[16];

    Mat4x4IdentEqu(r);
    Mat4x4RotX(r, phi);
    Sprite3Mat4x4B(dc, x, y, z, elems, r);
}

public U0 Sprite3YB(CDC *dc=gr.dc, I64 x, I64 y, I64 z, U8 *elems, F64 omega=0)
{//Plot rotated around Y axis.
    I64 r[16];

    Mat4x4IdentEqu(r);
    Mat4x4RotY(r, omega);
    Sprite3Mat4x4B(dc, x, y, z, elems, r);
}

public U0 Sprite3ZB(CDC *dc=gr.dc, I64 x, I64 y, I64 z, U8 *elems, F64 theta=0)
{//Plot rotated around Z axis.
    I64 r[16];

    Mat4x4IdentEqu(r);
    Mat4x4RotZ(r, theta);
    Sprite3Mat4x4B(dc, x, y, z, elems, r);
}

public U0 SpriteExtents(U8 *elems, I64 *min_x=NULL, I64 *max_x=NULL, I64 *min_y=NULL, I64 *max_y=NULL)
{//Ignores flood fills.
    CDC *dc = DCNew(I32_MAX, I32_MAX, Fs, TRUE);

    DCExtentsInit(dc);
    Sprite3(dc, I32_MAX / 2, I32_MAX / 2, I32_MAX / 2, elems);
    if (dc->min_x <= dc->max_x)
    {
        dc->min_x -= I32_MAX / 2;
        dc->max_x -= I32_MAX / 2;
    }
    if (dc->min_y <= dc->max_y)
    {
        dc->min_y -= I32_MAX / 2;
        dc->max_y -= I32_MAX / 2;
    }
    if (min_x)
        *min_x = dc->min_x;
    if (max_x)
        *max_x = dc->max_x;
    if (min_y)
        *min_y = dc->min_y;
    if (max_y)
        *max_y = dc->max_y;
    DCDel(dc);
}

public CDC *Sprite2DC(U8 *elems)
{//Convert sprite to device context.
    CDC *res;
    I64  min_x, max_x, min_y, max_y;

    SpriteExtents(elems, &min_x, &max_x, &min_y, &max_y);
    res = DCNew(max_x - min_x + 1, max_y - min_y + 1);
    Sprite3(res, -min_x, -min_y, 0, elems);

    return res;
}

public U8 *SpriteInterpolate(F64 t, U8 *elems0, U8 *elems1)
{//The two CSprite should be ident except for points shifted around.
//t ranges from 0.0 to 1.0.
    I64      i, t1 = GR_SCALE * t, t0 = GR_SCALE - t1;
    I32     *ptr0, *ptr1, *ptrr;
    CD3I32  *p0, *p1, *pr;
    U8      *res;
    CSprite *tmpg0 = elems0 - offset(CSprite.start), *tmpg1 = elems1 - offset(CSprite.start), *tmpgr;

    if (t < 0.5)
    {
        i = SpriteSize(elems0), res = MAlloc(i);
        MemCopy(res, elems0, i);
    }
    else
    {
        i = SpriteSize(elems1), res = MAlloc(i);
        MemCopy(res, elems1, i);
    }
    tmpgr = res - offset(CSprite.start);
    while (tmpg0->type & SPG_TYPE_MASK)
    {
        if (tmpg0->type & SPG_TYPE_MASK != tmpg1->type & SPG_TYPE_MASK)
            throw('Graphics');
        switch (tmpg0->type & SPG_TYPE_MASK)
        {
            case SPT_ROTATED_RECT:
                tmpgr->ppa.angle = (tmpg0->ppa.angle * t0 + tmpg1->ppa.angle * t1) / GR_SCALE;
            case SPT_RECT:
            case SPT_LINE:
            case SPT_ARROW:
            case SPT_PLANAR_SYMMETRY:
                tmpgr->pp.x2 = (tmpg0->pp.x2 * t0 + tmpg1->pp.x2 * t1) >> 32;
                tmpgr->pp.y2 = (tmpg0->pp.y2 * t0 + tmpg1->pp.y2 * t1) >> 32;
            case SPT_TEXT:
            case SPT_TEXT_BOX:
            case SPT_TEXT_DIAMOND:
            case SPT_PT:
            case SPT_FLOOD_FILL:
            case SPT_FLOOD_FILL_NOT:
            case SPT_SHIFT:
                tmpgr->p.x1 = (tmpg0->p.x1 * t0 + tmpg1->p.x1 * t1) >> 32;
                tmpgr->p.y1 = (tmpg0->p.y1 * t0 + tmpg1->p.y1 * t1) >> 32;
                break;

            case SPT_CIRCLE:
                tmpgr->pr.radius = (tmpg0->pr.radius * t0 + tmpg1->pr.radius * t1) >> 32;
                tmpgr->pr.x1     = (tmpg0->pr.x1     * t0 + tmpg1->pr.x1     * t1) >> 32;
                tmpgr->pr.y1     = (tmpg0->pr.y1     * t0 + tmpg1->pr.y1     * t1) >> 32;
                break;

            case SPT_ELLIPSE:
            case SPT_POLYGON:
                tmpgr->pwha.x1      = (tmpg0->pwha.x1     * t0 + tmpg1->pwha.x1     * t1) >> 32;
                tmpgr->pwha.y1      = (tmpg0->pwha.y1     * t0 + tmpg1->pwha.y1     * t1) >> 32;
                tmpgr->pwha.width   = (tmpg0->pwha.width  * t0 + tmpg1->pwha.width  * t1) >> 32;
                tmpgr->pwha.height  = (tmpg0->pwha.height * t0 + tmpg1->pwha.height * t1) >> 32;
                break;

            case SPT_BITMAP:
                tmpgr->pwhu.x1 = (tmpg0->pwhu.x1 * t0 + tmpg1->pwhu.x1 * t1) >> 32;
                tmpgr->pwhu.y1 = (tmpg0->pwhu.y1 * t0 + tmpg1->pwhu.y1 * t1) >> 32;
                break;

            case SPT_POLYLINE:
                ptr0 = &tmpg0->nu.u;
                ptr1 = &tmpg1->nu.u;
                ptrr = &tmpgr->nu.u;
                for (i = 0; i < tmpg0->nu.num; i++)
                {
                    ptrr[i << 1]     = (ptr0[i << 1]     * t0 + ptr1[i << 1]     * t1) >> 32;
                    ptrr[i << 1 + 1] = (ptr0[i << 1 + 1] * t0 + ptr1[i << 1 + 1] * t1) >> 32;
                }
                break;

            case SPT_POLYPT:
                tmpgr->npu.x = (tmpg0->npu.x * t0 + tmpg1->npu.x * t1) >> 32;
                tmpgr->npu.y = (tmpg0->npu.y * t0 + tmpg1->npu.y * t1) >> 32;
                break;

            case SPT_BSPLINE2:
            case SPT_BSPLINE3:
            case SPT_BSPLINE2_CLOSED:
            case SPT_BSPLINE3_CLOSED:
                p0 = &tmpg0->nu.u;
                p1 = &tmpg1->nu.u;
                pr = &tmpgr->nu.u;
                for (i = 0; i < tmpg0->nu.num; i++)
                {
                    pr[i].x = (p0[i].x * t0 + p1[i].x * t1) >> 32;
                    pr[i].y = (p0[i].y * t0 + p1[i].y * t1) >> 32;
                    pr[i].z = (p0[i].z * t0 + p1[i].z * t1) >> 32;
                }
                break;

            case SPT_MESH:
                p0 = &tmpg0->mu.u;
                p1 = &tmpg1->mu.u;
                pr = &tmpgr->mu.u;
                for (i = 0; i < tmpg0->mu.vertex_count; i++)
                {
                    pr[i].x = (p0[i].x * t0 + p1[i].x * t1) >> 32;
                    pr[i].y = (p0[i].y * t0 + p1[i].y * t1) >> 32;
                    pr[i].z = (p0[i].z * t0 + p1[i].z * t1) >> 32;
                }
                break;

            case SPT_SHIFTABLE_MESH:
                p0 = &tmpg0->pmu.u;
                p1 = &tmpg1->pmu.u;
                pr = &tmpgr->pmu.u;
                for (i = 0; i < tmpg0->pmu.vertex_count; i++)
                {
                    pr[i].x = (p0[i].x * t0 + p1[i].x * t1) >> 32;
                    pr[i].y = (p0[i].y * t0 + p1[i].y * t1) >> 32;
                    pr[i].z = (p0[i].z * t0 + p1[i].z * t1) >> 32;
                }
                break;
        }
        tmpg0(U8 *) += SpriteElemSize(tmpg0);
        tmpg1(U8 *) += SpriteElemSize(tmpg1);
        tmpgr(U8 *) += SpriteElemSize(tmpgr);
    }

    return res;
}

#help_index "Graphics/Sprite;DolDoc/Output;StdOut/DolDoc"
public CDocEntry *DocSprite(CDoc *doc=NULL, U8 *elems, U8 *format=NULL)
{//Put a sprite into a document.    You can, optionally, supply a format string
//for DolDoc cmd with a %d for the bin_num.
    I64          size;
    U8          *st;
    Bool         unlock;
    CDocEntry   *doc_e;
    CDocBin     *tmpb;

    if (!doc && !(doc = DocPut))
        return NULL;
    unlock = DocLock(doc);
    size = SpriteSize(elems);
    tmpb = CAlloc(sizeof(CDocBin), doc->mem_task);
    tmpb->size      = size;
    tmpb->data      = MAlloc(size, doc->mem_task);
    MemCopy(tmpb->data, elems, size);
    tmpb->num       = doc->cur_bin_num;
    tmpb->use_count = 1;
    QueueInsert(tmpb, doc->bin_head.last);
    if (format)
        st = MStrPrint(format, doc->cur_bin_num++);
    else
        st = MStrPrint("$SP,\"\",BI=%d$", doc->cur_bin_num++);
    doc_e = DocPrint(doc, "%s", st);
    Free(st);
    doc_e->bin_data = tmpb;
    if (doc_e && doc_e->de_flags & DOCEF_TAG && doc_e->tag && *doc_e->tag)
        tmpb->tag = StrNew(doc_e->tag, doc->mem_task);
    if (unlock)
        DocUnlock(doc);

    return doc_e;
}

public CDocEntry *Sprite(U8 *elems, U8 *format=NULL)
{//Put sprite to the command-line, DocPut.
//If you set format, then include dollars ("$SP ...$") and leave %d for num.
    CDoc *doc;

    if (doc = DocPut)
        return DocSprite(doc, elems, format);

    return NULL;
}