
public class MyGraphics
{
    public MyGraphics(java.awt.Graphics g,
                      java.awt.Dimension gsize,
                      double x0, double x1, double y0, double y1)
    {
        this.g = g;
        this.gsize = gsize;
        fitToWindow(x0, x1, y0, y1);
    }
    public MyGraphics(java.awt.Graphics g,
                      java.awt.Dimension gsize)
    {
        this.g = g;
        this.gsize = gsize;
        fitToWindow(-1., 1., -1., 1.);
    }






    public void fit(double x0, double x1, double y0, double y1,
                    double left, double right, double bottom, double top)
    {
        scaleX = (right-left) / (x1-x0);
        scaleY = (top-bottom) / (y1-y0);
        translateX = left - scaleX*x0;
        translateY = bottom - scaleY*y0;
    }
    public void fitToWindow(double x0, double x1, double y0, double y1)
    {
        fit(x0, x1, y0, y1,
            .5, gsize.width-.5, gsize.height-.5, .5);
    }
    public void fitToPixel(double centerX, double centerY,
                           double pixelSizeX, double pixelSizeY)
    {
        fit(centerX, centerX+pixelSizeX, centerY, centerY+pixelSizeY,
            gsize.width*.5, gsize.width*.5 + 1., gsize.height*.5, gsize.height*.5 + 1);
    }

    public void translate(double x, double y)
    {
        translateX += x*scaleX;
        translateY += y*scaleY;
    }

    public void pick(double x, double y, double result[ ])
    {
        result[0] = (x-translateX)/scaleX;
        result[1] = (y-translateY)/scaleY;
    }


    public void unpick(double x, double y, double result[ ])
    {
        result[0] = x*scaleX + translateX;
        result[1] = y*scaleY + translateY;
    }


    public void fillWindow()
    {
        g.fillRect(0,0, gsize.width,gsize.height);
    }

    public void drawLineNonClipped(double x0, double y0, double x1, double y1,
                                   boolean antiAlias)
    {
        if (antiAlias)
        {
            if (x0 == x1 && y0 == y1)
            {
                drawPoint(x0, y0, 1);
                return;
            }




            x0 = (x0 * scaleX + translateX - .5);
            y0 = (y0 * scaleY + translateY - .5);
            x1 = (x1 * scaleX + translateX - .5);
            y1 = (y1 * scaleY + translateY - .5);

            java.awt.Color savedColor = getColor();
            java.awt.Color[ ] transparentColorCache = getTransparentColorCache(savedColor);

            if (Math.abs(y1-y0) >= Math.abs(x1-x0))
            {
                int Y0 = (int)Math.round(y0);
                int Y1 = (int)Math.round(y1);
                int dirY = (Y0 <= Y1 ? 1 : -1);
                double slope = (x1-x0)/(y1-y0);
                for (int Y = Y0; Y != Y1; Y += dirY)
                {


                    double x = x0 + (Y-y0) * slope;
                    int X = (int)Math.floor(x);
                    double frac = x-X;
                    int alpha = (int)(frac * 256.);
                    setColor(transparentColorCache[255-alpha]);
                    g.fillRect(X, Y, 1, 1);
                    setColor(transparentColorCache[alpha]);
                    g.fillRect(X+1, Y, 1, 1);
                }
            }
            else
            {
                int X0 = (int)Math.round(x0);
                int X1 = (int)Math.round(x1);
                int dirX = (X0 <= X1 ? 1 : -1);
                double slope = (y1-y0)/(x1-x0);
                for (int X = X0; X != X1; X += dirX)
                {


                    double y = y0 + (X-x0) * slope;
                    int Y = (int)Math.floor(y);
                    double frac = y-Y;
                    int alpha = (int)(frac * 256.);
                    setColor(transparentColorCache[255-alpha]);
                    g.fillRect(X, Y, 1, 1);
                    setColor(transparentColorCache[alpha]);
                    g.fillRect(X, Y+1, 1, 1);
                }
            }

            setColor(savedColor);
        }
        else
        {







            if (false)
            g.drawLine((int)(x0 * scaleX + translateX - .5),
                       (int)(y0 * scaleY + translateY - .5),
                       (int)(x1 * scaleX + translateX - .5),
                       (int)(y1 * scaleY + translateY - .5));
            else
            g.drawLine((int)(x0 * scaleX + translateX),
                       (int)(y0 * scaleY + translateY),
                       (int)(x1 * scaleX + translateX),
                       (int)(y1 * scaleY + translateY));
        }
    }

    public void drawLine(double x0, double y0, double x1, double y1,
                         boolean antiAlias)
    {

        drawLineNonClipped(x0, y0, x1, y1, antiAlias);
    }

    public void drawPoint(double x, double y, int nPixels)
    {
        int x0 = (int)(x * scaleX + translateX - .5*nPixels);
        int y0 = (int)(y * scaleY + translateY - .5*nPixels);
        if (verbose >= 2) System.out.println("in drawPoint("+x+","+y+","+nPixels+") -> "+x0+","+y0);


        if (x0 < -nPixels
         || x0 > gsize.width+nPixels
         || y0 < -nPixels
         || y0 > gsize.width+nPixels)
            return;
        g.fillRect(x0, y0, nPixels, nPixels);
    }
    public void drawString(String s, double x, double y)
    {
        g.drawString(s, (int)(x * scaleX + translateX),
                        (int)(y * scaleY + translateY));
    }
    public void drawString(String s, double x, double y,
                                     double xJustify,
                                     double yJustify)
    {
        java.awt.FontMetrics fm = g.getFontMetrics();

        g.drawString(s, (int)(x * scaleX + translateX - fm.stringWidth(s)*(xJustify+1.)*.5),
                        (int)(y * scaleY + translateY + fm.getAscent()*(1.-yJustify)*.5));

    }
    public void drawStringCentered(String s, double x, double y)
    {
        drawString(s, x, y, 0, 0);
    }
    public void drawStringRightJustified(String s, double x, double y)
    {
        drawString(s, x, y, 1, 0);
    }

    public double getFontHeight()
    {
        java.awt.FontMetrics fm = g.getFontMetrics();
        return Math.abs(fm.getHeight() / scaleY);
    }




    public void smartDrawArcNonClipped(
        double focusX, double focusY,
        double focusAngleRadians,
        double c,
        double start, double end,
        boolean antiAlias)
    {


        final boolean useSegments = antiAlias;
        if (useSegments)
        {
            int nSegs = ((10)>=((int)((((end-start)*c) < 0 ? -((end-start)*c) : ((end-start)*c)) / (1*(Math.PI/180))))?(10):((int)((((end-start)*c) < 0 ? -((end-start)*c) : ((end-start)*c)) / (1*(Math.PI/180)))));


            double cosFocusAngle = Math.cos(focusAngleRadians);
            double sinFocusAngle = Math.sin(focusAngleRadians);
            double normalAndTangent[][] = {
                { cosFocusAngle, sinFocusAngle},
                {-sinFocusAngle, cosFocusAngle},
            };

            double prevPoint[] = new double[2];
            double point[] = new double[2];
            double scratch[] = new double[2];
            for (int i = 0; i < nSegs+1; ++i)
            {
                double t = ((start) + ((double)i/nSegs)*((end)-(start)));
                findPointOnArc(t, focusX, focusY, c, normalAndTangent, scratch, point);

                if (i > 0)
                    this.drawLineNonClipped(prevPoint[0], prevPoint[1],
                                            point[0], point[1],
                                            antiAlias);

                System.arraycopy(point, 0, prevPoint, 0, 2);
            }
            return;
        }





        if (c*Math.abs(start-end) > .75*Math.PI)
        {
            smartDrawArcNonClipped(focusX, focusY, focusAngleRadians, c,
                                   start, (start+end)*.5,
                                   antiAlias);
            smartDrawArcNonClipped(focusX, focusY, focusAngleRadians, c,
                                   (start+end)*.5, end,
                                   antiAlias);

            return;
        }






        {
            focusX = focusX * scaleX + translateX;
            focusY = focusY * scaleY + translateY;
            if (scaleY < 0.)
            {
                focusAngleRadians *= -1.;
                start *= -1.;
                end *= -1.;
            }
            double scale = .5 * (Math.abs(scaleX) + Math.abs(scaleY));
            c /= scale;
            start *= scale;
            end *= scale;
        }






        {
            if (c < 0.)
            {
                c *= -1.;
                focusAngleRadians += Math.PI;
                start *= -1.;
                end *= -1.;
            }
            if (start > end)
            {
                double temp = start;
                start = end;
                end = temp;
            }
        }



        double normalX = Math.cos(focusAngleRadians);
        double normalY = Math.sin(focusAngleRadians);
        double normalAndTangent[][] = {
            { normalX, normalY},
            {-normalY, normalX},
        };





        double startX, startY;
        double endX, endY;
        {
            double point[] = new double[2];
            double scratch[] = new double[2];

            findPointOnArc(start, focusX, focusY, c, normalAndTangent, scratch, point);
            startX = point[0];
            startY = point[1];

            findPointOnArc(end, focusX, focusY, c, normalAndTangent, scratch, point);
            endX = point[0];
            endY = point[1];
        }


        int maxPointsToDraw = (int)Math.ceil(Math.abs(end-start)) + 1;

        int nPointsDrawn = 0;
        int x = (int)Math.round(startX), y = (int)Math.round(startY);
        double X = x - focusX;
        double Y = y - focusY;

        this.g.fillRect(x, y, 1, 1);
        nPointsDrawn++;

        if (c < 1)
        {
            while (true)
            {
                final int quantizedDirVectors[][] = {
                    {1,0}, {1,1}, {0,1}, {-1,1}, {-1,0}, {-1,-1}, {0,-1}, {1,-1}
                };

                int dir;
                {


                    double thisNormalX = c*X + normalX;
                    double thisNormalY = c*Y + normalY;
                    double thisTangentX = -thisNormalY;
                    double thisTangentY = thisNormalX;
                    int quantizedDirX = (thisTangentX < 0. ? -1 : thisTangentX > 0. ? 1 : 0);
                    int quantizedDirY = (thisTangentY < 0. ? -1 : thisTangentY > 0. ? 1 : 0);
                    double absThisTangentX = Math.abs(thisTangentX);
                    double absThisTangentY = Math.abs(thisTangentY);
                    if (absThisTangentX > 2*absThisTangentY)
                        quantizedDirY = 0;
                    else if (absThisTangentY > 2*absThisTangentX)
                        quantizedDirX = 0;

                    for (dir = 0; dir < 8; ++dir)
                    {
                        if (quantizedDirVectors[dir][0] == quantizedDirX
                         && quantizedDirVectors[dir][1] == quantizedDirY)
                            break;
                    }
                    if (dir == 8)
                    {
                        System.out.println("huh?????");
                        break;
                    }
                }

                x += quantizedDirVectors[dir][0];
                y += quantizedDirVectors[dir][1];
                X = x - focusX;
                Y = y - focusY;



                double signedDistanceOutsideCircle =
                    (c * (X*X + Y*Y) + 2 * (X*normalX + Y*normalY))
                  / (MyMath.hypot(c*X + normalX, c*Y + normalY) + 1.);





                double inflation;
                {
                    double thisNormalX = c*X + normalX;
                    double thisNormalY = c*Y + normalY;
                    inflation = MyMath.hypot(thisNormalX, thisNormalY)
                              / Math.max(Math.abs(thisNormalX), Math.abs(thisNormalY));
                }

                signedDistanceOutsideCircle *= inflation;


                if (signedDistanceOutsideCircle > .5001)
                {
                    x -= quantizedDirVectors[dir][0];
                    y -= quantizedDirVectors[dir][1];

                    dir = (dir + 1) % 8;
                    x += quantizedDirVectors[dir][0];
                    y += quantizedDirVectors[dir][1];
                    X = x - focusX;
                    Y = y - focusY;
                }
                else if (signedDistanceOutsideCircle < -.5001)
                {
                    x -= quantizedDirVectors[dir][0];
                    y -= quantizedDirVectors[dir][1];

                    dir = (dir - 1 + 8) % 8;
                    x += quantizedDirVectors[dir][0];
                    y += quantizedDirVectors[dir][1];
                    X = x - focusX;
                    Y = y - focusY;
                }

                {
                    double thisNormalX = c*X + normalX;
                    double thisNormalY = c*Y + normalY;
                    signedDistanceOutsideCircle =
                        (c * (X*X + Y*Y) + 2 * (X*normalX + Y*normalY))
                      / (MyMath.hypot(c*X + normalX, c*Y + normalY) + 1.);
                    double arcPointX = focusX + X - signedDistanceOutsideCircle * thisNormalX;
                    double arcPointY = focusY + Y - signedDistanceOutsideCircle * thisNormalY;
                    double startToArcPoint = MyMath.hypot(arcPointX-startX,
                                                          arcPointY-startY);
                    double arcDistance = startToArcPoint * MyMath.asin_over_x(c*startToArcPoint*.5);
                    if (false)
                    {
                        System.out.println("    nPointsDrawn = "+nPointsDrawn);
                        System.out.println("        signedDistanceOutsideCircle = "+signedDistanceOutsideCircle);
                        System.out.println("        startX = "+startX);
                        System.out.println("        startY = "+startY);
                        System.out.println("        arcPointX = "+arcPointX);
                        System.out.println("        arcPointY = "+arcPointY);
                        System.out.println("        startToArcPoint = "+startToArcPoint);
                        System.out.println("        arcDistance = "+arcDistance);
                        System.out.println("        start = "+start);
                        System.out.println("        end = "+end);
                        System.out.println("        c = "+c);
                    }
                    if (arcDistance >= end-start)
                        break;
                }

                this.g.fillRect(x, y, 1, 1);
                nPointsDrawn++;




                if (nPointsDrawn >= maxPointsToDraw-1)
                    break;
            }
            this.g.fillRect((int)Math.round(endX), (int)Math.round(endY), 1, 1);
            nPointsDrawn++;
        }

    }

    public void smartDrawArcClipped(
        double focusX, double focusY,
        double focusAngleRadians,
        double c,
        double start, double end,
        boolean antiAlias,
        double clipX0, double clipX1, double clipY0, double clipY1,
        double slack)
    {

        double clipMinX = Math.min(clipX0, clipX1);
        double clipMaxX = Math.max(clipX0, clipX1);
        double clipMinY = Math.min(clipY0, clipY1);
        double clipMaxY = Math.max(clipY0, clipY1);

        if (c * Math.abs(end-start) > 2*Math.PI)
        {
            start = 0.;
            end = 2*Math.PI / c;
        }

        int dir = (start <= end ? 1 : -1);

        double normalX = Math.cos(focusAngleRadians);
        double normalY = Math.sin(focusAngleRadians);
        double normalAndTangent[][] = {
            { normalX, normalY},
            {-normalY, normalX},
        };
        double point[] = new double[2];
        double scratch[] = new double[2];






        while (true)
        {


            findPointOnArc(start, focusX, focusY, c, normalAndTangent, scratch, point);
            double startX = point[0];
            double startY = point[1];


            double dist = ((startX-clipMaxX)>=(clipMinX-startX)?((startX-clipMaxX)>=(startY-clipMaxY)?((startX-clipMaxX)>=(clipMinY-startY)?(startX-clipMaxX):(clipMinY-startY)):((startY-clipMaxY)>=(clipMinY-startY)?(startY-clipMaxY):(clipMinY-startY))):((clipMinX-startX)>=(startY-clipMaxY)?((clipMinX-startX)>=(clipMinY-startY)?(clipMinX-startX):(clipMinY-startY)):((startY-clipMaxY)>=(clipMinY-startY)?(startY-clipMaxY):(clipMinY-startY))))


                                               ;

            if (dist <= slack)
                break;
            start += dir * dist;

            if (dir * (end-start) < 0)
            {

                return;
            }
        }
        while (true)
        {


            findPointOnArc(end, focusX, focusY, c, normalAndTangent, scratch, point);
            double endX = point[0];
            double endY = point[1];


            double dist = ((endX-clipMaxX)>=(clipMinX-endX)?((endX-clipMaxX)>=(endY-clipMaxY)?((endX-clipMaxX)>=(clipMinY-endY)?(endX-clipMaxX):(clipMinY-endY)):((endY-clipMaxY)>=(clipMinY-endY)?(endY-clipMaxY):(clipMinY-endY))):((clipMinX-endX)>=(endY-clipMaxY)?((clipMinX-endX)>=(clipMinY-endY)?(clipMinX-endX):(clipMinY-endY)):((endY-clipMaxY)>=(clipMinY-endY)?(endY-clipMaxY):(clipMinY-endY))))


                                             ;
            if (dist <= slack)
                break;
            end -= dir * dist;

            if (dir * (end-start) < 0)
            {

                return;
            }
        }






        {
            double mid = (start+end)*.5;
            findPointOnArc(mid, focusX, focusY, c, normalAndTangent, scratch, point);
            double midX = point[0];
            double midY = point[1];
            double dist = ((midX-clipMaxX)>=(clipMinX-midX)?((midX-clipMaxX)>=(midY-clipMaxY)?((midX-clipMaxX)>=(clipMinY-midY)?(midX-clipMaxX):(clipMinY-midY)):((midY-clipMaxY)>=(clipMinY-midY)?(midY-clipMaxY):(clipMinY-midY))):((clipMinX-midX)>=(midY-clipMaxY)?((clipMinX-midX)>=(clipMinY-midY)?(clipMinX-midX):(clipMinY-midY)):((midY-clipMaxY)>=(clipMinY-midY)?(midY-clipMaxY):(clipMinY-midY))))


                                             ;
            if (dist > slack)
            {
                smartDrawArcClipped(focusX, focusY, focusAngleRadians, c,
                                    start, mid,
                                    antiAlias,
                                    clipX0, clipX1, clipY0, clipY1, slack);
                smartDrawArcClipped(focusX, focusY, focusAngleRadians, c,
                                    mid, end,
                                    antiAlias,
                                    clipX0, clipX1, clipY0, clipY1, slack);

                return;
            }
        }

        smartDrawArcNonClipped(focusX,focusY, focusAngleRadians, c, start,end,
                               antiAlias);

    }

    public void smartDrawArc_(
        double focusX, double focusY,
        double focusAngleRadians,
        double c,
        double start, double end,
        boolean antiAlias)
    {



        smartDrawArcClipped(focusX, focusY, focusAngleRadians, c, start, end,
                            antiAlias,
                            (0-translateX)/scaleX,
                            (gsize.width-translateX)/scaleX,
                            (0-translateY)/scaleY,
                            (gsize.height-translateY)/scaleY,
                            2./((Math.abs(scaleX)+Math.abs(scaleY))*.5));

    }





    public void smartDrawThickArc(
        double thicknessInPixels,
        double focusX, double focusY,
        double focusAngleRadians,
        double c,
        double start, double end,
        boolean antiAlias)
    {
        double n = (int)Math.ceil(2.*thicknessInPixels-1.);
        if (n <= 1)
        {
            smartDrawArc_(focusX, focusY, focusAngleRadians, c, start, end, antiAlias);
            return;
        }

        double scale = .5 * (Math.abs(scaleX) + Math.abs(scaleY));
        double onePixel = 1./scale;
        double dx = Math.cos(focusAngleRadians);
        double dy = Math.sin(focusAngleRadians);

        for (int i = 0; (i) < (n); ++i)
        {
            double s = ((-(thicknessInPixels-1)/2) + (i/(n-1.))*(((thicknessInPixels-1)/2)-(-(thicknessInPixels-1)/2)))
                                      * onePixel;
            double foo = 1. + c*s;
            if (foo <= 0.)
                continue;
            smartDrawArc_(
                    focusX + s * dx,
                    focusY + s * dy,
                    focusAngleRadians,
                    c / foo,
                    start * foo,
                    end * foo,
                    antiAlias);
        }
    }

    public void drawThickArc(double x, double y, double width, double height,
                        double startRadians, double arcRadians,
                        double thicknessInPixels,
                        boolean antiAlias)
    {

        double centerX = x + width*.5;
        double centerY = y + height*.5;
        double radius = .25*(width+height);
        double curvature = 1./radius;
        double focusAngleRadians = startRadians;
        double start = 0.;
        double end = arcRadians * radius;
        double focusX = centerX + radius * Math.cos(focusAngleRadians);
        double focusY = centerY + radius * Math.sin(focusAngleRadians);
        smartDrawThickArc(
                     thicknessInPixels,
                     focusX, focusY,
                     focusAngleRadians,
                     curvature,
                     start, end,
                     antiAlias);

        return;
    }

    public java.awt.Color getColor()
    {
        return g.getColor();
    }
    public void setColor(java.awt.Color color)
    {
        g.setColor(color);
    }

    private java.awt.Graphics g;
    private java.awt.Dimension gsize;
    private double scaleX = 1;
    private double scaleY = 1;
    private double translateX = 0;
    private double translateY = 0;
    public int verbose = 0;

            private java.awt.Color[ ] cache = null;
            private java.awt.Color cacheColor = null;
            private int cacheRGB;
        private java.awt.Color[ ] getTransparentColorCache(java.awt.Color color)
        {
            if (color != cacheColor)
            {
                cacheColor = color;

                int colorRGB = color.getRGB();

                if (cache == null || colorRGB != cacheRGB)
                {
                    cacheRGB = colorRGB;
                    if (cache == null)
                        cache = new java.awt.Color[256];
                    int r = (colorRGB >> 16) & 0xff;
                    int g = (colorRGB >> 8) & 0xff;
                    int b = (colorRGB >> 0) & 0xff;
                    for (int i = 0; i < 256; ++i)
                    {

                        cache[i] = new java.awt.Color(r, g, b, i);

                    }

                }
                else
                {

                }
            }
            else
            {

            }

            return cache;
        }


        private static void findPointOnArc(double t,
                                           double focusX, double focusY,
                                           double c,
                                           double normalAndTangent[][],
                                           double scratch[ ],
                                           double result[ ])
        {

            scratch[0] = -MyMath.cosf1_over_x(t*c) * t;

            scratch[1] = MyMath.sin_over_x(t*c) * t;
            VecMath.vxm(result, scratch, normalAndTangent);
            result[0] += focusX;
            result[1] += focusY;
        }

    public void flush()
    {
    }
}
