

public class HyperbolicDrawUtils
{
    public static void
    DrawOmnitruncatedTiling(MyGraphics mg,
                            int hyperbolicModel,
                            Isometry2 F0,
                            int p, int q,
                            int maxLevels,
                            int maxIsometries,
                            double wythoffCoeffs[ ],
                            boolean drawPrimal,
                                float primalR, float primalG, float primalB,
                            boolean drawDual,
                                float dualR, float dualG, float dualB,
                            boolean drawSnub,
                                float snubR, float snubG, float snubB,
                                int snubParity,
                            boolean doCurveLines,
                            boolean doAntiAlias,
                            boolean doRemoveDups,
                            boolean doRandomJitter,
                            double lineThicknessInPixels,
                            int showIsometryLabels,
                            int verbose,
                            Isometry2 return_smallestIsometry,
                            PickableSchwarzPolygon return_pickableSchwarz)
    {
        double tolerance = 1e-3;

        double halfEdgeLength = HyperbolicUtils.calcRegularTilingHalfEdgeLength(p,q);
        if (verbose >= 1)
        {
            {System.out.print("    ");System.out.println("p" + " = " + (p));};
            {System.out.print("    ");System.out.println("q" + " = " + (q));};
            {System.out.print("    ");System.out.println("halfEdgeLength" + " = " + (halfEdgeLength));};
        }
        double R = HyperbolicUtils.h2eNorm(2*halfEdgeLength);
        double r = HyperbolicUtils.h2eNorm(halfEdgeLength);


        Isometry2 A = new Isometry2(new Complex(Math.cos(Math.PI-2*Math.PI/q),
                                                Math.sin(Math.PI-2*Math.PI/q)),
                                    new Complex(R,0),
                                    1);
        Isometry2 B = new Isometry2(new Complex(Math.cos(2*Math.PI/q),
                                                Math.sin(2*Math.PI/q)),
                                    Complex.zero,
                                    1);
        Isometry2 gens[] = {A, B};





        double tooSmall = 1e-3;
        double tooFar = 1 - tooSmall;

        double x0 = 0, y0 = 0, x1 = R, y1 = 0;
        Isometry2 list[] = new Isometry2[maxIsometries];
        {
            int i;
            for (i = 0; (i) < (maxIsometries); ++i)
                list[i] = new Isometry2();
        }

        boolean useNewShellMethod = true;
        int nIsometries;
        if (useNewShellMethod)
        {



            {
                Isometry2 C;
                {
                    C = Isometry2.mul(Isometry2.pureTranslation(R,0),
                                      Isometry2.pureRotation(Math.PI));
                }

                gens = new Isometry2[q];
                for (int iGen = 0; (iGen) < (q); ++iGen)
                {
                    Isometry2 rotate = Isometry2.pow(B, iGen);
                    Isometry2 temp = Isometry2.mul(C, rotate.inverse());
                    gens[iGen] = Isometry2.mul(rotate, temp);
                }
            }

            nIsometries = HyperbolicUtils.UnCachedEnumerateIsometry2GroupForUniformTiling(
                                F0,
                                gens.length,
                                gens,
                                new int[] {p},
                                q,
                                new int[] {~0},
                                maxIsometries, list,
                                null,
                                return_smallestIsometry,
                                maxLevels,
                                tooFar,
                                tooSmall,
                                verbose);

            {
                int newNIsometries = nIsometries * q;
                Isometry2 newIsometries[] = new Isometry2[newNIsometries];
                Isometry2 rotates[] = new Isometry2[q];
                for (int iArm = 0; (iArm) < (q); ++iArm)
                    rotates[iArm] = Isometry2.pow(B, iArm);
                for (int iIsometry = 0; (iIsometry) < (nIsometries); ++iIsometry)
                {
                    for (int iArm = 0; (iArm) < (q); ++iArm)
                        newIsometries[iIsometry*q + iArm] = Isometry2.mul(
                                        list[iIsometry],
                                        rotates[iArm]);
                }
                nIsometries = newNIsometries;
                list = newIsometries;
            }
        }
        else
            nIsometries = HyperbolicUtils.UnCachedEnumerateIsometry2Group(
                                    F0,
                                    gens.length,
                                    gens,
                                    q,
                                    maxIsometries, list,
                                    return_smallestIsometry,
                                    maxLevels,
                                    tooFar,
                                    tooSmall,
                                    verbose);
        if (verbose >= 1) System.out.println(""+nIsometries+" isometries");

        boolean drawGreenGridForDebugging = false;
        if (drawGreenGridForDebugging)
        {
            mg.setColor(new java.awt.Color(0.f, .5f, 0.f));
            int subDiv = 20;
            int i;
            for (i = 0; (i) < (subDiv+1); ++i)
            {
                mg.drawLine(((-1.f) + ((float)i/subDiv)*((1.f)-(-1.f))), -1.f,
                            ((-1.f) + ((float)i/subDiv)*((1.f)-(-1.f))), 1.f,
                            doAntiAlias);
                mg.drawLine(-1.f, ((-1.f) + ((float)i/subDiv)*((1.f)-(-1.f))),
                            1.f, ((-1.f) + ((float)i/subDiv)*((1.f)-(-1.f))),
                            doAntiAlias);
            }
        }


        double elementCenters[][] = new double[3][2];
        elementCenters[0][0] = 0.;
        elementCenters[0][1] = 0.;
        elementCenters[1][0] = r;
        elementCenters[1][1] = 0.;
        HyperbolicUtils.calcFaceCenter(p, q, elementCenters[2]);

        return_pickableSchwarz.setVertices(elementCenters, F0);

        boolean drawSchwarzPolygon = false;
        if (drawSchwarzPolygon)
        {
            Complex sp[] = return_pickableSchwarz.SchwarzPolygon;
            int i;
            System.out.println("schwarz polygone "+sp.length);
            mg.setColor(new java.awt.Color(dualR, dualG, dualB));
            for (i = 0; (i) < (sp.length); ++i)
                DrawLine2fPoincare(mg,
                                   sp[i].x,
                                   sp[i].y,
                                   sp[(i+1)%sp.length].x,
                                   sp[(i+1)%sp.length].y,
                                   tolerance,
                                   hyperbolicModel,
                                   doCurveLines,
                                   doAntiAlias,
                                   doRandomJitter,
                                   lineThicknessInPixels+4);
        }


        if (drawDual)
        {
     mg.setColor(new java.awt.Color(dualR, dualG, dualB));

            Complex P[] = new Complex[3];
            P[0] = new Complex(0,0);
            P[1] = new Complex(r,0);
            double P2[] = new double[2];
            HyperbolicUtils.calcFaceCenter(p, q, P2);
            P[2] = new Complex(P2);
            int segments[][] = new int[4][2];
            int nSegments = 0;
            if (wythoffCoeffs[0] != 0.)
            {
                segments[nSegments][0] = 1;
                segments[nSegments][1] = 2;
                nSegments++;
            }
            if (wythoffCoeffs[1] != 0.)
            {
                segments[nSegments][0] = 0;
                segments[nSegments][1] = 2;
                nSegments++;
            }
            if (wythoffCoeffs[2] != 0.)
            {
                segments[nSegments][0] = 0;
                segments[nSegments][1] = 1;
                nSegments++;
            }
            { if (!(nSegments <= segments.length)) HyperbolicUtils.set_exception("HyperbolicDrawUtils.java","DrawOmnitruncatedTiling" , "nSegments <= segments.length" ); };

            DrawTiling(mg, hyperbolicModel, nIsometries, list, P.length, P, nSegments, segments, null, doCurveLines, doAntiAlias, doRemoveDups, doRandomJitter, lineThicknessInPixels, 0, tolerance, verbose);
        }

        if (drawSnub)
        {
            mg.setColor(new java.awt.Color(snubR, snubG, snubB));

            Complex P[] = new Complex[6];
            double P0[] = new double[2];
            if (!HyperbolicUtils.hBary2(3, P0, wythoffCoeffs, elementCenters, verbose))
                return;
            P[0] = new Complex(P0);
            {
                P[1] = B.apply(P[0]);
                P[2] = B.inverse().apply(P[0]);
                P[3] = A.apply(P[1]);
                P[4] = A.apply(P[0]);
                P[5] = A.inverse().apply(P[0]);
            }
            if (snubParity == 1)
            {
                for (int i = 0; (i) < (P.length); ++i)
                    P[i].y *= -1;
            }

            final int segments[ ][ ] = {{0,1},{0,2},{0,3},{0,4}};
            int nSegments = segments.length;
            DrawTiling(mg, hyperbolicModel, nIsometries, list, P.length, P, nSegments, segments, null, doCurveLines, doAntiAlias, doRemoveDups, doRandomJitter, lineThicknessInPixels, showIsometryLabels, tolerance, verbose);
        }

        if (drawPrimal)
        {
     mg.setColor(new java.awt.Color(primalR, primalG, primalB));

            Complex P[] = new Complex[7];
            double P0[] = new double[2];
            if (!HyperbolicUtils.hBary2(3, P0, wythoffCoeffs, elementCenters, verbose))
                return;
            P[0] = new Complex(P0);
            for (int i = 0; (i) < (3); ++i)
            {
                double temp[] = new double[2];
                HyperbolicUtils.calcClosestPointOnLine2(
                                                temp,
                                                P0,
                                                elementCenters[i],
                                                elementCenters[(i+1)%3]);
                P[i+1] = new Complex(temp);

                HyperbolicUtils.hlerp2_poincare(P0, temp, 2., temp);

                P[i+1+3] = new Complex(temp);
            }






            int segments[][] = new int[6][2];
            int nSegments = 0;
System.out.println("wythoff "+wythoffCoeffs[0]+"  "+wythoffCoeffs[1]+"  "+wythoffCoeffs[2]);
System.out.println("elementCenters "+elementCenters[0][0]+","+elementCenters[0][1]+"  "+elementCenters[1][0]+","+elementCenters[1][1]+"  "+elementCenters[2][0]+","+elementCenters[2][1]);
System.out.println("P "+P[0]+"  "+P[1]+"  "+P[2]);
            for (int i = 0; (i) < (3); ++i)
                if (wythoffCoeffs[(i+2)%3] != 0.)
                {
                    segments[nSegments][0] = 0;
                    segments[nSegments][1] = i+1;
                    nSegments++;
                    if (i==2
                     || wythoffCoeffs[2*i] != 0.)
                    {
                        segments[nSegments][0] = i+1;
                        segments[nSegments][1] = i+1 + 3;
                        nSegments++;
                    }
                }
            { if (!(nSegments <= segments.length)) HyperbolicUtils.set_exception("HyperbolicDrawUtils.java","DrawOmnitruncatedTiling" , "nSegments <= segments.length" ); };
System.out.println("nsegments="+nSegments);
            DrawTiling(mg, hyperbolicModel, nIsometries, list, P.length, P, nSegments, segments, null, doCurveLines, doAntiAlias, doRemoveDups, doRandomJitter, lineThicknessInPixels, showIsometryLabels, tolerance, verbose);
        }

        if (hyperbolicModel == HyperbolicUtils.POINCARE_DISK
         || hyperbolicModel == HyperbolicUtils.KLEIN_DISK
         || hyperbolicModel == HyperbolicUtils.ANTIKLEIN_DISK)
        {



            mg.setColor(new java.awt.Color(primalR, primalG, primalB));
            mg.drawThickArc(-1.,-1.,2.,2.,0.,2*Math.PI,		/* afficher le cercle unité */
                            lineThicknessInPixels,
                            doAntiAlias);
        }
    }

    public static void
    DrawUniformTiling(MyGraphics mg,
                            int hyperbolicModel,
                            Isometry2 F0,
                            int pp[], int q, int backEdgeInds[ ],
                            int maxLevels,
                            int maxIsometries,
                            double wythoffCoeffs[ ],
                            boolean drawPrimal,
                                float primalR, float primalG, float primalB,
                            boolean drawDual,
                                float dualR, float dualG, float dualB,
                            boolean drawSnub,
                                float snubR, float snubG, float snubB,
                                int snubParity,
                            boolean doCurveLines,
                            boolean doAntiAlias,
                            boolean doRemoveDups,
                            boolean doRandomJitter,
                            double lineThicknessInPixels,
                            int showIsometryLabels,
                            int verbose,
                            Isometry2 return_smallestIsometry,
                            PickableSchwarzPolygon return_pickableSchwarz)
    {
        double tolerance = 1e-3;

        double halfEdgeLength = HyperbolicUtils.calcUniformTilingHalfEdgeLength(pp,q,1);
        int p = pp[0];
        if (verbose >= 1)
        {
            {System.out.print("    ");System.out.println("p" + " = " + (p));};
            {System.out.print("    ");System.out.println("q" + " = " + (q));};
            {System.out.print("    ");System.out.println("halfEdgeLength" + " = " + (halfEdgeLength));};
        }
        double R = HyperbolicUtils.h2eNorm(2*halfEdgeLength);
        double r = HyperbolicUtils.h2eNorm(halfEdgeLength);
        double angles[] = new double[pp.length * q];
        {
            double angle = 0.;
            for (int i = 0; (i) < (pp.length*q); ++i)
            {
                angles[i] = angle;
                angle += 2*HyperbolicUtils.polygonHalfAngle(pp[i%pp.length], halfEdgeLength);
            }
        }
        Complex neighbors[] = new Complex[pp.length * q];
        Complex edgeCenters[] = new Complex[pp.length * q];
        {
            for (int i = 0; (i) < (pp.length*q); ++i)
            {
                double angle = angles[i];
                neighbors[i] = new Complex(R*Math.cos(angle),R*Math.sin(angle));
                edgeCenters[i] = new Complex(r*Math.cos(angle),r*Math.sin(angle));
            }
        }
        Complex faceCenters[] = new Complex[pp.length*q];
        {
            for (int i = 0; (i) < (pp.length*q); ++i)
            {
                double P2[] = new double[2];
                HyperbolicUtils.calcFaceCenterFromHalfEdgeLength(pp[i%pp.length], halfEdgeLength, P2);
                faceCenters[i] = new Complex(P2);

                Isometry2.pureRotation(angles[i]).apply(faceCenters[i], faceCenters[i]);
            }
        }

        Isometry2 gens[] = new Isometry2[pp.length * q];
        {
            for (int i = 0; (i) < (pp.length*q); ++i)
            {
                boolean isRotation = false;
                int j = backEdgeInds[i%pp.length];
                if (j < 0)
                {
                    j = ~j;
                    isRotation = true;
                }
                j += i/pp.length*pp.length;


                Isometry2 G = Isometry2.pureRotation(-angles[j]);
                if (isRotation)
                {

                    G = Isometry2.mul(
                            new Isometry2(new Complex(-1,0),
                                          new Complex(0,0),
                                          1),
                            G);
                }
                else
                {




                    G = Isometry2.mul(
                            new Isometry2(new Complex(-1,0),
                                          new Complex(0,0),
                                          -1),
                            G);
                }

                G = Isometry2.mul(
                        Isometry2.pureRotation(angles[i]),
                        G);

                G = Isometry2.mul(
                        Isometry2.pureTranslation(neighbors[i].x,neighbors[i].y),
                        G);

                gens[i] = G;
            }
        }







        double tooSmall = 5e-4;
        double tooFar = 1 - tooSmall;

        double x0 = 0, y0 = 0, x1 = R, y1 = 0;
        Isometry2 list[] = new Isometry2[maxIsometries];
        {
            int i;
            for (i = 0; (i) < (maxIsometries); ++i)
                list[i] = new Isometry2();
        }

        boolean useNewShellMethod = true;

        int compositionLengths[] = null;
        if (drawSnub && useNewShellMethod)
            compositionLengths = new int[maxIsometries];

        int nIsometries;
        if (useNewShellMethod)
            nIsometries = HyperbolicUtils.UnCachedEnumerateIsometry2GroupForUniformTiling(
                                F0,
                                gens.length,
                                gens,
                                pp,
                                q,
                                backEdgeInds,
                                maxIsometries, list,
                                compositionLengths,
                                return_smallestIsometry,
                                maxLevels,
                                tooFar,
                                tooSmall,
                                verbose);
        else
            nIsometries = HyperbolicUtils.UnCachedEnumerateIsometry2Group(
                                F0,
                                gens.length,
                                gens,
                                q,
                                maxIsometries, list,
                                return_smallestIsometry,
                                maxLevels,
                                tooFar,
                                tooSmall,
                                verbose);
        if (verbose >= 1) System.out.println(""+nIsometries+" isometries");

        boolean drawGreenGridForDebugging = false;
        if (drawGreenGridForDebugging)
        {
            mg.setColor(new java.awt.Color(0.f, .5f, 0.f));
            int subDiv = 20;
            int i;
            for (i = 0; (i) < (subDiv+1); ++i)
            {
                mg.drawLine(((-1.f) + ((float)i/subDiv)*((1.f)-(-1.f))), -1.f,
                            ((-1.f) + ((float)i/subDiv)*((1.f)-(-1.f))), 1.f,
                            doAntiAlias);
                mg.drawLine(-1.f, ((-1.f) + ((float)i/subDiv)*((1.f)-(-1.f))),
                            1.f, ((-1.f) + ((float)i/subDiv)*((1.f)-(-1.f))),
                            doAntiAlias);
            }
        }

        if (drawDual)
        {
     mg.setColor(new java.awt.Color(dualR, dualG, dualB));






            Complex P[] = new Complex[2*edgeCenters.length];
            int segments[][ ] = new int[2*edgeCenters.length][2];
            for (int i = 0; (i) < (edgeCenters.length); ++i)
            {
                P[2*i+0] = edgeCenters[i];
                P[2*i+1] = faceCenters[i];
            }
            for (int i = 0; (i) < (2*edgeCenters.length); ++i)
            {
                segments[i][0] = i;
                segments[i][1] = (i+1)%segments.length;
            }

            DrawTiling(mg, hyperbolicModel, nIsometries, list, P.length, P, segments.length, segments, null, doCurveLines, doAntiAlias, doRemoveDups, doRandomJitter, lineThicknessInPixels, 0, tolerance, verbose);
        }

        return_pickableSchwarz.setVerticesFromDoubles(faceCenters, F0);
        boolean drawSchwarzPolygon = false;
        if (drawSchwarzPolygon)
        {
            mg.setColor(java.awt.Color.red);
            Complex sp[] = return_pickableSchwarz.SchwarzPolygon;
            int i;
            for (i = 0; (i) < (sp.length); ++i)
                DrawLine2fPoincare(mg,
                                   sp[i].x,
                                   sp[i].y,
                                   sp[(i+1)%sp.length].x,
                                   sp[(i+1)%sp.length].y,
                                   tolerance,
                                   hyperbolicModel,
                                   doCurveLines,
                                   doAntiAlias,
                                   doRandomJitter,
                                   lineThicknessInPixels);
        }

        if (drawSnub)
        {
     mg.setColor(new java.awt.Color(snubR, snubG, snubB));



            Complex P[] = new Complex[neighbors.length];
            int segments[][ ] = new int[neighbors.length][ ];

            if (wythoffCoeffs == null)
            {
                P[0] = Complex.zero;
                for (int i = 0; (i) < (neighbors.length); ++i)
                {
                    P[i] = neighbors[i];
                }
            }
            else
            {
                double P2[] = new double[2];
                if (!HyperbolicUtils.hBary2(faceCenters.length, P2, wythoffCoeffs, faceCenters, verbose))
                    return;
                Complex complexP2 = new Complex(P2);



                for (int i = 0; (i) < (neighbors.length); ++i)
                {
                    P[i] = new Complex();
                              HyperbolicUtils.calcClosestPointOnLine2(
                                 P[i],
                                 complexP2,
                                 faceCenters[(((i-1)+(neighbors.length))%(neighbors.length))], faceCenters[i]);
                    HyperbolicUtils.hlerp2_poincare(complexP2, P[i], 2., P[i]);
                }
            }

            for (int i = 0; (i) < (neighbors.length); ++i)
                segments[i] = new int[] {i,(i+1)%neighbors.length};

            Isometry2 snubbedList[] = new Isometry2[nIsometries];
            int nSnubbedIsometries = 0;
            {
                int desiredParity = 1 - snubParity;
                for (int i = 0; (i) < (nIsometries); ++i)
                {
                    if (compositionLengths != null
                     && compositionLengths[i] % 2 == desiredParity)
                        snubbedList[nSnubbedIsometries++] = list[i];
                }
            }

            DrawTiling(mg, hyperbolicModel, nSnubbedIsometries, snubbedList, P.length, P, segments.length, segments, null, doCurveLines, doAntiAlias, doRemoveDups, doRandomJitter, lineThicknessInPixels, showIsometryLabels, tolerance, verbose);
        }

        if (drawPrimal)
        {
     mg.setColor(new java.awt.Color(primalR, primalG, primalB));

            Complex P[] = new Complex[neighbors.length + 1];
            int segments[][ ] = new int[neighbors.length][ ];

            if (wythoffCoeffs == null)
            {
                P[0] = Complex.zero;
                for (int i = 0; (i) < (neighbors.length); ++i)
                {

                    P[i+1] = edgeCenters[i];
                }
            }
            else
            {
                double P2[] = new double[2];
                if (!HyperbolicUtils.hBary2(faceCenters.length, P2, wythoffCoeffs, faceCenters, verbose))
                    return;



                P[0] = new Complex(P2);
                for (int i = 0; (i) < (neighbors.length); ++i)
                {
                    P[i+1] = new Complex();
                              HyperbolicUtils.calcClosestPointOnLine2(
                                 P[i+1],
                                 P[0],
                                 faceCenters[(i-1+neighbors.length)%neighbors.length], faceCenters[i]);
                }
            }

            for (int i = 0; (i) < (neighbors.length); ++i)
                segments[i] = new int[] {0,i+1};

            DrawTiling(mg, hyperbolicModel, nIsometries, list, P.length, P, segments.length, segments, null, doCurveLines, doAntiAlias, doRemoveDups, doRandomJitter, lineThicknessInPixels, showIsometryLabels, tolerance, verbose);
        }

        if (hyperbolicModel == HyperbolicUtils.POINCARE_DISK
         || hyperbolicModel == HyperbolicUtils.KLEIN_DISK
         || hyperbolicModel == HyperbolicUtils.ANTIKLEIN_DISK)
        {



            mg.setColor(new java.awt.Color(primalR, primalG, primalB));
            mg.drawThickArc(-1.,-1.,2.,2.,0.,2*Math.PI,
                            lineThicknessInPixels,
                            doAntiAlias);
        }
    }

    private static void
    DrawTiling(MyGraphics mg,
               int hyperbolicModel,
               int nIsometries, Isometry2 isometries[],
               int nLocalVerts, Complex localVerts[],
               int nSegments, int segments[][ ],
               int segmentColors[ ][ ],
               boolean doCurveLines,
               boolean doAntiAlias,
               boolean doRemoveDups,
               boolean doRandomJitter,
               double lineThicknessInPixels,
               int showIsometryLabels,
               double tolerance,
               int verbose)
    {
        double label0Position[ ] = null, labelPosition[ ] = null;
        if (showIsometryLabels >= 1)
        {
            if (nIsometries >= 2)
            {
                double v0[] = {isometries[0].P.x, isometries[0].P.y};
                double v1[] = {isometries[1].P.x, isometries[1].P.y};
                double hDist = .25 * HyperbolicUtils.hlerp2_poincare(v0, v1, 0., null);
                double eDist = HyperbolicUtils.h2eNorm(hDist);
                if ((((eDist)-(0.)) > 1e-3))
                    label0Position = new double[]{eDist, .3*eDist};
                else
                    label0Position = new double[]{.08, .03};
            }
            else
                label0Position = new double[]{.08, .03};
            labelPosition = new double[2];
        }

        Complex verts[] = new Complex[nLocalVerts];
        int i, j;
        for (i = 0; (i) < (nLocalVerts); ++i)
            verts[i] = new Complex();
        for (i = 0; (i) < (nIsometries); ++i)
        {
            for (j = 0; (j) < (nLocalVerts); ++j)
                isometries[i].apply(localVerts[j], verts[j]);
            for (j = 0; (j) < (nSegments); ++j)
            {
                if (doRemoveDups)
                {

                }
                DrawLine2fPoincare(mg,
                                  verts[segments[j][0]].x,
                                  verts[segments[j][0]].y,
                                  verts[segments[j][1]].x,
                                  verts[segments[j][1]].y,
                                  tolerance,
                                  hyperbolicModel,
                                  doCurveLines,
                                  doAntiAlias,
                                  doRandomJitter,
                                  lineThicknessInPixels);
                if (showIsometryLabels >= 2)
                {
                    java.awt.Color savedColor = mg.getColor();
                    mg.setColor(java.awt.Color.red);
                    double v0[] = {verts[segments[j][0]].x,verts[segments[j][0]].y};
                    double v1[] = {verts[segments[j][1]].x,verts[segments[j][1]].y};
                    HyperbolicUtils.hlerp2_poincare(v0, v1, .5, labelPosition);
                    String label = "" + i + ":" + j;
                    if (isometries[i].R == -1)
                        label = "["+label+"]";
                    mg.drawStringCentered(label, labelPosition[0], labelPosition[1]);
                    mg.setColor(savedColor);
                }
            }

            if (showIsometryLabels == 1)
            {
                java.awt.Color savedColor = mg.getColor();
                mg.setColor(java.awt.Color.red);
                isometries[i].apply(label0Position, labelPosition);
                String label = "" + i;
                if (isometries[i].R == -1)
                    label = "["+label+"]";
                mg.drawStringCentered(label, labelPosition[0], labelPosition[1]);
                mg.setColor(savedColor);
            }
        }
    }

    public static void
    DrawLine2fPoincare(MyGraphics mg,
                       double x0, double y0, double x1, double y1,
                       double tolerance,
                       int hyperbolicModel,
                       boolean doCurveLines,
                       boolean doAntiAlias,
                       boolean doRandomJitter,
                       double lineThicknessInPixels)
    {
        if (doRandomJitter)
        {
            double jit = .02;
            x0 += (2*Math.random()-1.) * jit;
            y0 += (2*Math.random()-1.) * jit;
            x1 += (2*Math.random()-1.) * jit;
            y1 += (2*Math.random()-1.) * jit;
            double len0Sqrd = x0*x0+y0*y0;
            double len1Sqrd = x1*x1+y1*y1;
            if (len0Sqrd > .99)
            {
                double scale = .99 / Math.sqrt(len0Sqrd);
                x0 *= scale;
                y0 *= scale;
            }
            if (len1Sqrd > .99)
            {
                double scale = .99 / Math.sqrt(len1Sqrd);
                x1 *= scale;
                y1 *= scale;
            }
        }

        if (hyperbolicModel == HyperbolicUtils.KLEIN_DISK)
        {
            double p0[] = {x0,y0};
            double p1[] = {x1,y1};


            VecMath.vxs(p0, p0, 2./(1.+VecMath.normsqrd(p0)));
            VecMath.vxs(p1, p1, 2./(1.+VecMath.normsqrd(p1)));



            mg.drawLine(p0[0],p0[1],p1[0],p1[1], doAntiAlias);
        }
        else if (hyperbolicModel == HyperbolicUtils.POINCARE_DISK)
        {
            if (!doCurveLines)
            {
                mg.drawLine(x0,y0,x1,y1, doAntiAlias);
            }
            else
            {
                if (false)
                {
                    int maxIntermediatePoints = 9;
                    double intermediatePoints[][] = new double[maxIntermediatePoints+2][2];
                    intermediatePoints[0][0] = x0;
                    intermediatePoints[0][1] = y0;
                    intermediatePoints[maxIntermediatePoints+1][0] = x1;
                    intermediatePoints[maxIntermediatePoints+1][1] = y1;
                    int totalPoints = 2 + HyperbolicUtils.subdivideArc(intermediatePoints[0], intermediatePoints[maxIntermediatePoints+1], tolerance, maxIntermediatePoints, 1, intermediatePoints);
                    intermediatePoints[totalPoints-1][0] = x1;
                    intermediatePoints[totalPoints-1][1] = y1;

                    int i;
                    for (i = 0; (i) < (totalPoints); ++i)
                    {
                        mg.drawPoint(intermediatePoints[i][0], intermediatePoints[i][1],
                                     i==0||i==totalPoints-1 ? 3 : 1);
                    }
                }
                else if (true)
                {
                    if (((x1-x0)*(x1-x0)) + ((y1-y0)*(y1-y0)) > 1e-9)
                    {
                        double p0[] = {x0,y0};
                        double p1[] = {x1,y1};


                        double mid[] = new double[2];
                        HyperbolicUtils.hlerp2_poincare(p0, p1, .5, mid);

                        int sign;
                        {
                            double seg0[] = VecMath.vmv(mid,p0);
                            double seg1[] = VecMath.vmv(p1,mid);
                            sign = VecMath.vxv2(seg0,seg1) < 0. ? -1 : 1;
                        }

                        double curvature, start, end, focusAngleRadians;
                        {





                            double a = VecMath.dist(p0,mid);
                            double b = VecMath.dist(mid,p1);
                            double c = VecMath.dist(p0,p1);
                            double s = (a+b+c)/2;

                            double temp = s*(a+b-s)*(a+c-s)*(b+c-s);
                            if (temp < 0.)
                                temp = 0.;
                            curvature = 4*Math.sqrt(temp)/(a*b*c);

                            start = sign * - a * MyMath.asin_over_x(a*.5*curvature);
                            end = sign * b * MyMath.asin_over_x(b*.5*curvature);

                            if (!(((end-start) < 0 ? -(end-start) : (end-start)) < 2.01))
                            {
                                System.out.println("    " + "p0" + " = " + VecMath.toString(p0));
                                System.out.println("    " + "p1" + " = " + VecMath.toString(p1));
                                System.out.println("    " + "mid" + " = " + VecMath.toString(mid));
                                {System.out.print("    ");System.out.println("a" + " = " + (a));};
                                {System.out.print("    ");System.out.println("b" + " = " + (b));};
                                {System.out.print("    ");System.out.println("c" + " = " + (c));};
                                {System.out.print("    ");System.out.println("a+b" + " = " + (a+b));};
                                {System.out.print("    ");System.out.println("s" + " = " + (s));};
                                {System.out.print("    ");System.out.println("a+b-s" + " = " + (a+b-s));};
                                {System.out.print("    ");System.out.println("a+c-s" + " = " + (a+c-s));};
                                {System.out.print("    ");System.out.println("b+c-s" + " = " + (b+c-s));};
                                {System.out.print("    ");System.out.println("s*(a+b-s)*(a+c-s)*(b+c-s)" + " = " + (s*(a+b-s)*(a+c-s)*(b+c-s)));};
                                {System.out.print("    ");System.out.println("curvature" + " = " + (curvature));};
                                {System.out.print("    ");System.out.println("start" + " = " + (start));};
                                {System.out.print("    ");System.out.println("end" + " = " + (end));};
                            }
                            { if (!(((end-start) < 0 ? -(end-start) : (end-start)) < 2.01)) HyperbolicUtils.set_exception("HyperbolicDrawUtils.prejava","DrawLine2fPoincare" , "ABS(end-start) < 2.01"); };

                            double dirAtMid = Math.atan2(mid[1]-y0,mid[0]-x0);
                            dirAtMid += sign * Math.asin(a*.5*curvature);
                            focusAngleRadians = dirAtMid - sign * (Math.PI/2);
                        }

                        mg.smartDrawThickArc(lineThicknessInPixels,
                                             mid[0], mid[1],
                                             focusAngleRadians,
                                             curvature,
                                             start, end,
                                             doAntiAlias);
                    }
                }
            }
        }
        else
        {
            { if (!(false)) HyperbolicUtils.set_exception("HyperbolicDrawUtils.java","DrawLine2fPoincare" , "false" + ""); };
        }
    }
}
