#help_index "Graphics/Sprite;Sprites"

CSprite *SpriteSetSettings(CDC *dc=NULL, CSprite *head, I64 elem_num, I64 x=0, I64 y=0,
                           CColorROPU32 *_color=NULL, I64 *_thick=NULL, I64 *_xx=NULL, I64 *_yy=NULL)
{
    CSprite         *res = head->next;
    I64              thick = 1, xx = 0, yy = 0;
    CColorROPU32     color = BLACK;

    if (dc)
        DCReset(dc);
    while (elem_num-- > 0 && res != head)
    {
        switch (res->type & SPG_TYPE_MASK)
        {
            case SPT_COLOR:
                color = res->c.color;
                if (dc)
                    dc->color = color;
                break;

            case SPT_DITHER_COLOR:
                color = res->d.dither_color.u8[0] | res->d.dither_color.u8[1] << COLORROP_BITS | ROPF_DITHER;
                if (dc)
                    dc->color = color;
                break;

            case SPT_THICK:
                thick = res->t.thick;
                if (dc)
                    dc->thick = thick;
                break;

            case SPT_SHIFT:
                xx += res->p.x1;
                yy += res->p.y1;
                x  += res->p.x1;
                y  += res->p.y1;
                break;

            case SPT_PLANAR_SYMMETRY:
                if (dc)
                {
                    if (DCSymmetry3Set(dc,
                                       res->pp.x1 + x, res->pp.y1 + y, 0,
                                       res->pp.x2 + x, res->pp.y2 + y, 0,
                                       res->pp.x2 + x, res->pp.y2 + y, 1))
                        dc->flags |= DCF_SYMMETRY;
                    else
                        dc->flags &= ~DCF_SYMMETRY;
                }
                break;
        }
        res = res->next;
    }
    if (_color)
        *_color = color;
    if (_thick)
        *_thick = thick;
    if (_xx)
        *_xx = xx;
    if (_yy)
        *_yy = yy;

    return res;
}

Bool SpritePolyPtPlot(CSprite *head, I64 x, I64 y, I64)
{
    CSprite *tmpg = CAlloc(SpriteElemQueuedBaseSize(SPT_PT));


    tmpg->type = SPT_PT;
    tmpg->p.x1 = x;
    tmpg->p.y1 = y;
    QueueInsert(tmpg, head->last);

    return TRUE;
}

CSprite *Sprite2SpriteQueue(U8 *elems)
{
    I64      s;
    CSprite *res = CAlloc(sizeof(CSprite)), *tmpg = elems - offset(CSprite.start), *tmpg1;

    QueueInit(res);
    while (tmpg->type & SPG_TYPE_MASK)
    {
        tmpg1 = MAlloc(SpriteElemSize(tmpg) + offset(CSprite.start));
        s = SpriteElemSize(tmpg);
        MemCopy(&tmpg1->start, &tmpg->start, s);
        QueueInsert(tmpg1, res->last);
        tmpg(U8 *) += s;
    }

    return res;
}

U8 *SpriteQueue2Sprite(CSprite *head, I64 *_size=NULL)
{
    I64      i, size = sprite_elem_base_sizes[SPT_END];
    CSprite *tmpg = head->next;
    U8      *res, *dst;

    while (tmpg != head)
    {
        size += SpriteElemSize(tmpg);
        tmpg = tmpg->next;
    }
    if (_size)
        *_size = size;
    res = dst = MAlloc(size);
    tmpg = head->next;
    while (tmpg != head)
    {
        i = SpriteElemSize(tmpg);
        MemCopy(dst, &tmpg->start, i);
        dst += i;
        tmpg = tmpg->next;
    }
    *dst = SPT_END;

    return res;
}

U0 SpriteEdUpdate(CDoc *doc, CDocEntry *doc_ce, CSprite *head)
{
    CDocBin *tmpb = doc_ce->bin_data;
    I64      size;
    Bool     unlock = DocLock(doc);

    Free(tmpb->data);
    tmpb->data = SpriteQueue2Sprite(head, &size);
    tmpb->size = size;
    if (unlock)
        DocUnlock(doc);
}

U0 SpriteSetOrigin(CSprite *head, I64 dx, I64 dy, I64 dz)
{
    I64      i;
    I32     *ptr;
    CD3I32  *p;
    CSprite *tmpg = head->next;

    while (tmpg != head)
    {
        if (Bt(&tmpg->type, SPf_SEL))
            switch (tmpg->type & SPG_TYPE_MASK)
            {
                case SPT_ARROW:
                case SPT_LINE:
                case SPT_PLANAR_SYMMETRY:
                case SPT_RECT:
                case SPT_ROTATED_RECT:
                    tmpg->pp.x2 += dx;
                    tmpg->pp.y2 += dy;
                case SPT_PT:
                case SPT_FLOOD_FILL:
                case SPT_FLOOD_FILL_NOT:
                case SPT_TEXT:
                case SPT_TEXT_BOX:
                case SPT_TEXT_DIAMOND:
                case SPT_CIRCLE:
                case SPT_BITMAP:
                case SPT_ELLIPSE:
                case SPT_POLYGON:
                    tmpg->p.x1 += dx;
                    tmpg->p.y1 += dy;
                    break;

                case SPT_POLYLINE:
                    ptr = &tmpg->nu.u;
                    for (i = 0; i < tmpg->nu.num; i++)
                    {
                        ptr[i << 1]     += dx;
                        ptr[i << 1 + 1] += dy;
                    }
                    break;

                case SPT_POLYPT:
                    tmpg->npu.x += dx;
                    tmpg->npu.y += dy;
                    break;

                case SPT_BSPLINE2:
                case SPT_BSPLINE3:
                case SPT_BSPLINE2_CLOSED:
                case SPT_BSPLINE3_CLOSED:
                    p = &tmpg->nu.u;
                    for (i = 0; i < tmpg->nu.num; i++, p++)
                    {
                        p->x += dx;
                        p->y += dy;
                        p->z += dz;
                    }
                    break;

                case SPT_MESH:
                    p = &tmpg->mu.u;
                    for (i = 0; i < tmpg->mu.vertex_count; i++, p++)
                    {
                        p->x += dx;
                        p->y += dy;
                        p->z += dz;
                    }
                    break;

                case SPT_SHIFTABLE_MESH:
                    tmpg->pmu.x += dx;
                    tmpg->pmu.y += dy;
                    tmpg->pmu.z += dz;
                    break;
            }
        tmpg = tmpg->next;
    }
}

CSprite *SpriteTransformCircle(I64 *r, CSprite *tmpg)
{
    I64      x, y, z;
    F64      m1, arg1, m2, radius = tmpg->pr.radius << 16;
    CSprite *tmpg1 = CAlloc(SpriteElemQueuedBaseSize(SPT_ELLIPSE));

    tmpg1->type = SPT_ELLIPSE;

    x = tmpg->pr.x1;
    y = tmpg->pr.y1;
    z = 0;
    Mat4x4MulXYZ(r, &x, &y, &z);
    tmpg1->pwha.x1 = x;
    tmpg1->pwha.y1 = y;

    x = radius;
    y = 0;
    z = 0;
    Mat4x4MulXYZ(r, &x, &y, &z);
    R2P(&m1, &arg1, x, y);

    x = 0;
    y = radius;
    z = 0;
    Mat4x4MulXYZ(r, &x, &y, &z);
    m2 = Sqrt(x * x + y * y);

    tmpg1->pwha.width  = ToI64(m1) / 0x10000;
    tmpg1->pwha.height = ToI64(m2) / 0x10000;
    tmpg1->pwha.angle  = -arg1;

    tmpg1->type |= tmpg->type & SPF_SEL;

    return tmpg1;
}

CSprite *SpriteTransformEllipse(I64 *r, CSprite *tmpg)
{
    I64      x, y, z;
    F64      m1, arg1, m2, arg2, s, c, x_radius = tmpg->pwha.width << 16, y_radius = tmpg->pwha.height << 16;
    CSprite *tmpg1 = CAlloc(SpriteElemQueuedBaseSize(tmpg->type & SPG_TYPE_MASK));

    tmpg1->type = tmpg->type;
    if (tmpg->type & SPG_TYPE_MASK == SPT_POLYGON)
        tmpg1->pwhas.sides = tmpg->pwhas.sides;

    x = tmpg->pwha.x1;
    y = tmpg->pwha.y1;
    z = 0;
    Mat4x4MulXYZ(r, &x, &y, &z);
    tmpg1->pwha.x1 = x;
    tmpg1->pwha.y1 = y;

    c = Cos(-tmpg->pwha.angle);
    s = Sin(-tmpg->pwha.angle);

    x = x_radius * c;
    y = x_radius * s;
    z = 0;
    Mat4x4MulXYZ(r, &x, &y, &z);
    R2P(&m1, &arg1, x, y);

    x = -y_radius * s;
    y = y_radius * c;
    z = 0;
    Mat4x4MulXYZ(r, &x, &y, &z);
    R2P(&m2, &arg2, x, y);
    m2 *= Abs(Sin(arg2 - arg1));

    tmpg1->pwha.width = ToI64(m1) / 0x10000;
    if (tmpg1->pwha.width < 1)
        tmpg1->pwha.width = 1;
    tmpg1->pwha.height = ToI64(m2) / 0x10000;
    if (tmpg1->pwha.height < 1)
        tmpg1->pwha.height = 1;
    tmpg1->pwha.angle = -arg1;

    tmpg1->type |= tmpg->type & SPF_SEL;

    return tmpg1;
}

CSprite *SpriteTransformRect(I64 *r, CSprite *tmpg, F64 theta)
{
    I64      x, y, z, w, h;
    F64      m1, arg1, m2, arg2, s, c, 
             x_radius = (tmpg->pp.x2 - tmpg->pp.x1) << 16, 
             y_radius = (tmpg->pp.y2 - tmpg->pp.y1) << 16;
    CSprite *tmpg1 = CAlloc(SpriteElemQueuedBaseSize(SPT_ROTATED_RECT));

    tmpg1->type = SPT_ROTATED_RECT;

    x = tmpg->pp.x1;
    y = tmpg->pp.y1;
    z = 0;
    Mat4x4MulXYZ(r, &x, &y, &z);
    tmpg1->ppa.x1 = x;
    tmpg1->ppa.y1 = y;

    c = Cos(-theta);
    s = Sin(-theta);

    x = x_radius * c;
    y = x_radius * s;
    z = 0;
    Mat4x4MulXYZ(r, &x, &y, &z);
    R2P(&m1, &arg1, x, y);

    x = -y_radius * s;
    y = y_radius * c;
    z = 0;
    Mat4x4MulXYZ(r, &x, &y, &z);
    R2P(&m2, &arg2, x, y);
    m2 *= Abs(Sin(arg2 - arg1));

    w = ToI64(m1) / 0x10000;
    if (w < 1)
        w = 1;
    h = ToI64(m2) / 0x10000;
    if (h < 1)
        h = 1;
    tmpg1->ppa.x2 = tmpg1->ppa.x1 + w;
    tmpg1->ppa.y2 = tmpg1->ppa.y1 + h;
    tmpg1->ppa.angle = -arg1;

    tmpg1->type |= tmpg->type & SPF_SEL;

    return tmpg1;
}

CSprite *SpriteTransformBitMap(I64 *r, CSprite *tmpg)
{
    CDC     *img, *dc3;
    U8      *elems;
    I64      x, y, z, minx, maxx, miny, maxy, minz, maxz;
    CSprite *tmpg1;

    x = tmpg->pwhu.x1;
    y = tmpg->pwhu.y1;
    z = 0;
    Mat4x4MulXYZ(r, &x, &y, &z);
    minx = maxx = x;
    miny = maxy = y;
    minz = maxz = z;

    x = tmpg->pwhu.x1;
    y = tmpg->pwhu.y1 + tmpg->pwhu.height;
    z = 0;
    Mat4x4MulXYZ(r, &x, &y, &z);
    if (x < minx) minx = x;
    if (x > maxx) maxx = x;
    if (y < miny) miny = y;
    if (y > maxy) maxy = y;
    if (z < minz) minz = z;
    if (z > maxz) maxz = z;

    x = tmpg->pwhu.x1 + tmpg->pwhu.width;
    y = tmpg->pwhu.y1;
    z = 0;
    Mat4x4MulXYZ(r, &x, &y, &z);
    if (x < minx) minx = x;
    if (x > maxx) maxx = x;
    if (y < miny) miny = y;
    if (y > maxy) maxy = y;
    if (z < minz) minz = z;
    if (z > maxz) maxz = z;

    x = tmpg->pwhu.x1 + tmpg->pwhu.width;
    y = tmpg->pwhu.y1 + tmpg->pwhu.height;
    z = 0;
    Mat4x4MulXYZ(r, &x, &y, &z);
    if (x < minx) minx = x;
    if (x > maxx) maxx = x;
    if (y < miny) miny = y;
    if (y > maxy) maxy = y;
    if (z < minz) minz = z;
    if (z > maxz) maxz = z;

    dc3 = DCNew(maxx - minx + 1, maxy - miny + 1);

    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;

    dc3->color = TRANSPARENT;
    GrRect(dc3, 0, 0, maxx - minx + 1, maxy - miny + 1);

    Free(dc3->r);
    DCMat4x4Set(dc3, r);
    dc3->flags |= DCF_TRANSFORMATION;

    dc3->x = tmpg->pwhu.x1 - minx;
    dc3->y = tmpg->pwhu.y1 - miny;
    dc3->z = -minz;
    GrBlot3(dc3, 0, 0, 0, img);
    Free(img);

    elems = DC2Sprite(dc3);
    dc3->r = NULL;
    DCDel(dc3);
    tmpg1 = CAlloc(offset(CSprite.start) + MSize(elems));
    MemCopy(tmpg1(U8 *) + offset(CSprite.start), elems, MSize(elems));
    tmpg1->type = tmpg->type;

    x = tmpg->pwhu.x1;
    y = tmpg->pwhu.y1;
    z = 0;
    Mat4x4MulXYZ(r, &x, &y, &z);
    tmpg1->pwhu.x1 = x;
    tmpg1->pwhu.y1 = y;

    return tmpg1;
}

U0 SpriteTransformQueue(CSprite *head, I64 *r)
{
    I64      i, j, k, num, x, y, z, x1, y1, z1, x2, y2, z2, x3, y3, z3;
    I32     *ptr;
    CD3I32  *p;
    CSprite *tmpg = head->next, head2, *tmpg1, *tmpg2, *tmpg3;

    while (tmpg != head)
    {
        if (Bt(&tmpg->type, SPf_SEL))
            switch (tmpg->type & SPG_TYPE_MASK)
            {
                case SPT_THICK:
                    tmpg->t.thick *= Sqrt(Mat4x4NormSqr65536(r)) / 65536;
                    if (tmpg->t.thick < 0)
                        tmpg->t.thick = 0;
                    break;

                case SPT_PLANAR_SYMMETRY:
                case SPT_ARROW:
                case SPT_LINE:
                    x = tmpg->pp.x2;
                    y = tmpg->pp.y2;
                    z = 0;
                    Mat4x4MulXYZ(r, &x, &y, &z);
                    tmpg->pp.x2 = x;
                    tmpg->pp.y2 = y;
                case SPT_PT:
                case SPT_FLOOD_FILL:
                case SPT_FLOOD_FILL_NOT:
                case SPT_TEXT:
                case SPT_TEXT_BOX:
                case SPT_TEXT_DIAMOND:
                    x = tmpg->p.x1;
                    y = tmpg->p.y1;
                    z = 0;
                    Mat4x4MulXYZ(r, &x, &y, &z);
                    tmpg->p.x1 = x;
                    tmpg->p.y1 = y;
                    break;

                case SPT_BITMAP:
                    tmpg1 = SpriteTransformBitMap(r, tmpg);
                    QueueInsert(tmpg1, tmpg);
                    QueueRemove(tmpg);
                    Free(tmpg);
                    tmpg = tmpg1;
                    break;

                case SPT_ROTATED_RECT:
                    tmpg1 = SpriteTransformRect(r, tmpg, tmpg->ppa.angle);
                    QueueInsert(tmpg1, tmpg);
                    QueueRemove(tmpg);
                    Free(tmpg);
                    tmpg = tmpg1;
                    break;

                case SPT_RECT:
                    tmpg1 = SpriteTransformRect(r, tmpg, 0);
                    QueueInsert(tmpg1, tmpg);
                    QueueRemove(tmpg);
                    Free(tmpg);
                    tmpg = tmpg1;
                    break;

                case SPT_CIRCLE:
                    tmpg1 = SpriteTransformCircle(r, tmpg);
                    QueueInsert(tmpg1, tmpg);
                    QueueRemove(tmpg);
                    Free(tmpg);
                    tmpg = tmpg1;
                    break;

                case SPT_ELLIPSE:
                case SPT_POLYGON:
                    tmpg1 = SpriteTransformEllipse(r, tmpg);
                    QueueInsert(tmpg1, tmpg);
                    QueueRemove(tmpg);
                    Free(tmpg);
                    tmpg = tmpg1;
                    break;

                case SPT_POLYLINE:
                    ptr = &tmpg->nu.u;
                    for (i = 0; i < tmpg->nu.num; i++)
                    {
                        x = ptr[i << 1];
                        y = ptr[i << 1 + 1];
                        z = 0;
                        Mat4x4MulXYZ(r, &x, &y, &z);
                        ptr[i << 1]     = x;
                        ptr[i << 1 + 1] = y;
                    }
                    break;

                case SPT_POLYPT:
                    QueueInit(&head2);
                    x = tmpg->npu.x;
                    y = tmpg->npu.y;
                    z = 0;
                    x1 = x;  //
                    y1 = y;  //
                    z1 = z;  //unrotated cur coordinates
                    Mat4x4MulXYZ(r, &x, &y, &z);
                    ptr = &tmpg->npu.u;
                    k = tmpg->npu.num * 3;
                    x2 = x;  //
                    y2 = y;  //
                    z2 = z;  //rotated start coordinates

                    x3 = x;  //
                    y3 = y;  //
                    z3 = z;  //lag 1 rotated coordinates
                    for (i = 0; i < k; i += 3)
                    {
                        j = BFieldExtU32(ptr, i, 3);
                        x1 += gr_x_offsets[j];
                        y1 += gr_y_offsets[j];
                        x = x1;
                        y = y1;
                        z = z1;
                        Mat4x4MulXYZ(r, &x, &y, &z);
                        Line(&head2, x3 - x2, y3 - y2, 0, x - x2, y - y2, 0, &SpritePolyPtPlot);
                        x3 = x;
                        y3 = y;
                        z3 = z;
                    }

                    num = 0;
                    tmpg1 = head2.next;
                    x3 = 0;
                    y3 = 0;
                    z3 = 0;
                    while (tmpg1 != &head2)
                    {
                        tmpg2 = tmpg1->next;
                        if (tmpg1->p.x1 == x3 && tmpg1->p.y1 == y3)
                        {
                            QueueRemove(tmpg1);
                            Free(tmpg1);
                        }
                        else
                        {
                            num++;
                            x3 = tmpg1->p.x1;
                            y3 = tmpg1->p.y1;
                        }
                        tmpg1 = tmpg2;
                    }

                    tmpg3 = CAlloc(SpriteElemQueuedBaseSize(SPT_POLYPT) + (num * 3 + 7) >> 3);
                    tmpg3->npu.x = x2;
                    tmpg3->npu.y = y2;
                    ptr = &tmpg3->npu.u;
                    x3 = 0;
                    y3 = 0;
                    z3 = 0;
                    i = 0;
                    tmpg1 = head2.next;
                    while (tmpg1 != &head2)
                    {
                        tmpg2 = tmpg1->next;
                        BFieldOrU32(ptr, i, polypt_map[SignI64(tmpg1->p.x1 - x3) + 1 + 3 * (SignI64(tmpg1->p.y1 - y3) + 1)]);
                        i += 3;
                        x3 = tmpg1->p.x1;
                        y3 = tmpg1->p.y1;
                        QueueRemove(tmpg1);
                        Free(tmpg1);
                        tmpg1 = tmpg2;
                    }
                    tmpg3->type = SPT_POLYPT | tmpg->type & SPF_SEL;
                    tmpg3->npu.num = num;
                    QueueInsert(tmpg3, tmpg);
                    QueueRemove(tmpg);
                    Free(tmpg);
                    tmpg = tmpg3;
                    break;

                case SPT_BSPLINE2:
                case SPT_BSPLINE3:
                case SPT_BSPLINE2_CLOSED:
                case SPT_BSPLINE3_CLOSED:
                    p = &tmpg->nu.u;
                    for (i = 0; i < tmpg->nu.num; i++, p++)
                    {
                        x = p->x;
                        y = p->y;
                        z = p->z;
                        Mat4x4MulXYZ(r, &x, &y, &z);
                        p->x = x;
                        p->y = y;
                        p->z = z;
                    }
                    break;

                case SPT_SHIFTABLE_MESH:
                    x = tmpg->pmu.x;
                    y = tmpg->pmu.y;
                    z = tmpg->pmu.z;
                    Mat4x4MulXYZ(r, &x, &y, &z);
                    tmpg->pmu.x = x;
                    tmpg->pmu.y = y;
                    tmpg->pmu.z = z;
                    p = &tmpg->pmu.u;
                    for (i = 0; i < tmpg->pmu.vertex_count; i++, p++)
                    {
                        x = p->x;
                        y = p->y;
                        z = p->z;
                        Mat4x4MulXYZ(r, &x, &y, &z);
                        p->x = x;
                        p->y = y;
                        p->z = z;
                    }
                    break;

                case SPT_MESH:
                    p = &tmpg->mu.u;
                    for (i = 0; i < tmpg->mu.vertex_count; i++, p++)
                    {
                        x = p->x;
                        y = p->y;
                        z = p->z;
                        Mat4x4MulXYZ(r, &x, &y, &z);
                        p->x = x;
                        p->y = y;
                        p->z = z;
                    }
                    break;
            }
        tmpg = tmpg->next;
    }
}

I64 SpriteQueueSelCount(CSprite *head, Bool val=TRUE)
{
    I64      res = 0;
    CSprite *tmpg = head->next;

    val = ToBool(val);
    while (tmpg != head)
    {
        if (Bt(&tmpg->type, SPf_SEL) == val)
            res++;
        tmpg = tmpg->next;
    }

    return res;
}

I64 SpriteQueueSelAll(CSprite *head, Bool val=TRUE)
{
    I64      res = 0;
    CSprite *tmpg = head->next;

    while (tmpg != head)
    {
        BEqual(&tmpg->type, SPf_SEL, val);
        res++;
        tmpg = tmpg->next;
    }

    return res;
}

Bool SpriteEdText(CSprite **_head, I64 *_cur_elem_num)
{
    Bool     res;
    CSprite *head = *_head;
    U8      *elems = SpriteQueue2Sprite(head);
    CDoc    *doc = DocNew, *doc2, *old_put = DocPut;

    StrPrint(doc->filename.name, "AI:0x%X", doc);
    DocPrint(doc,
                "//$PURPLE$$TX+CX,\"Sprite Edit as Text\"$$FG$\n"
                "//$LK+PU+CX,\"Click for Help\","
                "A=\"FI:::/Doc/SpriteEdText.DD\"$\n\n");
    Sprite2Code(doc, elems);
    Free(elems);
    while (TRUE)
    {
        if (res = PopUpPrint("DocEd(0x%X,0x%X);", doc, 0))
        {
            Fs->put_doc = doc2 = DocNew;
            "$WW,1$";
            if (elems = Code2Sprite(doc))
            {
                DocDel(doc2);
                Fs->put_doc = old_put;
                QueueDel(head);
                Free(head);
                head = Sprite2SpriteQueue(elems);
                Free(elems);
                *_cur_elem_num = QueueCount(head); //TODO: Might want to improve this.
                break;
            }
            else
            {
                PopUpPrint("DocEd(0x%X,0x%X);", doc2, 0);
                DocDel(doc2);
                Fs->put_doc = old_put;
            }
        }
        else
            break;
    }
    DocDel(doc);
    if (_head)
        *_head = head;

    return res;
}

#define SPED_SEL_UNSEL_ALL      0
#define SPED_SEL                2
#define SPED_SEL_RECTS          3
#define SPED_UNSEL              4
#define SPED_UNSEL_RECTS        5
#define SPED_SHIFT_PTS          6
#define SPED_SHIFT_RECTS        7
#define SPED_SHIFT_SEL          8
#define SPED_TRANSFORM_SEL      9
#define SPED_SET_ORIGIN         10
#define SPED_SHIFT_SUB_ORIGIN   11
#define SPED_TEXT_ED            12
#define SPED_INS_CLIP           13
#define SPED_MAIN_MENU          14
#define SPED_EXIT               15

U0 GrInit3()
{
    DefineListLoad("ST_SPRITE_ED_MENU",

                        "Select/Unselect All\0"
                        " \0"
                        "Select\0"
                        "Select Rects\0"
                        "Unselect\0"
                        "Unselect Rects\0"
                        "Shift Points\0"
                        "Shift Rects\0"
                        "Shift Selected\0"
                        "Transform Selected\0"
                        "Set Origin\0"
                        "Insert Shift SubOrigin\0"
                        "Edit as Text\0"
                        "Insert Clip\0"
                        "Main Menu\0");
}
GrInit3;

I64 PopUpSpriteEd(CSprite **_head, I64 *_cur_elem_num)
{
    U8      *st;
    CTask   *pu_task;
    I64      res;
    CDoc    *doc = DocNew;

    DocPrint(doc,
                "$PURPLE$$TX+CX,\"Sprite Edit Menu\"$\n"
                "$LK+PU+CX,\"Click for Help\",A=\"FI:::/Doc/SpriteEd.DD\"$\n\n"
                "$LTBLUE$$MU-UL,\"Select/Unselect All\",LE=SPED_SEL_UNSEL_ALL$\n"
                "$MU-UL,\"Select Elems\",LE=SPED_SEL$\n"
                "$MU-UL,\"Select Elems with Rects\",LE=SPED_SEL_RECTS$\n"
                "$MU-UL,\"Unsel Elems\",LE=SPED_UNSEL$\n"
                "$MU-UL,\"Unsel Elems with Rects\",LE=SPED_UNSEL_RECTS$\n\n"
                "$MU-UL,\"Shift Points\",LE=SPED_SHIFT_PTS$\n"
                "$MU-UL,\"Shift Points with Rects\",LE=SPED_SHIFT_RECTS$\n"
                "$MU-UL,\"Shift Selected Elems\",LE=SPED_SHIFT_SEL$\n"
                "$MU-UL,\"Transform Selected Elems\",LE=SPED_TRANSFORM_SEL$\n\n"
                "$MU-UL,\"Set Origin\",LE=SPED_SET_ORIGIN$\n"
                "$MU-UL,\"Insert Shift SubOrigin\",LE=SPED_SHIFT_SUB_ORIGIN$\n\n"
                "$MU-UL,\"Edit as Text\",LE=SPED_TEXT_ED$\n"
                "$MU-UL,\"Insert Clip Sprite's\",LE=SPED_INS_CLIP$\n\n"
                "$PURPLE$$MU-UL,\"+] Sprite Main Menu\",LE=SPED_MAIN_MENU$$LTBLUE$\n"
                "$MU-UL,\"Exit  Sprite\",LE=SPED_EXIT$\n"
                "$MU-UL,\"Abort Sprite\",LE=DOCM_CANCEL$");

    st = MStrPrint("SpriteSideBarTask(0x%X,0x%X,0x%X);", Fs, _head, _cur_elem_num);
    PopUp(st, NULL, &pu_task);
    Free(st);
    res = PopUpMenu(doc);
    if (TaskValidate(pu_task))
    {
        *_head = SpriteSideBar2SpriteQueue(DocPut(pu_task), *_head, _cur_elem_num);
        Kill(pu_task);
    }
    DocDel(doc);

    return res;
}

#define SPEDT_SIMPLE_PT     0
#define SPEDT_WIDTH_HEIGHT  1

#define SPEDF_SEL           1

class CEdSprite
{
    CEdSprite   *next, *last;
    CSprite     *g;
    I32          type, num, flags, xx, yy, zz;
    I32         *x, *y, *z, *w, *h;
};

CEdSprite *EdSpriteNew(I64 type, CSprite *tmpg)
{
    CEdSprite *res = CAlloc(sizeof(CEdSprite));

    res->g = tmpg;
    if (tmpg->type & SPF_SEL)
        res->flags |= SPEDF_SEL;
    res->type = type;

    return res;
}

U0 SpritePtQueueNew(U8 *elems, I64 x, I64 y, CEdSprite *head)
{
    I64          i, num = 0;
    I32         *ptr;
    CD3I32      *p;
    CEdSprite   *tmpes;
    CSprite     *tmpg = elems - offset(CSprite.start);

    QueueInit(head);
    while (tmpg->type & SPG_TYPE_MASK)
    {
        switch (tmpg->type & SPG_TYPE_MASK)
        {
            case SPT_ELLIPSE:
            case SPT_POLYGON:
                tmpes = EdSpriteNew(SPEDT_WIDTH_HEIGHT, tmpg);
                tmpes->xx   = x;
                tmpes->yy   = y;
                tmpes->x    = &tmpg->pwha.x1;
                tmpes->y    = &tmpg->pwha.y1;
                tmpes->w    = &tmpg->pwha.width;
                tmpes->h    = &tmpg->pwha.height;
                tmpes->num  = num;
                QueueInsert(tmpes, head->last);
                goto pq_x1_y1;

            case SPT_RECT:
            case SPT_ROTATED_RECT:
            case SPT_LINE:
            case SPT_ARROW:
            case SPT_PLANAR_SYMMETRY:
                tmpes = EdSpriteNew(SPEDT_SIMPLE_PT, tmpg);
                tmpes->xx   = x;
                tmpes->yy   = y;
                tmpes->x    = &tmpg->pp.x2;
                tmpes->y    = &tmpg->pp.y2;
                tmpes->num  = num;
                QueueInsert(tmpes, head->last);
            case SPT_TEXT:
            case SPT_TEXT_BOX:
            case SPT_TEXT_DIAMOND:
            case SPT_PT:
            case SPT_BITMAP:
            case SPT_FLOOD_FILL:
            case SPT_FLOOD_FILL_NOT:
            case SPT_CIRCLE:
pq_x1_y1:
                tmpes = EdSpriteNew(SPEDT_SIMPLE_PT, tmpg);
                tmpes->xx   = x;
                tmpes->yy   = y;
                tmpes->x    = &tmpg->p.x1;
                tmpes->y    = &tmpg->p.y1;
                tmpes->num  = num;
                QueueInsert(tmpes, head->last);
                break;

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

            case SPT_POLYLINE:
                ptr = &tmpg->nu.u;
                for (i = 0; i < tmpg->nu.num; i++)
                {
                    tmpes = EdSpriteNew(SPEDT_SIMPLE_PT, tmpg);
                    tmpes->xx   = x;
                    tmpes->yy   = y;
                    tmpes->x    = &ptr[i << 1];
                    tmpes->y    = &ptr[i << 1 + 1];
                    tmpes->num  = num;
                    QueueInsert(tmpes, head->last);
                }
                break;

            case SPT_POLYPT:
                tmpes = EdSpriteNew(SPEDT_SIMPLE_PT, tmpg);
                tmpes->xx   = x;
                tmpes->yy   = y;
                tmpes->x    = &tmpg->npu.x;
                tmpes->y    = &tmpg->npu.y;
                tmpes->num  = num;
                QueueInsert(tmpes, head->last);
                break;

            case SPT_BSPLINE2:
            case SPT_BSPLINE3:
            case SPT_BSPLINE2_CLOSED:
            case SPT_BSPLINE3_CLOSED:
                p = &tmpg->nu.u;
                for (i = 0; i < tmpg->nu.num; i++)
                {
                    tmpes = EdSpriteNew(SPEDT_SIMPLE_PT, tmpg);
                    tmpes->xx   = x;
                    tmpes->yy   = y;
                    tmpes->x    = &p[i].x;
                    tmpes->y    = &p[i].y;
                    tmpes->z    = &p[i].z;
                    tmpes->num  = num;
                    QueueInsert(tmpes, head->last);
                }
                break;

            case SPT_MESH:
                break;

            case SPT_SHIFTABLE_MESH:
                tmpes = EdSpriteNew(SPEDT_SIMPLE_PT, tmpg);
                tmpes->xx   = x;
                tmpes->yy   = y;
                tmpes->x    = &tmpg->pmu.x;
                tmpes->y    = &tmpg->pmu.y;
                tmpes->z    = &tmpg->pmu.z;
                tmpes->num  = num;
                QueueInsert(tmpes, head->last);
                break;
        }
        tmpg(U8 *) += SpriteElemSize(tmpg);
        num++;
    }
}

U0 SpriteCtrlPtsDraw(CDC *dc, CEdSprite *head)
{
    I64          x, y;
    CEdSprite   *tmpes;

    Refresh;
    DCFill(dc);
    if (Blink(20))
    {
        tmpes = head->next;
        while (tmpes != head)
        {
            switch (tmpes->type)
            {
                case SPEDT_SIMPLE_PT:
                    x = *tmpes->x + tmpes->xx;
                    y = *tmpes->y + tmpes->yy;
                    break;
                case SPEDT_WIDTH_HEIGHT:
                    x = *tmpes->w + *tmpes->x + tmpes->xx;
                    y = *tmpes->h + *tmpes->y + tmpes->yy;
                    break;
            }
            if (tmpes->flags & SPEDF_SEL)
                dc->color = RED;
            else
                dc->color = BLACK;
            GrRect(dc, x - 2, y - 2, 4, 4);
            dc->color = WHITE;
            GrRect(dc, x - 1, y - 1, 2, 2);
            tmpes = tmpes->next;
        }
    }
}

U0 SpriteCtrlPtsMove(CEdSprite *head, I64 dx, I64 dy)
{
    CEdSprite *tmpes;

    tmpes = head->next;
    while (tmpes != head)
    {
        if (tmpes->flags & SPEDF_SEL)
            switch (tmpes->type)
            {
                case SPEDT_SIMPLE_PT:
                    if (tmpes->x)
                        *tmpes->x += dx;
                    if (tmpes->y)
                        *tmpes->y += dy;
                    break;
                case SPEDT_WIDTH_HEIGHT:
                    if (tmpes->w)
                        *tmpes->w += dx;
                    if (tmpes->h)
                        *tmpes->h += dy;
                    break;
            }
        tmpes = tmpes->next;
    }
}

Bool SpriteSelUnselShiftPts(U8 *elems, I64 x, I64 y, I64 *_cur_elem_num, I64 mode)
{
    I64          message_code, arg1, arg2, xx, yy, xx2, yy2, dd, best_dd, cur_elem_num;
    Bool         res = TRUE;
    CDC         *dc = DCAlias;
    CEdSprite    head, *tmpes, *best_es;

    SpritePtQueueNew(elems, x, y, &head);
    cur_elem_num = 0;
    if (head.next != &head)
    {
        while (TRUE)
        {
            SpriteCtrlPtsDraw(dc, &head); //has Refresh
            switch (message_code = MessageScan(&arg1, &arg2, 
                                               1 << MESSAGE_MS_R_UP | 1 << MESSAGE_MS_L_DOWN | 1 << MESSAGE_KEY_DOWN))
            {
                case MESSAGE_MS_L_DOWN:
                    switch (mode)
                    {
                        case SPED_SEL:
                        case SPED_UNSEL:
                        case SPED_SHIFT_PTS:
                            xx = arg1;
                            yy = arg2;
                            best_dd = I64_MAX;
                            tmpes = head.next;
                            while (tmpes != &head)
                            {
                                switch (tmpes->type)
                                {
                                    case SPEDT_SIMPLE_PT:
                                        dd = SqrI64(*tmpes->x + tmpes->xx - xx) + SqrI64(*tmpes->y + tmpes->yy - yy);
                                        break;
                                    case SPEDT_WIDTH_HEIGHT:
                                        dd = SqrI64(*tmpes->x + *tmpes->w + tmpes->xx - xx) +
                                             SqrI64(*tmpes->y + *tmpes->h + tmpes->yy - yy);
                                        break;
                                }
                                if (dd < best_dd)
                                {
                                    best_dd = dd;
                                    best_es = tmpes;
                                }
                                tmpes = tmpes->next;
                            }
                            cur_elem_num = best_es->num;
                            if (mode != SPED_UNSEL)
                            {
                                best_es->flags |= SPEDF_SEL;
                                best_es->g->type |= SPF_SEL;
                            }
                            else
                            {
                                best_es->flags &= ~SPEDF_SEL;
                                best_es->g->type &= ~SPF_SEL;
                            }
                            break;
                        start:
                            xx2 = xx = arg1;
                            yy2 = yy = arg2;
                            while (TRUE)
                            {
                                SpriteCtrlPtsDraw(dc, &head);
                                dc->color = ROPF_DITHER + WHITE << 16 + RED;
                                GrBorder(dc, xx, yy, xx2, yy2);
                                if (message_code = MessageScan(&arg1, &arg2, 1 << MESSAGE_MS_MOVE | 1 << MESSAGE_MS_L_UP))
                                {
                                    if (message_code == MESSAGE_MS_MOVE)
                                    {
                                        xx2 = arg1;
                                        yy2 = arg2;
                                    }
                                    else
                                        break;
                                }
                            }
                            if (xx2 < xx)
                                SwapI64(&xx, &xx2);
                            if (yy2 < yy)
                                SwapI64(&yy, &yy2);
                            tmpes = head.next;
                            while (tmpes != &head)
                            {
                                switch (tmpes->type)
                                {
                                    case SPEDT_SIMPLE_PT:
                                        if (xx <= *tmpes->x + tmpes->xx <= xx2 && yy <= *tmpes->y + tmpes->yy <= yy2)
                                        {
                                            if (mode != SPED_UNSEL_RECTS)
                                            {
                                                tmpes->flags |= SPEDF_SEL;
                                                tmpes->g->type |= SPF_SEL;
                                            }
                                            else
                                            {
                                                tmpes->flags &= ~SPEDF_SEL;
                                                tmpes->g->type &= ~SPF_SEL;
                                            }
                                        }
                                        break;
                                    case SPEDT_WIDTH_HEIGHT:
                                        if (xx <= *tmpes->x + *tmpes->w + tmpes->xx <= xx2 &&
                                            yy <= *tmpes->y + *tmpes->h + tmpes->yy <= yy2)
                                        {
                                            if (mode != SPED_UNSEL_RECTS)
                                            {
                                                tmpes->flags |= SPEDF_SEL;
                                                tmpes->g->type |= SPF_SEL;
                                            }
                                            else
                                            {
                                                tmpes->flags &= ~SPEDF_SEL;
                                                tmpes->g->type &= ~SPF_SEL;
                                            }
                                        }
                                        break;
                                }
                                tmpes = tmpes->next;
                            }
                            case SPED_SEL_RECTS:
                            case SPED_UNSEL_RECTS:
                                break;
                            case SPED_SHIFT_RECTS:
                                do
                                {
                                    SpriteCtrlPtsDraw(dc, &head);
                                    message_code = MessageScan(&arg1, &arg2, 1 << MESSAGE_KEY_DOWN | 1 << MESSAGE_MS_L_DOWN);
                                    if (message_code == MESSAGE_KEY_DOWN)
                                        goto gs_key;
                                }
                                while (message_code != MESSAGE_MS_L_DOWN);

                                xx = arg1;
                                yy = arg2;
                                break;
                        end:
                    }
                    switch (mode)
                    {
                        case SPED_SHIFT_PTS:
                        case SPED_SHIFT_RECTS:
                            do
                            {
                                SpriteCtrlPtsDraw(dc, &head);
                                if (message_code = MessageScan(&arg1, &arg2, 1 << MESSAGE_MS_MOVE | 1 << MESSAGE_MS_L_UP))
                                {
                                    SpriteCtrlPtsMove(&head, arg1 - xx, arg2 - yy);
                                    xx = arg1;
                                    yy = arg2;
                                }
                            }
                            while (message_code != MESSAGE_MS_L_UP);

                            tmpes = head.next;
                            while (tmpes != &head)
                            {
                                tmpes->flags &= ~SPEDF_SEL;
                                tmpes->g->type &= ~SPF_SEL;
                                tmpes = tmpes->next;
                            }
                            break;
                    }
                    break;
                case MESSAGE_KEY_DOWN:
gs_key:
                    switch (arg1.u8[0])
                    {
                        case CH_SHIFT_ESC:
                            res = FALSE;
                        case CH_ESC:
                            MessageGet(&arg1, &arg2, 1 << MESSAGE_KEY_UP);
                            goto gs_done;

                        case 'p':
                        case 'P':
                            mode &= ~1;
                            break;

                        case 'r':
                        case 'R':
                            mode |= 1;
                            break;
                    }
                    break;
                case MESSAGE_MS_R_UP:
                    goto gs_done;
            }
        }
gs_done:
        QueueDel(&head, TRUE);
    }
    DCFill(dc);
    DCDel(dc);
    if (_cur_elem_num && res)
        *_cur_elem_num = cur_elem_num;

    return res;
}

I64 SpriteEd(CDoc *doc, CDocEntry *doc_ce, I64 x, I64 y, CSprite **_head, I64 *_cur_elem_num)
{
    CDocEntry   *doc_e2;
    CDocBin     *tmpb;
    Bool         unlock;
    I64          i, r[16], message_code, arg1, arg2, xx, yy, old_de_flags;
    CSprite     *head2, *next, *last, *tmpg, *insert_pt;

    old_de_flags = doc_ce->de_flags;
    tmpb = doc_ce->bin_data;
    DocUnlock(doc);
    SpriteQueueSelAll(*_head, FALSE);
    do
    {
        if (winmgr.fps < 10)
            doc_ce->de_flags |= DOCEF_DONT_DRAW;
        StrCopy(Fs->task_title, "Sprite Edit Menu");
        i=PopUpSpriteEd(_head, _cur_elem_num);
        SpriteEdUpdate(doc, doc_ce, *_head);
        if (0 <= i < SPED_EXIT)
        {
            StrCopy(Fs->task_title, DefineSub(i, "ST_SPRITE_ED_MENU"));
            switch (i)
            {
                case SPED_SEL_UNSEL_ALL:
                    if (!SpriteQueueSelCount(*_head))
                        SpriteQueueSelAll(*_head);
                    else
                        SpriteQueueSelAll(*_head, FALSE);
                    break;

                case SPED_SET_ORIGIN:
                    SpriteQueueSelAll(*_head);
                    doc_ce->de_flags = old_de_flags;
                    MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_L_UP);
                    SpriteSetOrigin(*_head, x - arg1, y - arg2, 0);
                    SpriteEdUpdate(doc, doc_ce, *_head);
                    SpriteQueueSelAll(*_head, FALSE);
                    break;

                case SPED_SHIFT_SEL:
                    if (!SpriteQueueSelCount(*_head))
                        SpriteQueueSelAll(*_head);
                    doc_ce->de_flags = old_de_flags;
                    MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_L_DOWN);
                    xx = arg1;
                    yy = arg2;
                    do
                    {
                        message_code = MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_L_UP + 1 << MESSAGE_MS_MOVE);
                        SpriteSetOrigin(*_head, arg1 - xx, arg2 - yy, 0);
                        xx = arg1;
                        yy = arg2;
                        SpriteEdUpdate(doc, doc_ce, *_head);
                    }
                    while (message_code != MESSAGE_MS_L_UP);

                    if (!SpriteQueueSelCount(*_head, FALSE))
                        SpriteQueueSelAll(*_head, FALSE);
                    break;

                case SPED_SEL:
                case SPED_SEL_RECTS:
                case SPED_UNSEL:
                case SPED_UNSEL_RECTS:
                case SPED_SHIFT_PTS:
                case SPED_SHIFT_RECTS:
                    RegOneTimePopUp(ARf_CSPRITE_PTS_RECTANGLES, 
                                "You can switch between points\n"
                                "and rectangles with '$GREEN$p$FG$' and '$GREEN$r$FG$'.\n"
                                "Press '$GREEN$r$FG$' after one rectangle\n"
                                "to OR another rectangle.\n");
                    doc_ce->de_flags = old_de_flags;
                    if (SpriteSelUnselShiftPts(tmpb->data, x, y, _cur_elem_num, i))
                    {
                        QueueDel(*_head);
                        Free(*_head);
                        *_head = Sprite2SpriteQueue(tmpb->data);
                    }
                    else
                        SpriteEdUpdate(doc, doc_ce, *_head);
                    break;

                case SPED_TRANSFORM_SEL:
                    if (!SpriteQueueSelCount(*_head))
                        SpriteQueueSelAll(*_head);
                    if (PopUpTransform(r))
                    {
                        SpriteTransformQueue(*_head, r);
                        SpriteEdUpdate(doc, doc_ce, *_head);
                    }
                    if (!SpriteQueueSelCount(*_head, FALSE))
                        SpriteQueueSelAll(*_head, FALSE);
                    break;

                case SPED_SHIFT_SUB_ORIGIN:
                    doc_ce->de_flags = old_de_flags;
                    insert_pt = SpriteSetSettings(, *_head, *_cur_elem_num);
                    tmpg = CAlloc(SpriteElemQueuedBaseSize(SPT_SHIFT));
                    tmpg->type = SPT_SHIFT;
                    tmpg->p.x1 = 0;
                    tmpg->p.y1 = 0;
                    QueueInsert(tmpg, insert_pt->last);
                    MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_L_DOWN);
                    xx = arg1;
                    yy = arg2;
                    do
                    {
                        message_code=MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_L_UP + 1 << MESSAGE_MS_MOVE);
                        tmpg->p.x1 = arg1 - xx;
                        tmpg->p.y1 = arg2 - yy;
                        SpriteEdUpdate(doc, doc_ce, *_head);
                    }
                    while (message_code != MESSAGE_MS_L_UP);

                    *_cur_elem_num += 1;
                    break;

                case SPED_INS_CLIP:
                    RegOneTimePopUp(ARf_CSPRITE_INS_CLIP, 
                                "You will probably want to shift around\n"
                                "the location of element groups.  Use\n"
                                "'Insert shift sub-origin' after picking the\n"
                                "element to insert before.  Or,\n"
                                "use 'shift points'.\n");
                    insert_pt = SpriteSetSettings(, *_head, *_cur_elem_num);
                    unlock = DocLock(sys_clip_doc);
                    doc_e2 = sys_clip_doc->head.next;
                    while (doc_e2 != sys_clip_doc)
                    {
                        if (doc_e2->type_u8 == DOCT_SPRITE)
                        {
                            head2 = Sprite2SpriteQueue(doc_e2->bin_data->data);
                            if (head2->next != head2)
                            {
                                tmpg = head2->next;
                                while (tmpg != head2)
                                {
                                    *_cur_elem_num += 1;
                                    tmpg = tmpg->next;
                                }
                                next = head2->next;
                                last = head2->last;
                                insert_pt->last->next = next;
                                next->last = insert_pt->last;
                                insert_pt->last = last;
                                last->next = insert_pt;
                            }
                            Free(head2);
                        }
                        doc_e2 = doc_e2->next;
                    }
                    if (unlock)
                        DocUnlock(sys_clip_doc);
                    SpriteEdUpdate(doc, doc_ce, *_head);
                    break;

                case SPED_TEXT_ED:
                    if (SpriteEdText(_head, _cur_elem_num))
                        SpriteEdUpdate(doc, doc_ce, *_head);
                    break;
            }
        }
    }
    while (i != DOCM_CANCEL && i != SPED_EXIT && i != SPED_MAIN_MENU);

    doc_ce->de_flags = old_de_flags;

    switch (i)
    {
        case DOCM_CANCEL:
            return SPE_ABORT;
        case SPED_EXIT:
            return SPE_EXIT;
        case SPED_MAIN_MENU:
            return SPE_CONT;
    }
}

#help_index "Graphics/Sprite;Sprites;Graphics/Math/3D Transformation"
public U8 *SpriteTransform(U8 *elems, I64 *r)
{//Rotate Sprite using 4x4 matrix. Uses fixed-point.
    U8      *res;
    CSprite *head = Sprite2SpriteQueue(elems);

    SpriteQueueSelAll(head);
    SpriteTransformQueue(head, r);
    res = SpriteQueue2Sprite(head);
    QueueDel(head);
    Free(head);

    return res;
}