#help_index "Graphics"

public Bool GrClamp(CDC *dc=gr.dc, I64 *left, I64 *top, I64 *right, I64 *bottom, I64 width=0, I64 height=0)
{//Returns screen, not window coordinates.
    CTask *win_task;

    *left   = 0;
    *top    = 0;
    *right  = dc->width  - 1;
    *bottom = dc->height - 1;
    if (dc->flags & DCF_SCREEN_BITMAP)
    {
        win_task = dc->win_task;
        if (GR_WIDTH - 1 < *right)
            *right = GR_WIDTH - 1;

        if (GR_HEIGHT - 1 < *bottom)
            *bottom = GR_HEIGHT - 1;

        if (win_task->pix_left > *left)
            *left = win_task->pix_left;

        if (win_task->pix_top > *top)
            *top = win_task->pix_top;

        if (win_task->pix_right < *right)
            *right = win_task->pix_right;

        if (win_task->pix_bottom < *bottom)
            *bottom = win_task->pix_bottom;
    }
    *left   -= width;
    *right  += width;
    *top    -= height;
    *bottom += height;

    return *left <= *right && *top <= *bottom;
}

Bool DCClipLine(CDC *dc=gr.dc, I64 *x1, I64 *y1, I64 *x2, I64 *y2, I64 width=0, I64 height=0)
{//Also converts window to screen coordinates
    I64      left, top, right, bottom;
    CTask   *win_task;

    if (GrClamp(dc, &left, &top, &right, &bottom, width, height))
    {
        if (dc->flags & DCF_SCREEN_BITMAP)
        {
            win_task = dc->win_task;
            *x1 += win_task->pix_left + win_task->scroll_x;
            *y1 += win_task->pix_top  + win_task->scroll_y;
            *x2 += win_task->pix_left + win_task->scroll_x;
            *y2 += win_task->pix_top  + win_task->scroll_y;
        }
        return ClipLine(x1, y1, x2, y2, left, top, right, bottom);
    }
    else
        return FALSE;
}

public Bool GrPlot(CDC *dc=gr.dc, I64 x, I64 y)
{//2D. Clipping but No transformation or thick.
    I32             *db = dc->depth_buf;
    CTask           *win_task;
    CColorROPU32     old_color;

    dc->depth_buf = NULL;
    if (dc->brush)
    {
        old_color = dc->color;
        if (dc->color.c0.rop != ROPB_COLLISION)
            dc->color.c0.rop = ROPB_MONO;
        GrBlot(dc, x, y, dc->brush);
        dc->color = old_color;
    }
    else if (dc->flags & DCF_SCREEN_BITMAP)
    {
        win_task = dc->win_task;
        x += win_task->pix_left + win_task->scroll_x;
        y += win_task->pix_top  + win_task->scroll_y;
        if (    win_task->pix_left <= x <= win_task->pix_right  &&
                win_task->pix_top  <= y <= win_task->pix_bottom &&
                0 <= x < dc->width && 0 <= y < dc->height       &&
                (win_task->next_task == sys_winmgr_task || dc->flags & DCF_ON_TOP || !IsPixCovered0(win_task, x, y)))
            GrPlot0(dc, x, y);
    }
    else
        if (0 <= x < dc->width && 0 <= y < dc->height)
            GrPlot0(dc, x, y);
    dc->depth_buf = db;

    return TRUE;
}

Bool GrPlot1(CDC *dc=gr.dc, I64 x, I64 y)
{//Clipping but No transformation or thick, called with db_z set
    CTask           *win_task;
    CColorROPU32     old_color;

    if (dc->brush)
    {
        old_color = dc->color;
        if (dc->color.c0.rop != ROPB_COLLISION)
            dc->color.c0.rop = ROPB_MONO;
        if (dc->depth_buf)
            GrBlot3(dc, x, y, dc->db_z, dc->brush);
        else
            GrBlot(dc, x, y, dc->brush);
        dc->color = old_color;
    }
    else if (dc->flags & DCF_SCREEN_BITMAP)
    {
        win_task = dc->win_task;
        x += win_task->pix_left + win_task->scroll_x;
        y += win_task->pix_top  + win_task->scroll_y;
        if (    win_task->pix_left <= x <= win_task->pix_right  &&
                win_task->pix_top  <= y <= win_task->pix_bottom &&
                0 <= x<dc->width && 0 <= y<dc->height           &&
                (win_task->next_task == sys_winmgr_task || dc->flags & DCF_ON_TOP || !IsPixCovered0(win_task, x, y)))
            GrPlot0(dc, x, y);
    }
    else
        if (0 <= x < dc->width && 0 <= y < dc->height)
            GrPlot0(dc, x, y);

    return TRUE;
}

public I64 GrPeek(CDC *dc=gr.dc, I64 x, I64 y)
{//2D. Clipping but no transformation.
//Returns pix color or -1 if off-screen or covered.
    CTask *win_task;

    if (dc->flags & DCF_SCREEN_BITMAP)
    {
        win_task = dc->win_task;
        x += win_task->pix_left + win_task->scroll_x;
        y += win_task->pix_top  + win_task->scroll_y;
        if (    !(win_task->pix_left <= x <= win_task->pix_right)   ||
                !(win_task->pix_top  <= y <= win_task->pix_bottom)  ||
                !(0 <= x < dc->width) || !(0 <= y < dc->height)     ||
                win_task->next_task != sys_winmgr_task && !(dc->flags & DCF_ON_TOP) && IsPixCovered0(win_task, x, y))
            return -1;
    }
    else
        if (!(0 <= x<dc->width) || !(0 <= y<dc->height))
            return -1;

    return GrPeek0(dc, x, y);
}

/*

This is an easier to understand
version of the nonrecursive routine below.
I64 GrFloodFillRay(CDC *dc, I64 x, I64 y, I64 z, I32 *db)
{
    I64 res, j, x1, ray_len, ray_len2;

    if (UnusedStack < 0x80)
        Panic("Stack Overflow", Fs);

    res = ray_len = GrRayLen(dc, &x, y, z, db);
    y--;
    j = ray_len;
    x1 = x;
    while (j > 0)
    {
        if (ray_len2 = GrRayLenMinus(dc, x1, y))
            res += GrFloodFillRay(dc, x1, y, z, db);
        j  -= ray_len2 + 1;
        x1 -= ray_len2 + 1;
    }
    y += 2;
    j = ray_len;
    x1 = x;
    while (j > 0)
    {
        if (ray_len2 = GrRayLenMinus(dc, x1, y))
            res += GrFloodFillRay(dc, x1, y, z, db);
        j  -= ray_len2 + 1;
        x1 -= ray_len2 + 1;
    }

    return res;
}
*/

class CFFRay
{
    I64 state, x, y, j, x1, ray_len, ray_len2;
};

I64 GrFloodFillRay(CDC *dc, I64 x, I64 y, I64 z, I32 *db)
{//See the above commented-out routine for an easier to understand version.
//Returns count of pixs changed
    I64      res = 0;
//We don't dynamically calculate the size to avoid fragmentation of memory.
    CFFRay  *f_dc = MAlloc(sizeof(CFFRay) * 0x80000), *f = f_dc;

    f->x     = x;
    f->y     = y;
    f->state = 0;
    do
    {
        switch [f->state]
        {
            case 0:
                f->state++;
                res += f->ray_len = GrRayLen(dc, &f->x, f->y, z, db);
                f->y--;
                f->j  = f->ray_len;
                f->x1 = f->x;
                break;

            case 1:
                if (f->j > 0)
                {
                    f->state++;
                    if (f->ray_len2 = GrRayLenMinus(dc, f->x1, f->y))
                    {
                        f[1].x      = f->x1;
                        f[1].y      = f->y;
                        f[1].state  = 0;
                        f++;
                    }
                }
                else
                    f->state += 2;
                break;

            case 2:
                f->state--;
                f->j  -= f->ray_len2 + 1;
                f->x1 -= f->ray_len2 + 1;
                break;

            case 3:
                f->state++;
                f->y += 2;
                f->j  = f->ray_len;
                f->x1 = f->x;
                break;

            case 4:
                if (f->j > 0)
                {
                    f->state++;
                    if (f->ray_len2 = GrRayLenMinus(dc, f->x1, f->y))
                    {
                        f[1].x      = f->x1;
                        f[1].y      = f->y;
                        f[1].state  = 0;
                        f++;
                    }
                }
                else
                    f->state += 2;
                break;

            case 5:
                f->state--;
                f->j  -= f->ray_len2 + 1;
                f->x1 -= f->ray_len2 + 1;
                break;

            case 6:
                f--;
                break;
        }
    }
    while (f >= f_dc);

    Free(f_dc);

    return res;
}

public I64 GrFloodFill(CDC *dc=gr.dc, I64 x, I64 y, Bool not_color=FALSE, I64 z=0, I32 *db=NULL)
{//2D. Ignore z and db.
//not_color=TRUE means fill up to everything which is not the current color.
    //not_color=FALSE means fill all parts equ to the color under the point.
    //Returns count of pixs changed
    I64              res = 0, j, old_flags = dc->flags;
    CColorROPU32     old_color2 = dc->color2;
    CDC             *old_brush;

    if (dc->flags & DCF_DONT_DRAW) //TODO
        return 0;
    old_brush = dc->brush;
    dc->brush = NULL;
    if ((j = GrPeek(dc, x, y)) >= 0)
    {
        if (not_color)
        {
            dc->color2 = dc->color.c0.color;
            dc->flags |= DCF_FILL_NOT_COLOR;
        }
        else
        {
            dc->color2 = j;
            if (dc->color.c1.rop & ROPBF_DITHER)
            {
                if (dc->color2.c0.color == dc->color.c0.color && dc->color.c0.color == dc->color.c1.color)
                    goto ff_done;
            }
            else if (dc->color2.c0.color == dc->color.c0.color)
                goto ff_done;
            dc->flags &= ~DCF_FILL_NOT_COLOR;
        }
        if (not_color && j != dc->color2 || !not_color)
            res = GrFloodFillRay(dc, x, y, z, db);
    }
ff_done:
    dc->brush   = old_brush;
    dc->flags   = old_flags;
    dc->color2  = old_color2;

    return res;
}

I64 GrFillSemiCircle(CDC *dc=gr.dc, I64 cx, I64 cy, I64 z=0, I64 diameter, I64 n)
{//2D. Clipping but not transformation.
    I64 res = 0, i, k, r = diameter >> 1, rr;

    if (diameter >= 1)
        switch (n)
        {
            case 0:
                if (diameter < GR_PEN_BRUSHES_NUM)
                    for (i = 0; i < r; i++)
                        res += GrHLine(dc, gr.circle_lo[diameter][i] + cx, gr.circle_hi[diameter][i] + cx, cy + i - r, z, z);
                else
                {
                    k = diameter + 1;
                    rr = SqrI64((k + 1) >> 1);
                    for (i = 0; i < r; i++)
                        res += GrHLine(dc, -Sqrt(rr - SqrI64(r - i)) + cx, Sqrt(rr - SqrI64(r - i)) + cx, cy + i - r, z, z);
                }
                break;

            case 1:
                if (diameter < GR_PEN_BRUSHES_NUM)
                    for (i = r + 1 ; i < diameter; i++)
                        res += GrHLine(dc, gr.circle_lo[diameter][i] + cx, gr.circle_hi[diameter][i] + cx, cy + i - r, z, z);
                else
                {
                    k = diameter + 1;
                    rr = SqrI64((k + 1) >> 1);
                    for (i = r + 1; i < k; i++)
                        res += GrHLine(dc, -Sqrt(rr - SqrI64(i - r)) + cx, Sqrt(rr - SqrI64(i - r)) + cx, cy + i - r, z, z);
                }
                break;

            case 2:
                if (diameter < GR_PEN_BRUSHES_NUM)
                    for (i = 0; i < r; i++)
                        res += GrVLine(dc, cx + i - r, gr.circle_lo[diameter][i] + cy, gr.circle_hi[diameter][i] + cy, z, z);
                else
                {
                    k = diameter + 1;
                    rr = SqrI64((k + 1) >> 1);
                    for (i = 0; i < r; i++)
                        res += GrVLine(dc, cx + i - r, -Sqrt(rr - SqrI64(r - i)) + cy, Sqrt(rr - SqrI64(r - i)) + cy, z, z);
                }
                break;

            case 3:
                if (diameter < GR_PEN_BRUSHES_NUM)
                    for (i = r + 1; i < diameter; i++)
                        res += GrVLine(dc, cx + i - r, gr.circle_lo[diameter][i] + cy, gr.circle_hi[diameter][i] + cy, z, z);
                else
                {
                    k = diameter + 1;
                    rr = SqrI64((k + 1) >> 1);
                    for (i = r + 1; i < k; i++)
                        res += GrVLine(dc, cx + i - r, -Sqrt(rr - SqrI64(i - r)) + cy, Sqrt(rr - SqrI64(i - r)) + cy, z, z);
                }
                break;

            case 4:
                if (diameter < GR_PEN_BRUSHES_NUM)
                    for (i = 0; i < r; i++)
                        res += GrHLine(dc, gr.circle_lo[diameter][i] + cx, gr.circle_hi[diameter][i] + cx, cy + i - r, z, z);
                else
                {
                    k = diameter + 1;
                    rr = SqrI64((k + 1) >> 1);
                    for (i = 0; i < r; i++)
                        res += GrHLine(dc, -Sqrt(rr - SqrI64(r - i)) + cx, Sqrt(rr - SqrI64(r - i)) + cx, cy + i - r, z, z);
                }
                break;

            case 5:
                if (diameter < GR_PEN_BRUSHES_NUM)
                    for (i = r + 1; i < diameter; i++)
                        res += GrHLine(dc, gr.circle_lo[diameter][i] + cx, gr.circle_hi[diameter][i] + cx, cy + i - r, z, z);
                else
                {
                    k = diameter + 1;
                    rr = SqrI64((k + 1) >> 1);
                    for (i = r + 1; i < k; i++)
                        res += GrHLine(dc, -Sqrt(rr - SqrI64(i - r)) + cx, Sqrt(rr - SqrI64(i - r)) + cx, cy + i - r, z, z);
                }
                break;

            case 6:
                if (diameter < GR_PEN_BRUSHES_NUM)
                    for (i = 0; i < r; i++)
                        res += GrVLine(dc, cx + i - r, gr.circle_lo[diameter][i] + cy, gr.circle_hi[diameter][i] + cy, z, z);
                else
                {
                    k = diameter + 1;
                    rr = SqrI64((k + 1) >> 1);
                    for (i = 0; i < r; i++)
                        res += GrVLine(dc, cx + i - r, -Sqrt(rr - SqrI64(r - i)) + cy, Sqrt(rr - SqrI64(r - i)) + cy, z, z);
                }
                break;

            case 7:
                if (diameter < GR_PEN_BRUSHES_NUM)
                    for (i = r + 1; i < diameter; i++)
                        res += GrVLine(dc, cx + i - r, gr.circle_lo[diameter][i] + cy, gr.circle_hi[diameter][i] + cy, z, z);
                else
                {
                    k = diameter + 1;
                    rr = SqrI64((k + 1) >> 1);
                    for (i = r + 1; i < k; i++)
                        res += GrVLine(dc, cx + i - r, -Sqrt(rr - SqrI64(i - r)) + cy, Sqrt(rr - SqrI64(i - r)) + cy, z, z);
                }
                break;
        }

    return res;
}

public I64 GrFillCircle(CDC *dc=gr.dc, I64 cx, I64 cy, I64 z=0, I64 diameter)
{//2D. Clipping but not transformation.
    I64 res = 0, i, k, r = diameter >> 1, rr;

    if (diameter >= 1)
    {
        if (diameter < GR_PEN_BRUSHES_NUM)
            for (i = 0; i < diameter; i++)
                res += GrHLine(dc, gr.circle_lo[diameter][i] + cx, gr.circle_hi[diameter][i] + cx, cy + i - r, z, z);
        else
        {
            k = diameter + 1;
            rr = SqrI64((k + 1) >> 1);
            for (i = 0; i <= r; i++)
                res += GrHLine(dc, -Sqrt(rr - SqrI64(r - i)) + cx, Sqrt(rr - SqrI64(r - i)) + cx, cy + i - r, z, z);
            for (; i < k; i++)
                res += GrHLine(dc, -Sqrt(rr - SqrI64(i - r)) + cx, Sqrt(rr - SqrI64(i - r)) + cx, cy + i - r, z, z);
        }
    }

    return res;
}

public Bool GrPlot3B(CDC *dc=gr.dc, I64 x, I64 y, I64 z)
{//3D. Clipping and transformation but no thick.
    I64  _x, _y, _z;
    Bool was_transform = FALSE, was_symmetry = FALSE;

    if (dc->flags & DCF_TRANSFORMATION)
    {
        (*dc->transform)(dc, &x, &y, &z);
        dc->flags &= ~DCF_TRANSFORMATION;
        was_transform = TRUE;
    }
    if (dc->flags & DCF_SYMMETRY)
    {
        _x = x;
        _y = y;
        _z = z;
        DCReflect(dc, &_x, &_y, &_z);
        dc->flags &= ~DCF_SYMMETRY;
        dc->db_z = _z;
        GrPlot1(dc, _x, _y);
        was_symmetry = TRUE;
        if (dc->flags & DCF_JUST_MIRROR)
            goto gr_done;
    }
    dc->db_z = z;
    GrPlot1(dc, x, y);
gr_done:
    if (was_transform)
        dc->flags |= DCF_TRANSFORMATION;
    if (was_symmetry)
        dc->flags |= DCF_SYMMETRY;

    return TRUE;
}

public Bool GrPlot3(CDC *dc=gr.dc, I64 x, I64 y, I64 z)
{//3D. Clipping and transformation and thick.
    I64              _x, _y, _z, w, dist;
    CColorROPU32     old_color = dc->color;
    Bool             record, was_transform = FALSE, was_symmetry = FALSE;
    CTask           *win_task;

    if (dc->flags & DCF_TRANSFORMATION)
    {
        (*dc->transform)(dc, &x, &y, &z);
        dc->flags &= ~DCF_TRANSFORMATION;
        was_transform = TRUE;
    }
    if (dc->flags & DCF_SYMMETRY)
    {
        _x = x;
        _y = y;
        _z = z;
        DCReflect(dc, &_x, &_y, &_z);
        dc->flags &= ~DCF_SYMMETRY;
        GrPlot3(dc, _x, _y, _z);
        was_symmetry = TRUE;
        if (dc->flags & DCF_JUST_MIRROR)
            goto gr_done;
    }
    w = dc->thick >> 1;
    dc->db_z = z;
    if (dc->brush || w <= 0)
        GrPlot1(dc, x, y);
    else if (dc->thick < GR_PEN_BRUSHES_NUM)
    {
        if (dc->color.c0.rop != ROPB_COLLISION)
            dc->color.c0.rop = ROPB_MONO;
        if (dc->depth_buf)
        {
            if (dc->color.c1.rop & ROPBF_DITHER)
            {
                dc->color.c1.rop = dc->color.c0.rop;
                if (((x - w) ^ (y - w)) & 1)
                {
                    record = GrBlot3(dc, x - w, y - w, z, gr.odd_pen_brushes[dc->thick]);
                    dc->color.c0 = dc->color.c1;
                    record = GrBlot3(dc, x - w, y - w, z, gr.even_pen_brushes[dc->thick]);
                }
                else
                {
                    record = GrBlot3(dc, x - w, y - w, z, gr.even_pen_brushes[dc->thick]);
                    dc->color.c0 = dc->color.c1;
                    record = GrBlot3(dc, x - w, y - w, z, gr.odd_pen_brushes[dc->thick]);
                }
            }
            else
            {
                if (dc->color.c0.rop == ROPB_COLLISION)
                {
                    if (dc->color.c0.color != dc->bkcolor.c0.color && dc->color.c0.color != TRANSPARENT)
                        record=GrBlot3(dc, x - w, y - w, z, gr.collision_pen_brushes[dc->thick]);
                    else
                        record = FALSE;
                }
                else
                    record = GrBlot3(dc, x - w, y - w, z, gr.pen_brushes[dc->thick]);
            }
        }
        else
        {
            if (dc->color.c1.rop & ROPBF_DITHER)
            {
                dc->color.c1.rop = dc->color.c0.rop;
                if (((x - w) ^ (y - w)) & 1)
                {
                    record = GrBlot(dc, x - w, y - w, gr.odd_pen_brushes[dc->thick]);
                    dc->color.c0 = dc->color.c1;
                    record = GrBlot(dc, x - w, y - w, gr.even_pen_brushes[dc->thick]);
                }
                else
                {
                    record = GrBlot(dc, x - w, y - w, gr.even_pen_brushes[dc->thick]);
                    dc->color.c0 = dc->color.c1;
                    record = GrBlot(dc, x - w, y - w, gr.odd_pen_brushes[dc->thick]);
                }
            }
            else
            {
                if (dc->color.c0.rop == ROPB_COLLISION)
                {
                    if (dc->color.c0.color != dc->bkcolor.c0.color && dc->color.c0.color != TRANSPARENT)
                        record = GrBlot(dc, x - w, y - w, gr.collision_pen_brushes[dc->thick]);
                    else
                        record = FALSE;
                }
                else
                    record = GrBlot(dc, x - w, y - w, gr.pen_brushes[dc->thick]);
            }
        }
        if (record)
        {
            if (dc->flags & DCF_SCREEN_BITMAP)
            {
                win_task = dc->win_task;
                x += win_task->pix_left + win_task->scroll_x;
                y += win_task->pix_top  + win_task->scroll_y;
            }
            if (dc->flags & DCF_LOCATE_NEAREST)
            {
                dist = DistSqrI64(x, y, dc->cur_x, dc->cur_y);
                if (dist <= dc->nearest_dist)
                    dc->nearest_dist = dist;
            }
            if (dc->flags & DCF_RECORD_EXTENTS)
            {
                if (x - w < dc->min_x)
                    dc->min_x = x - w;
                if (y - w < dc->min_y)
                    dc->min_y = y - w;
                if (dc->thick & 1)
                {
                    if (x + w > dc->max_x)
                        dc->max_x = x + w;
                    if (y + w > dc->max_y)
                        dc->max_y = y + w;
                }
                else
                {
                    if (x + w - 1 > dc->max_x)
                        dc->max_x = x + w - 1;
                    if (y + w - 1 > dc->max_y)
                        dc->max_y = y + w - 1;
                }
            }
        }
    }
    else
        GrFillCircle(dc, x, y, dc->db_z, dc->thick);
gr_done:
    dc->color = old_color;
    if (was_transform)
        dc->flags |= DCF_TRANSFORMATION;
    if (was_symmetry)
        dc->flags |= DCF_SYMMETRY;

    return TRUE;
}

Bool GrLinePlot0(CDC *dc, I64 x, I64 y, I64 z)
{//This is a callback.
    CTask *win_task = dc->win_task;

    if (!(dc->flags & DCF_SCREEN_BITMAP)        ||
        win_task->next_task == sys_winmgr_task  ||
        dc->flags & DCF_ON_TOP                  ||
        !IsPixCovered0(win_task, x, y))
    {
        dc->db_z = z;
        GrPlot0(dc, x, y);
    }

    return TRUE;
}

Bool GrLinePlot(CDC *dc, I64 x, I64 y, I64 z)
{//This is a callback.
    dc->db_z = z;
    GrPlot1(dc, x, y);

    return TRUE;
}

public Bool GrLine(CDC *dc=gr.dc, I64 x1, I64 y1, I64 x2, I64 y2, I64 step=1, I64 start=0)
{//2D. Clipping but not transformation.
    Bool res = FALSE;
    I32 *db = dc->depth_buf;

    dc->depth_buf = NULL;
    if (step == 1 && !start && !dc->brush && !dc->depth_buf)
    {
        if (DCClipLine(dc, &x1, &y1, &x2, &y2))
            res = Line(dc, x1, y1, 0, x2, y2, 0, &GrLinePlot0, step, start);
    }
    else
        res = Line(dc, x1, y1, 0, x2, y2, 0, &GrLinePlot, step, start);
    dc->depth_buf = db;

    return res;
}

public Bool GrCircle(CDC *dc=gr.dc, I64 cx, I64 cy, I64 radius, I64 step=1, F64 start_radians=0, F64 len_radians=2*pi)
{//2D. Clipping but not transformation.
    Bool res;
    I32 *db = dc->depth_buf;

    dc->depth_buf = NULL;
    res = Circle(dc, cx, cy, 0, radius, &GrLinePlot, step, start_radians, len_radians);
    dc->depth_buf = db;

    return res;
}

public Bool GrEllipse(CDC *dc=gr.dc, I64 cx, I64 cy, I64 x_radius, I64 y_radius, 
                      F64 rot_angle=0, I64 step=1, F64 start_radians=0, F64 len_radians=2*pi)
{//2D. Clipping but not transformation.
    Bool res;
    I32 *db = dc->depth_buf;

    dc->depth_buf = NULL;
    res = Ellipse(dc, cx, cy, 0, x_radius, y_radius, &GrLinePlot, rot_angle, step, start_radians, len_radians);
    dc->depth_buf = db;

    return res;
}

public Bool GrRegPoly(CDC *dc=gr.dc, I64 cx, I64 cy, I64 x_radius, I64 y_radius, I64 sides, F64 rot_angle=0, 
                      I64 step=1, F64 start_radians=0, F64 len_radians=2*pi)
{//2D. Clipping but no transform or thick.
    Bool res;
    I32 *db = dc->depth_buf;

    dc->depth_buf = NULL;
    res = RegPoly(dc, cx, cy, 0, x_radius, y_radius, sides, &GrLinePlot, rot_angle, step, start_radians, len_radians);
    dc->depth_buf = db;

    return res;
}

public Bool Gr2Bezier(CDC *dc=gr.dc, CD3I32 *ctrl)
{//2nd order. Clipping but no transform or thick.
    return Bezier2(dc, ctrl, &GrLinePlot);
}

public Bool Gr3Bezier(CDC *dc=gr.dc, CD3I32 *ctrl)
{//3rd order. Clipping but no transform or thick.
    return Bezier3(dc, ctrl, &GrLinePlot);
}

public Bool Gr2BSpline(CDC *dc=gr.dc, CD3I32 *ctrl, I64 count, Bool closed=FALSE)
{//2nd order. Clipping but no transform or thick.
    return BSpline2(dc, ctrl, count, &GrLinePlot, closed);
}

public Bool Gr3BSpline(CDC *dc=gr.dc, CD3I32 *ctrl, I64 count, Bool closed=FALSE)
{//3rd order. Clipping but no transform or thick.
    return BSpline3(dc, ctrl, count, &GrLinePlot, closed);
}

I64 GrLineFat3(CDC *dc=gr.dc, I64 x1, I64 y1, I64 z1, I64 x2, I64 y2, I64 z2, I64 width, I64 start=0)
{//Step through line segment calling callback.
//Uses fixed-point.
    I64 res = 0, i, j, d, dx = x2 - x1, dy = y2 - y1, dz = z2 - z1, _x, _y, _z, d_lo, d_hi,
        adx = AbsI64(dx), ady = AbsI64(dy), adz = AbsI64(dz);

    if (width > 0)
    {
        if (adx >= ady)
        {
            if (adx >= adz)
            {
                if (d = adx)
                {
                    if (dx >= 0)
                        dx = 0x100000000;
                    else
                        dx = -0x100000000;
                    dy = dy << 32 / d;
                    dz = dz << 32 / d;
                }
            }
            else
            {
                if (d = adz)
                {
                    dx = dx << 32 / d;
                    dy = dy << 32 / d;
                    if (dz >= 0)
                        dz = 0x100000000;
                    else
                        dz = -0x100000000;
                }
            }
            x1 <<= 32;
            y1 <<= 32;
            z1 <<= 32;
            for (j = 0; j < start; j++)
            {
                x1 += dx;
                y1 += dy;
                z1 += dz;
            }
            if (start >= d)
                res += GrFillCircle(dc, x1.i32[1], y1.i32[1], z1.i32[1], width);
            else
            {
                if (width == 1)
                    for (i = start; i <= d; i++)
                    {
                        dc->db_z = z1.i32[1];
                        res += GrPlot1(dc, x1.i32[1], y1.i32[1]);
                        _x = x1.i32[1];
                        _y = y1.i32[1];
                        _z = z1.i32[1];
                        x1 += dx;
                        y1 += dy;
                        z1 += dz;
                    }
                else
                {
                    i = width * Sqrt(SqrI64(adx) + SqrI64(ady)) / adx;
                    d_lo = i >> 1;
                    d_hi = (i - 1) >> 1;

                    if (dx >= 0)
                        res += GrFillSemiCircle(dc, x1.i32[1], y1.i32[1], z1.i32[1], width, 2);
                    else
                        res += GrFillSemiCircle(dc, x1.i32[1], y1.i32[1], z1.i32[1], width, 7);
                    for (i = start; i <= d; i++)
                    {
                        res += GrVLine(dc, x1.i32[1], y1.i32[1] - d_lo, y1.i32[1] + d_hi, z1.i32[1], z1.i32[1]);
                        _x = x1.i32[1];
                        _y = y1.i32[1];
                        _z = z1.i32[1];
                        x1 += dx;
                        y1 += dy;
                        z1 += dz;
                    }
                    x1 -= dx;
                    y1 -= dy;
                    z1 -= dz;
                    if (dx >= 0)
                        res += GrFillSemiCircle(dc, x1.i32[1], y1.i32[1], z1.i32[1], width, 3);
                    else
                        res += GrFillSemiCircle(dc, x1.i32[1], y1.i32[1], z1.i32[1], width, 6);
                }
            }
        }
        else
        {
            if (ady >= adz)
            {
                if (d = ady)
                {
                    dx = dx << 32 / d;
                    if (dy >= 0)
                        dy = 0x100000000;
                    else
                        dy = -0x100000000;
                    dz = dz << 32 / d;
                }
            }
            else
            {
                if (d = adz)
                {
                    dx = dx << 32 / d;
                    dy = dy << 32 / d;
                    if (dz >= 0)
                        dz = 0x100000000;
                    else
                        dz = -0x100000000;
                }
            }
            x1 <<= 32;
            y1 <<= 32;
            z1 <<= 32;
            for (j = 0; j < start; j++)
            {
                x1 += dx;
                y1 += dy;
                z1 += dz;
            }
            if (start >= d)
                res += GrFillCircle(dc, x1.i32[1], y1.i32[1], z1.i32[1], width);
            else
            {
                if (width == 1)
                    for (i = start; i <= d; i++)
                    {
                        dc->db_z = z1.i32[1];
                        res += GrPlot1(dc, x1.i32[1], y1.i32[1]);
                        _x = x1.i32[1];
                        _y = y1.i32[1];
                        _z = z1.i32[1];
                        x1 += dx;
                        y1 += dy;
                        z1 += dz;
                    }
                else
                {
                    i = width * Sqrt(SqrI64(ady) + SqrI64(adx)) / ady;
                    d_lo = i >> 1;
                    d_hi = (i - 1) >> 1;

                    if (dy >= 0)
                        res += GrFillSemiCircle(dc, x1.i32[1], y1.i32[1], z1.i32[1], width, 0);
                    else
                        res += GrFillSemiCircle(dc, x1.i32[1], y1.i32[1], z1.i32[1], width, 5);
                    for (i = start; i <= d; i++)
                    {
                        res += GrHLine(dc, x1.i32[1] - d_lo, x1.i32[1] + d_hi, y1.i32[1], z1.i32[1], z1.i32[1]);
                        _x = x1.i32[1];
                        _y = y1.i32[1];
                        _z = z1.i32[1];
                        x1 += dx;
                        y1 += dy;
                        z1 += dz;
                    }
                    x1 -= dx;
                    y1 -= dy;
                    z1 -= dz;
                    if (dy >= 0)
                        res += GrFillSemiCircle(dc, x1.i32[1], y1.i32[1], z1.i32[1], width, 1);
                    else
                        res += GrFillSemiCircle(dc, x1.i32[1], y1.i32[1], z1.i32[1], width, 4);
                }
            }
        }
    }

    return res;
}

public Bool GrLine3(CDC *dc=gr.dc, I64 x1, I64 y1, I64 z1, I64 x2, I64 y2, I64 z2, I64 step=1, I64 start=0)
{//3D. Transformation with thick.
    I64  _x1, _y1, _z1, _x2, _y2, _z2;
    Bool res = FALSE, was_transform = FALSE, was_symmetry = FALSE;

    if (dc->flags & DCF_TRANSFORMATION)
    {
        (*dc->transform)(dc, &x1, &y1, &z1);
        (*dc->transform)(dc, &x2, &y2, &z2);
        dc->flags &= ~DCF_TRANSFORMATION;
        was_transform = TRUE;
    }
    if (dc->flags & DCF_SYMMETRY)
    {
        _x1 = x1;
        _y1 = y1;
        _z1 = z1;
        DCReflect(dc, &_x1, &_y1, &_z1);
        _x2 = x2;
        _y2 = y2;
        _z2 = z2;
        DCReflect(dc, &_x2, &_y2, &_z2);
        dc->flags &= ~DCF_SYMMETRY;
        if (step == 1 && !dc->brush)
        {
            if (!start && dc->thick < 2 && !dc->depth_buf)
            {//TODO: clip z depbuf
                if (DCClipLine(dc, &_x1, &_y1, &_x2, &_y2))
                    res = Line(dc, _x1, _y1, 0, _x2, _y2, 0, &GrLinePlot0, step, start);
            }
            else
            {
                if (GrLineFat3(dc, _x1, _y1, _z1, _x2, _y2, _z2, dc->thick, start))
                    res = TRUE;
            }
        }
        else
            res = Line(dc, _x1, _y1, _z1, _x2, _y2, _z2, &GrPlot3, step, start);
        was_symmetry = TRUE;
        if (dc->flags & DCF_JUST_MIRROR)
            goto gr_done;
    }
    if (step == 1 && !dc->brush)
    {
        if (!start && dc->thick < 2 && !dc->depth_buf)
        {//TODO: clip z depbuf
            if (DCClipLine(dc, &x1, &y1, &x2, &y2))
                res |= Line(dc, x1, y1, 0, x2, y2, 0, &GrLinePlot0, step, start);
        }
        else
        {
            if (GrLineFat3(dc, x1, y1, z1, x2, y2, z2, dc->thick, start))
                res = TRUE;
        }
    }
    else
        res |= Line(dc, x1, y1, z1, x2, y2, z2, &GrPlot3, step, start);
gr_done:
    if (was_transform)
        dc->flags |= DCF_TRANSFORMATION;
    if (was_symmetry)
        dc->flags |= DCF_SYMMETRY;

    return res;
}

#help_index "Graphics/Char;Char/Graphics"

public Bool GrPutChar3(CDC *dc=gr.dc, I64 x, I64 y, I64 z, U8 ch)
{//3D. Transformation. DCF_SYMMETRY is silly.
    if (dc->flags & DCF_TRANSFORMATION)
        (*dc->transform)(dc, &x, &y, &z);

    return GrPutChar(dc, x, y, ch);
}

public I64 GrPrint3(CDC *dc=gr.dc, I64 x, I64 y, I64 z, U8 *format, ...)
{//3D. Transformation. DCF_SYMMETRY is silly.
    U8 *buf = StrPrintJoin(NULL, format, argc, argv);
    I64 res;

    if (dc->flags & DCF_TRANSFORMATION)
        (*dc->transform)(dc, &x, &y, &z);
    res = GrPrint(dc, x, y, "%s", buf);
    Free(buf);

    return res;
}

public I64 GrVPrint3(CDC *dc=gr.dc, I64 x, I64 y, I64 z, U8 *format, ...)
{//3D. Vertical text. Transformation. DCF_SYMMETRY is silly.
    U8 *buf = StrPrintJoin(NULL, format, argc, argv);
    I64 res;

    if (dc->flags & DCF_TRANSFORMATION)
        (*dc->transform)(dc, &x, &y, &z);
    res = GrVPrint(dc, x, y, "%s", buf);
    Free(buf);

    return res;
}

#help_index "Graphics"

public Bool GrEllipse3(CDC *dc=gr.dc, I64 cx, I64 cy, I64 cz, I64 x_radius, I64 y_radius, F64 rot_angle=0, 
                       I64 step=1, F64 start_radians=0, F64 len_radians=2*pi)
{//3D. Transformation with thick.
    Bool res;
    I64  x, y, z, xx, yy, zz;
    F64  m1, arg1, m2, arg2, s, c;

    if (dc->flags & DCF_TRANSFORMATION)
    {
        dc->flags &= ~DCF_TRANSFORMATION;
        (*dc->transform)(dc, &cx, &cy, &cz);

        c = Cos(rot_angle);
        s = Sin(rot_angle);

        x_radius <<= 16;
        y_radius <<= 16;

        xx = 0;
        yy = 0;
        zz = 0;
        (*dc->transform)(dc, &xx, &yy, &zz);

        x = x_radius * c;
        y = x_radius * s;
        z = 0;
        (*dc->transform)(dc, &x, &y, &z);
        x -= xx;
        y -= yy;
        z -= zz;
        R2P(&m1, &arg1, x, y);

        x = -y_radius * s;
        y = y_radius * c;
        z = 0;
        (*dc->transform)(dc, &x, &y, &z);
        x -= xx;
        y -= yy;
        z -= zz;
        R2P(&m2, &arg2, x, y);
        m2 *= Abs(Sin(arg2 - arg1));

        res = Ellipse(dc, cx, cy, cz, m1 / 0x10000, m2 / 0x10000, &GrPlot3, -arg1, step, start_radians, len_radians);
        dc->flags |= DCF_TRANSFORMATION;
    }
    else
        res = Ellipse(dc, cx, cy, cz, x_radius, y_radius, &GrPlot3, rot_angle, step, start_radians, len_radians);

    return res;
}

public Bool GrCircle3(CDC *dc=gr.dc, I64 cx, I64 cy, I64 cz, I64 radius, I64 step=1, F64 start_radians=0, F64 len_radians=2*pi)
{//3D. Transformation with thick.

    if (dc->flags & DCF_TRANSFORMATION)
        return GrEllipse3(dc, cx, cy, cz, radius, radius, 0, step, start_radians, len_radians);
    else
        return Circle(dc, cx, cy, cz, radius, &GrPlot3, step, start_radians, len_radians);
}

public Bool GrRegPoly3(CDC *dc=gr.dc, I64 cx, I64 cy, I64 cz, I64 x_radius, I64 y_radius, I64 sides, F64 rot_angle=0, 
                       I64 step=1, F64 start_radians=0, F64 len_radians=2*pi)
{//3D. Clipping and transform and thick.
    Bool res;
    I64  x, y, z, xx, yy, zz;
    F64  m1, arg1, m2, arg2, s, c;

    if (dc->flags & DCF_TRANSFORMATION)
    {
        dc->flags &= ~DCF_TRANSFORMATION;
        (*dc->transform)(dc, &cx, &cy, &cz);

        c = Cos(rot_angle);
        s = Sin(rot_angle);

        x_radius <<= 16;
        y_radius <<= 16;

        xx = 0;
        yy = 0;
        zz = 0;
        (*dc->transform)(dc, &xx, &yy, &zz);

        x = x_radius * c;
        y = x_radius * s;
        z = 0;
        (*dc->transform)(dc, &x, &y, &z);
        x -= xx;
        y -= yy;
        z -= zz;
        R2P(&m1, &arg1, x, y);

        x = -y_radius * s;
        y = y_radius * c;
        z = 0;
        (*dc->transform)(dc, &x, &y, &z);
        x -= xx;
        y -= yy;
        z -= zz;
        R2P(&m2, &arg2, x, y);
        m2 *= Abs(Sin(arg2 - arg1));

        res = RegPoly(dc, cx, cy, cz, m1 / 0x10000, m2 / 0x10000, sides, &GrPlot3, -arg1, step, start_radians, len_radians);
        dc->flags |= DCF_TRANSFORMATION;
    }
    else
        res = RegPoly(dc, cx, cy, cz, x_radius, y_radius, sides, &GrPlot3, rot_angle, step, start_radians, len_radians);

    return res;
}

public I64 GrFloodFill3(CDC *dc=gr.dc, I64 x1, I64 y1, I64 z1, Bool not_color=FALSE)
{//3D. Transformation.
//not_color=TRUE means fill up to everything which is not the current color.
    //not_color=FALSE means fill all parts equ to the color under the point.
    //Returns count of pixs changed
    I64 res, old_flags = dc->flags, _x, _y, _z;

    if (dc->flags & DCF_TRANSFORMATION)
    {
        (*dc->transform)(dc, &x1, &y1, &z1);
        dc->flags &= ~DCF_TRANSFORMATION;
    }
    if (dc->flags & DCF_SYMMETRY)
    {
        _x = x1;
        _y = y1;
        _z = z1;
        DCReflect(dc, &_x, &_y, &_z);
        dc->flags &= ~DCF_SYMMETRY;
        res = GrFloodFill(dc, _x, _y, not_color, _z, dc->depth_buf);
        if (dc->flags & DCF_JUST_MIRROR)
            goto gr_done;
    }
    res = GrFloodFill(dc, x1, y1, not_color, z1, dc->depth_buf);
gr_done:
    dc->flags = old_flags;

    return res;
}

#help_index "Graphics;Graphics/Device Contexts"

Option(OPTf_WARN_HEADER_MISMATCH, OFF);
public I64 GrBlot3(CDC *dc=gr.dc, I64 x1, I64 y1, I64 z1, CDC *img)
{//3D. Clipping and transformation.
    CColorROPU32     old_color = dc->color;
    I64              color,
                 reg i, j, w = img->width, h = img->height, 
                     d1, dx1, dy1, dz1, 
                 reg d2, dx2, dy2, dz2, 
                     adx1, ady1, adz1, adx2, ady2, adz2, x2, y2, z2, x3, y3, z3, 
                     dw, reg dh, x, y, _x1, _y1, _z1, _x2, _y2, _z2, _x3, _y3, _z3, last_x, last_y, res = 0;
    Bool             first;
    CDC             *old_brush = dc->brush;

    if (dc->depth_buf || dc->flags & (DCF_TRANSFORMATION | DCF_SYMMETRY))
    {
        x2 = x1 + w;
        y2 = y1;
        z2 = z1;
        x3 = x1;
        y3 = y1 + h;
        z3 = z1;
        if (dc->flags & DCF_TRANSFORMATION)
        {
            (*dc->transform)(dc, &x1, &y1, &z1);
            (*dc->transform)(dc, &x2, &y2, &z2);
            (*dc->transform)(dc, &x3, &y3, &z3);
        }
        if (dc->flags & DCF_SYMMETRY)
        {
            _x1 = x1;
            _y1 = y1;
            _z1 = z1;
            DCReflect(dc, &_x1, &_y1, &_z1);
            _x2 = x2;
            _y2 = y2;
            _z2 = z2;
            DCReflect(dc, &_x2, &_y2, &_z2);
            _x3 = x3;
            _y3 = y3;
            _z3 = z3;
            DCReflect(dc, &_x3, &_y3, &_z3);
            dx1 = _x2 - _x1;
            dy1 = _y2 - _y1;
            dz1 = _z2 - _z1;
            dx2 = _x3 - _x1;
            dy2 = _y3 - _y1;
            dz2 = _z3 - _z1;
            adx1 = AbsI64(dx1);
            ady1 = AbsI64(dy1);
            adz1 = AbsI64(dz1);
            adx2 = AbsI64(dx2);
            ady2 = AbsI64(dy2);
            adz2 = AbsI64(dz2);

            if (adx1 >= ady1)
            {
                if (adx1 >= adz1)
                    d1 = adx1;
                else
                    d1 = adz1;
            }
            else
            {
                if (ady1 >= adz1)
                    d1 = ady1;
                else
                    d1 = adz1;
            }
            if (adx2 >= ady2)
            {
                if (adx2 >= adz2)
                    d2 = adx2;
                else
                    d2 = adz2;
            }
            else
            {
                if (ady2 >= adz2)
                    d2 = ady2;
                else
                    d2 = adz2;
            }

            if (AbsI64(d1) != w || AbsI64(d2) != h)
            {
                d1 <<= 1;
                d2 <<= 1;
            }
            if (d1)
            {
                dx1 = dx1 << 32 / d1;
                dy1 = dy1 << 32 / d1;
                dz1 = dz1 << 32 / d1;
            }
            else
                goto normal_image;
            if (d2)
            {
                dx2 = dx2 << 32 / d2;
                dy2 = dy2 << 32 / d2;
                dz2 = dz2 << 32 / d2;
            }
            else
                goto normal_image;
            dc->brush = NULL;
            x = 0;
            y = 0;
            dw = w << 32 / d1;
            dh = h << 32 / d2;

            first = TRUE;
            _x1 <<= 32;
            _y1 <<= 32;
            _z1 <<= 32;
            for (j = 0; j <= d1; j++)
            {
                _x2 = _x1;
                _y2 = _y1;
                _z2 = _z1;
                y = 0;
                for (i = 0; i <= d2; i++)
                {
                    if (_x2.i32[1] != last_x || _y2.i32[1] != last_y || first)
                    {
                        if ((color = GrPeek(img, x.i32[1], y.i32[1])) >= 0)
                        {
                            if (dc->color.c0.rop == ROPB_MONO)
                            {
                                if (color)
                                {
                                    dc->color = old_color & ~ROPF_DITHER;
                                    if (dc->depth_buf)
                                    {
                                        dc->db_z = _z2.i32[1];
                                        GrPlot1(dc, _x2.i32[1], _y2.i32[1]);
                                    }
                                    else
                                        GrPlot(dc, _x2.i32[1], _y2.i32[1]);
                                }
                            }
                            else
                            {
                                if (color != TRANSPARENT)
                                {
                                    dc->color = old_color & ~COLORROP_NO_ROP0_MASK|color;
                                    if (dc->depth_buf)
                                    {
                                        dc->db_z = _z2.i32[1];
                                        GrPlot1(dc, _x2.i32[1], _y2.i32[1]);
                                    }
                                    else
                                        GrPlot(dc, _x2.i32[1], _y2.i32[1]);
                                }
                            }
                        }
                    }
                    first = FALSE;
                    last_x = _x2.i32[1];
                    last_y = _y2.i32[1];
                    _x2 += dx2;
                    _y2 += dy2;
                    _z2 += dz2;
                    y += dh;
                }
                _x1 += dx1;
                _y1 += dy1;
                _z1 += dz1;
                x += dw;
            }
            res = 1;
normal_image:
            if (dc->flags & DCF_JUST_MIRROR)
                goto gr_done;
        }
        dx1 = x2 - x1;
        dy1 = y2 - y1;
        dz1 = z2 - z1;
        dx2 = x3 - x1;
        dy2 = y3 - y1;
        dz2 = z3 - z1;
        adx1 = AbsI64(dx1);
        ady1 = AbsI64(dy1);
        adz1 = AbsI64(dz1);
        adx2 = AbsI64(dx2);
        ady2 = AbsI64(dy2);
        adz2 = AbsI64(dz2);

        if (adx1 >= ady1)
        {
            if (adx1 >= adz1)
                d1 = adx1;
            else
                d1 = adz1;
        }
        else
        {
            if (ady1 >= adz1)
                d1 = ady1;
            else
                d1 = adz1;
        }
        if (adx2 >= ady2)
        {
            if (adx2 >= adz2)
                d2 = adx2;
            else
                d2 = adz2;
        }
        else
        {
            if (ady2 >= adz2)
                d2 = ady2;
            else
                d2 = adz2;
        }
        if (AbsI64(d1) != w || AbsI64(d2) != h)
        {
            d1 <<= 1;
            d2 <<= 1;
        }
        if (d1)
        {
            dx1 = dx1 << 32 / d1;
            dy1 = dy1 << 32 / d1;
            dz1 = dz1 << 32 / d1;
        }
        else
            goto gr_done;
        if (d2)
        {
            dx2 = dx2 << 32 / d2;
            dy2 = dy2 << 32 / d2;
            dz2 = dz2 << 32 / d2;
        }
        else
            goto gr_done;
        dc->brush = NULL;
        x = 0;
        y = 0;
        dw = w << 32 / d1;
        dh = h << 32 / d2;

        first = TRUE;
        x1 <<= 32;
        y1 <<= 32;
        z1 <<= 32;
        for (j = 0; j <= d1; j++)
        {
            x2 = x1;
            y2 = y1;
            z2 = z1;
            y = 0;
            for (i = 0; i <= d2; i++)
            {
                if (x2.i32[1] != last_x || y2.i32[1] != last_y || first)
                {
                    if ((color = GrPeek(img, x.i32[1], y.i32[1])) >= 0)
                    {
                        if (dc->color.c0.rop == ROPB_MONO)
                        {
                            if (color)
                            {
                                dc->color = old_color & ~ROPF_DITHER;
                                if (dc->depth_buf)
                                {
                                    dc->db_z = z2.i32[1];
                                    GrPlot1(dc, x2.i32[1], y2.i32[1]);
                                }
                                else
                                    GrPlot(dc, x2.i32[1], y2.i32[1]);
                            }
                        }
                        else
                        {
                            if (color != TRANSPARENT)
                            {
                                dc->color = old_color & ~COLORROP_NO_ROP0_MASK | color;//COLOR
                                if (dc->depth_buf)
                                {
                                    dc->db_z = z2.i32[1];
                                    GrPlot1(dc, x2.i32[1], y2.i32[1]);
                                }
                                else
                                    GrPlot(dc, x2.i32[1], y2.i32[1]);
                            }
                        }
                    }
                }
                first = FALSE;
                last_x = x2.i32[1];
                last_y = y2.i32[1];
                x2 += dx2;
                y2 += dy2;
                z2 += dz2;
                y += dh;
            }
            x1 += dx1;
            y1 += dy1;
            z1 += dz1;
            x += dw;
        }
        res = 1;    //TODO: check off screen
    }
    else
        res = GrBlot(dc, x1, y1, img);
gr_done:
    dc->color = old_color;
    dc->brush = old_brush;

    return res;
}
Option(OPTf_WARN_HEADER_MISMATCH, ON);

#help_index "Graphics"

public Bool Gr2Bezier3(CDC *dc=gr.dc, CD3I32 *ctrl)
{//2nd order. Clipping and transform and thick.
    Bool     res = FALSE;
    I64      i, x, y, z, old_flags = dc->flags;
    CD3I32  *ctrl2 = NULL, *ctrl3 = NULL;

    if (dc->flags & DCF_TRANSFORMATION)
    {
        ctrl2 = MAlloc(sizeof(CD3I32) * 3);
        for (i = 0; i < 3; i++)
        {
            x = ctrl[i].x;
            y = ctrl[i].y;
            z = ctrl[i].z;
            (*dc->transform)(dc, &x, &y, &z);
            ctrl2[i].x = x;
            ctrl2[i].y = y;
            ctrl2[i].z = z;
        }
        dc->flags &= ~DCF_TRANSFORMATION;
        ctrl = ctrl2;
    }
    if (dc->flags & DCF_SYMMETRY)
    {
        ctrl3 = MAlloc(sizeof(CD3I32) * 3);
        for (i = 0; i < 3; i++)
        {
            x = ctrl[i].x;
            y = ctrl[i].y;
            z = ctrl[i].z;
            DCReflect(dc, &x, &y, &z);
            ctrl3[i].x = x;
            ctrl3[i].y = y;
            ctrl3[i].z = z;
        }
        dc->flags &= ~DCF_SYMMETRY;
        res = Bezier2(dc, ctrl3, &GrPlot3);
        if (dc->flags & DCF_JUST_MIRROR)
            goto gr_done;
    }

    res |= Bezier2(dc, ctrl, &GrPlot3);
gr_done:
    Free(ctrl2);
    Free(ctrl3);
    dc->flags = old_flags;

    return res;
}

public Bool Gr3Bezier3(CDC *dc=gr.dc, CD3I32 *ctrl)
{//3rd order. Clipping and transform and thick.
    Bool     res = FALSE;
    I64      i, x, y, z, old_flags = dc->flags;
    CD3I32  *ctrl2 = NULL, *ctrl3 = NULL;

    if (dc->flags & DCF_TRANSFORMATION)
    {
        ctrl2 = MAlloc(sizeof(CD3I32) * 4);
        for (i = 0; i < 4; i++)
        {
            x = ctrl[i].x;
            y = ctrl[i].y;
            z = ctrl[i].z;
            (*dc->transform)(dc, &x, &y, &z);
            ctrl2[i].x = x;
            ctrl2[i].y = y;
            ctrl2[i].z = z;
        }
        dc->flags &= ~DCF_TRANSFORMATION;
        ctrl = ctrl2;
    }
    if (dc->flags & DCF_SYMMETRY)
    {
        ctrl3 = MAlloc(sizeof(CD3I32) * 4);
        for (i = 0; i < 4; i++)
        {
            x = ctrl[i].x;
            y = ctrl[i].y;
            z = ctrl[i].z;
            DCReflect(dc, &x, &y, &z);
            ctrl3[i].x = x;
            ctrl3[i].y = y;
            ctrl3[i].z = z;
        }
        dc->flags &= ~DCF_SYMMETRY;
        res = Bezier3(dc, ctrl3, &GrPlot3);
        if (dc->flags & DCF_JUST_MIRROR)
            goto gr_done;
    }

    res |= Bezier3(dc, ctrl, &GrPlot3);
gr_done:
    Free(ctrl2);
    Free(ctrl3);
    dc->flags = old_flags;

    return res;
}

public I64 Gr2BSpline3(CDC *dc=gr.dc, CD3I32 *ctrl, I64 count, Bool closed=FALSE)
{//2nd order. Clipping and transform and thick.
    Bool     res = FALSE;
    I64      i, x, y, z, old_flags = dc->flags;
    CD3I32  *ctrl2 = NULL, *ctrl3 = NULL;

    if (dc->flags & DCF_TRANSFORMATION)
    {
        ctrl2 = MAlloc(sizeof(CD3I32) * count);
        for (i = 0; i < count; i++)
        {
            x = ctrl[i].x;
            y = ctrl[i].y;
            z = ctrl[i].z;
            (*dc->transform)(dc, &x, &y, &z);
            ctrl2[i].x = x;
            ctrl2[i].y = y;
            ctrl2[i].z = z;
        }
        dc->flags &= ~DCF_TRANSFORMATION;
        ctrl = ctrl2;
    }
    if (dc->flags & DCF_SYMMETRY)
    {
        ctrl3 = MAlloc(sizeof(CD3I32) * count);
        for (i = 0;i < count; i++)
        {
            x = ctrl[i].x;
            y = ctrl[i].y;
            z = ctrl[i].z;
            DCReflect(dc, &x, &y, &z);
            ctrl3[i].x = x;
            ctrl3[i].y = y;
            ctrl3[i].z = z;
        }
        dc->flags &= ~DCF_SYMMETRY;
        res = BSpline2(dc, ctrl3, count, &GrPlot3, closed);
        if (dc->flags & DCF_JUST_MIRROR)
            goto gr_done;
    }

    res |= BSpline2(dc, ctrl, count, &GrPlot3, closed);
gr_done:
    Free(ctrl2);
    Free(ctrl3);
    dc->flags = old_flags;

    return res;
}

public Bool Gr3BSpline3(CDC *dc=gr.dc, CD3I32 *ctrl, I64 count, Bool closed=FALSE)
{//3rd order. Clipping and transform and thick.
    Bool     res = FALSE;
    I64      i, x, y, z, old_flags = dc->flags;
    CD3I32  *ctrl2 = NULL, *ctrl3 = NULL;

    if (dc->flags & DCF_TRANSFORMATION)
    {
        ctrl2 = MAlloc(sizeof(CD3I32) * count);
        for (i = 0; i < count; i++)
        {
            x = ctrl[i].x;
            y = ctrl[i].y;
            z = ctrl[i].z;
            (*dc->transform)(dc, &x, &y, &z);
            ctrl2[i].x = x;
            ctrl2[i].y = y;
            ctrl2[i].z = z;
        }
        dc->flags &= ~DCF_TRANSFORMATION;
        ctrl = ctrl2;
    }
    if (dc->flags & DCF_SYMMETRY)
    {
        ctrl3 = MAlloc(sizeof(CD3I32) * count);
        for (i = 0; i < count; i++)
        {
            x = ctrl[i].x;
            y = ctrl[i].y;
            z = ctrl[i].z;
            DCReflect(dc, &x, &y, &z);
            ctrl3[i].x = x;
            ctrl3[i].y = y;
            ctrl3[i].z = z;
        }
        dc->flags &= ~DCF_SYMMETRY;
        res = BSpline3(dc, ctrl3, count, &GrPlot3, closed);
        if (dc->flags & DCF_JUST_MIRROR)
            goto gr_done;
    }

    res |= BSpline3(dc, ctrl, count, &GrPlot3, closed);
gr_done:
    Free(ctrl2);
    Free(ctrl3);
    dc->flags = old_flags;

    return res;
}

public I64 GrFillTri0(CDC *dc=gr.dc, CD3I32 *p1, CD3I32 *p2, CD3I32 *p4)
{//3D. Returns count of pixs changed
    I64      x1, x2, y1, y2, z1, z2, dx1, dy1, dz1, dx2, dy2, dz2, res = 0, i, min, max;
    CTask   *win_task;

    if (AbsI64(p1->y - p2->y) + AbsI64(p1->y - p4->y) <= AbsI64(p1->x - p2->x) + AbsI64(p1->x - p4->x))
    {//p1 is min x
        if (p4->x < p2->x)
            SwapI64(&p4, &p2);
        if (p2->x < p1->x)
            SwapI64(&p2, &p1);

            //p2y <= p4y
        if (p4->y < p2->y)
            SwapI64(&p4, &p2);

        min = 0;
        max = dc->height;
        if (dc->flags & DCF_SCREEN_BITMAP)
        {
            win_task = dc->win_task;
            min -= win_task->scroll_y + win_task->pix_top;
            max -= win_task->scroll_y + win_task->pix_top;
            if (max > win_task->pix_bottom - (win_task->scroll_y + win_task->pix_top))
                max = win_task->pix_bottom - (win_task->scroll_y + win_task->pix_top);
        }

        if ((dy2 = p4->y - p1->y) < 0)
        {
            dy1 = p2->y - p1->y;
            dx1 = (p1->x - p2->x) << 32 / dy1;
            dz1 = (p1->z - p2->z) << 32 / dy1;

            dx2 = (p1->x - p4->x) << 32 / dy2;
            dz2 = (p1->z - p4->z) << 32 / dy2;
            x1 = x2 = p1->x << 32;
            y1 = p1->y;
            z1 = z2 = p1->z << 32;
            if (y1 + dy2 < min)
            {
                i = min - (y1 + dy2);
                if (i > -dy2)
                    goto ft_done;
                dy2 += i;
            }
            if (y1 >= max)
            {
                i = y1 - max + 1;
                if (i > -dy2)
                    i = -dy2;
                dy2 += i;
                y1 -= i;
                x1 += dx1 * i;
                x2 += dx2 * i;
                z1 += dz1 * i;
                z2 += dz2 * i;
            }
            while (dy2++)
            {
                res += GrHLine(dc, x1.i32[1], x2.i32[1], y1, z1.i32[1], z2.i32[1]);
                y1--;
                x1 += dx1;
                x2 += dx2;
                z1 += dz1;
                z2 += dz2;
            }
            if (dy2 = p2->y-p4->y)
            {
                dx2 = (p4->x - p2->x) << 32 / dy2;
                dz2 = (p4->z - p2->z) << 32 / dy2;
                if (y1+dy2<min)
                {
                    i = min - (y1 + dy2);
                    if (i > -dy2)
                        goto ft_done;
                    dy2 += i;
                }
                if (y1 >= max)
                {
                    i = y1 - max + 1;
                    if (i > -dy2)
                        goto ft_done;
                    dy2 += i;
                    y1 -= i;
                    x1 += dx1 * i;
                    x2 += dx2 * i;
                    z1 += dz1 * i;
                    z2 += dz2 * i;
                }
            }
            while (dy2++ <= 0)
            {
                res += GrHLine(dc, x1.i32[1], x2.i32[1], y1, z1.i32[1], z2.i32[1]);
                y1--;
                x1 += dx1;
                x2 += dx2;
                z1 += dz1;
                z2 += dz2;
            }
        }
        else if ((dy2 = p2->y - p1->y) > 0)
        {
            dy1 = p4->y  - p1->y;
            dx1 = (p4->x - p1->x) << 32 / dy1;
            dz1 = (p4->z - p1->z) << 32 / dy1;

            dx2 = (p2->x-p1->x) << 32 / dy2;
            dz2 = (p2->z-p1->z) << 32 / dy2;
            x1 = x2 = p1->x << 32;
            y1 = p1->y;
            z1 = z2 = p1->z << 32;
            if (y1 + dy2 >= max)
            {
                i = y1 + dy2 - max + 1;
                if (i > dy2)
                    goto ft_done;
                dy2 -= i;
            }
            if (y1 < min)
            {
                i = min - y1;
                if (i > dy2)
                    i = dy2;
                dy2 -= i;
                y1 += i;
                x1 += dx1 * i;
                x2 += dx2 * i;
                z1 += dz1 * i;
                z2 += dz2 * i;
            }
            while (dy2--)
            {
                res += GrHLine(dc, x1.i32[1], x2.i32[1], y1, z1.i32[1], z2.i32[1]);
                y1++;
                x1 += dx1;
                x2 += dx2;
                z1 += dz1;
                z2 += dz2;
            }
            if (dy2 = p4->y - p2->y)
            {
                dx2 = (p4->x - p2->x) << 32 / dy2;
                dz2 = (p4->z - p2->z) << 32 / dy2;
                if (y1 + dy2 >= max)
                {
                    i = y1 + dy2 - max + 1;
                    if (i > dy2)
                        goto ft_done;
                    dy2 -= i;
                }
                if (y1 < min)
                {
                    i = min - y1;
                    if (i > dy2)
                        goto ft_done;
                    dy2 -= i;
                    y1 += i;
                    x1 += dx1 * i;
                    x2 += dx2 * i;
                    z1 += dz1 * i;
                    z2 += dz2 * i;
                }
            }
            while (dy2-- >= 0)
            {
                res += GrHLine(dc, x1.i32[1], x2.i32[1], y1, z1.i32[1], z2.i32[1]);
                y1++;
                x1 += dx1;
                x2 += dx2;
                z1 += dz1;
                z2 += dz2;
            }
        }
        else
        {
            if (dy1 = p2->y - p1->y)
            {
                dx1 = (p2->x - p1->x) << 32 / dy1;
                dz1 = (p2->z - p1->z) << 32 / dy1;
                if (dy2 = p2->y - p4->y)
                {
                    dx2 = (p2->x - p4->x) << 32 / dy2;
                    dz2 = (p2->z - p4->z) << 32 / dy2;
                }
                else
                {
                    dx2 = 0;
                    dz2 = 0;
                }
                x1 = x2 = p2->x << 32;
                y1 = p2->y;
                z1 = z2 = p2->z << 32;
                if (y1 < min)
                {
                    i = min - y1;
                    if (i > -dy1)
                        i = -dy1;
                    dy1 += i;
                    y1 += i;
                    x1 += dx1 * i;
                    x2 += dx2 * i;
                    z1 += dz1 * i;
                    z2 += dz2 * i;
                }
                while (dy1++ <= 0)
                {
                    if (y1 < max)
                        res += GrHLine(dc, x1.i32[1], x2.i32[1], y1, z1.i32[1], z2.i32[1]);
                    y1++;
                    x1 += dx1;
                    x2 += dx2;
                    z1 += dz1;
                    z2 += dz2;
                }
            }
            if (dy1 = p4->y - p1->y)
            {
                dx1 = (p1->x - p4->x) << 32 / dy1;
                dz1 = (p1->z - p4->z) << 32 / dy1;
                if (dy2 = p4->y - p2->y)
                {
                    dx2 = (p2->x - p4->x) << 32 / dy2;
                    dz2 = (p2->z - p4->z) << 32 / dy2;
                }
                else
                {
                    dx2 = 0;
                    dz2 = 0;
                }
                x1 = x2 = p4->x << 32;
                y1 = p4->y;
                z1 = z2 = p4->z << 32;
                if (y1-dy1 < min)
                {
                    i = min-(y1 - dy1);
                    if (i > dy1)
                        goto ft_done;
                    dy1 -= i;
                }
                if (y1 >= max)
                {
                    i = y1 - max + 1;
                    if (i > dy1)
                        goto ft_done;
                    dy1 -= i;
                    y1 -= i;
                    x1 += dx1 * i;
                    x2 += dx2 * i;
                    z1 += dz1 * i;
                    z2 += dz2 * i;
                }
                while (dy1-- >= 0)
                {
                    res += GrHLine(dc, x1.i32[1], x2.i32[1], y1, z1.i32[1], z2.i32[1]);
                    y1--;
                    x1 += dx1;
                    x2 += dx2;
                    z1 += dz1;
                    z2 += dz2;
                }
            }
        }
    }
    else
    {
//p1 is min y
        if (p4->y < p2->y)
            SwapI64(&p4, &p2);
        if (p2->y < p1->y)
            SwapI64(&p2, &p1);

            //p2x <= p4x
        if (p4->x < p2->x)
            SwapI64(&p4, &p2);

        min = 0;
        max = dc->width;
        if (dc->flags & DCF_SCREEN_BITMAP)
        {
            win_task = dc->win_task;
            min -= win_task->scroll_x + win_task->pix_left;
            max -= win_task->scroll_x + win_task->pix_left;
            if (max > win_task->pix_right - (win_task->scroll_x + win_task->pix_left))
                max = win_task->pix_right - (win_task->scroll_x + win_task->pix_left);
        }

        if ((dx2 = p4->x-p1->x) < 0)
        {
            dx1 = p2->x - p1->x;
            dy1 = (p1->y - p2->y) << 32 / dx1;
            dz1 = (p1->z - p2->z) << 32 / dx1;

            dy2 = (p1->y - p4->y) << 32 / dx2;
            dz2 = (p1->z - p4->z) << 32 / dx2;
            y1 = y2 = p1->y << 32;
            x1 = p1->x;
            z1 = z2 = p1->z << 32;
            if (x1 + dx2 < min)
            {
                i = min - (x1 + dx2);
                if (i > -dx2)
                    goto ft_done;
                dx2 += i;
            }
            if (x1 >= max)
            {
                i = x1 - max + 1;
                if (i > -dx2)
                    i = -dx2;
                dx2 += i;
                x1 -= i;
                y1 += dy1 * i;
                y2 += dy2 * i;
                z1 += dz1 * i;
                z2 += dz2 * i;
            }
            while (dx2++)
            {
                res += GrVLine(dc, x1, y1.i32[1], y2.i32[1], z1.i32[1], z2.i32[1]);
                x1--;
                y1 += dy1;
                y2 += dy2;
                z1 += dz1;
                z2 += dz2;
            }
            if (dx2 = p2->x - p4->x)
            {
                dy2 = (p4->y - p2->y) << 32 / dx2;
                dz2 = (p4->z - p2->z) << 32 / dx2;
                if (x1 + dx2 < min)
                {
                    i = min - (x1 + dx2);
                    if (i > -dx2)
                        goto ft_done;
                    dx2 += i;
                }
                if (x1 >= max)
                {
                    i = x1 - max + 1;
                    if (i > -dx2)
                        goto ft_done;
                    dx2 += i;
                    x1 -= i;
                    y1 += dy1 * i;
                    y2 += dy2 * i;
                    z1 += dz1 * i;
                    z2 += dz2 * i;
                }
            }
            while (dx2++ <= 0)
            {
                res += GrVLine(dc, x1, y1.i32[1], y2.i32[1], z1.i32[1], z2.i32[1]);
                x1--;
                y1 += dy1;
                y2 += dy2;
                z1 += dz1;
                z2 += dz2;
            }
        }
        else if ((dx2 = p2->x - p1->x) > 0)
        {
            dx1 = p4->x - p1->x;
            dy1 = (p4->y - p1->y) << 32 / dx1;
            dz1 = (p4->z - p1->z) << 32 / dx1;

            dy2 = (p2->y - p1->y) << 32 / dx2;
            dz2 = (p2->z - p1->z) << 32 / dx2;
            y1 = y2 = p1->y << 32;
            x1 = p1->x;
            z1 = z2 = p1->z << 32;
            if (x1 + dx2 >= max)
            {
                i = x1 + dx2 - max + 1;
                if (i > dx2)
                    goto ft_done;
                dx2 -= i;
            }
            if (x1 < min)
            {
                i = min - x1;
                if (i > dx2)
                    i = dx2;
                dx2 -= i;
                x1 += i;
                y1 += dy1 * i;
                y2 += dy2 * i;
                z1 += dz1 * i;
                z2 += dz2 * i;
            }
            while (dx2--)
            {
                res += GrVLine(dc, x1, y1.i32[1], y2.i32[1], z1.i32[1], z2.i32[1]);
                x1++;
                y1 += dy1;
                y2 += dy2;
                z1 += dz1;
                z2 += dz2;
            }
            if (dx2 = p4->x - p2->x)
            {
                dy2 = (p4->y - p2->y) << 32 / dx2;
                dz2 = (p4->z - p2->z) << 32 / dx2;
                if (x1 + dx2 >= max)
                {
                    i = x1 + dx2 - max + 1;
                    if (i > dx2)
                        goto ft_done;
                    dx2 -= i;
                }
                if (x1 < min)
                {
                    i = min - x1;
                    if (i > dx2)
                        goto ft_done;
                    dx2 -= i;
                    x1 += i;
                    y1 += dy1 * i;
                    y2 += dy2 * i;
                    z1 += dz1 * i;
                    z2 += dz2 * i;
                }
            }
            while (dx2-- >= 0)
            {
                res += GrVLine(dc, x1, y1.i32[1], y2.i32[1], z1.i32[1], z2.i32[1]);
                x1++;
                y1 += dy1;
                y2 += dy2;
                z1 += dz1;
                z2 += dz2;
            }
        }
        else
        {
            if (dx1 = p2->x - p1->x)
            {
                dy1 = (p2->y - p1->y) << 32 / dx1;
                dz1 = (p2->z - p1->z) << 32 / dx1;
                if (dx2 = p2->x - p4->x)
                {
                    dy2 = (p2->y - p4->y) << 32 / dx2;
                    dz2 = (p2->z - p4->z) << 32 / dx2;
                }
                else
                {
                    dy2 = 0;
                    dz2 = 0;
                }
                y1 = y2 = p2->y << 32;
                x1 = p2->x;
                z1 = z2 = p2->z << 32;
                if (x1 < min)
                {
                    i = min - x1;
                    if (i > -dx1)
                        i = -dx1;
                    dx1 += i;
                    x1 += i;
                    y1 += dy1 * i;
                    y2 += dy2 * i;
                    z1 += dz1 * i;
                    z2 += dz2 * i;
                }
                while (dx1++ <= 0)
                {
                    if (x1 < max)
                        res += GrVLine(dc, x1, y1.i32[1], y2.i32[1], z1.i32[1], z2.i32[1]);
                    x1++;
                    y1 += dy1;
                    y2 += dy2;
                    z1 += dz1;
                    z2 += dz2;
                }
            }
            if (dx1 = p4->x - p1->x)
            {
                dy1 = (p1->y - p4->y) << 32 / dx1;
                dz1 = (p1->z - p4->z) << 32 / dx1;
                if (dx2 = p4->x - p2->x)
                {
                    dy2 = (p2->y - p4->y) << 32 / dx2;
                    dz2 = (p2->z - p4->z) << 32 / dx2;
                }
                else
                {
                    dy2 = 0;
                    dz2 = 0;
                }
                y1 = y2 = p4->y << 32;
                x1 = p4->x;
                z1 = z2 = p4->z << 32;
                if (x1 - dx1 < min)
                {
                    i = min - (x1 - dx1);
                    if (i > dx1)
                        goto ft_done;
                    dx1 -= i;
                }
                if (x1 >= max)
                {
                    i = x1 - max + 1;
                    if (i > dx1)
                        goto ft_done;
                    dx1 -= i;
                    x1 -= i;
                    y1 += dy1 * i;
                    y2 += dy2 * i;
                    z1 += dz1 * i;
                    z2 += dz2 * i;
                }
                while (dx1-- >= 0)
                {
                    res += GrVLine(dc, x1, y1.i32[1], y2.i32[1], z1.i32[1], z2.i32[1]);
                    x1--;
                    y1 += dy1;
                    y2 += dy2;
                    z1 += dz1;
                    z2 += dz2;
                }
            }
        }
    }
ft_done:

    return res;
}