//***********************************************************
//*
//* File:           And1Player.java
//* Author:         Eric Li 
//*
//* Description:    The and1 cookie cutter
//*
//***********************************************************

//==================================================
// Evolution of Methods:
//      SimpleRectangularMethod
//      RotateRectangularMethod
//==================================================

package CookieCutter.g3;

import CookieCutter.*;
import java.io.Serializable;
import java.util.*;

public class Group3PlayerE implements IFCPlayer 
{
    // All cookie cutter properties start with cookie

    // Handle to the game
    CookieCutter          _cutter;

    // Some properties retrieved from the game
    Vertex[][]            _cookieShapes;
    int                   _cookieCopies;

    // Some misc vars
    static final String   _CNAME = "And1 Player";
    static Random         _random;

    // Some methods that are used
    Method meth1, meth2, meth3;

    public void register(CookieCutter __cookiecutter) throws Exception 
    {
        _cutter  = __cookiecutter;

        _cookieShapes = _cutter.cookieshapes();
        _cookieCopies  = _cutter.cookieCopies();

        meth1 = new CompactRotateRectangularMethod( _cutter );
        meth2 = new CompressionMethod( _cutter );
        meth3 = new PureForceMethod( _cutter );

    }

    public String name() throws Exception 
    {
        return _CNAME;
    }

    public boolean interactive() throws Exception 
    {
        return false;
    }

    public Move[] moves() throws Exception 
    {
        Move[] RET = new Move[_cookieShapes.length];

        if ( _cookieCopies <= 20 )
        {
            Move[] moves1 = meth1.moves();
            Move[] moves2 = meth2.moves();
            Move[] moves3 = meth3.moves();
    
            for ( int i = 0; i < _cookieShapes.length; i++ )
            {
                RET[i] = moves1[i];
                if ( moves2[i].GetRightBoundary() < RET[i].GetRightBoundary() )
                    RET[i] = moves2[i];
                if ( moves3[i].GetRightBoundary() < RET[i].GetRightBoundary() )
                    RET[i] = moves3[i];
            }
        }
        else
        {
            Move[] moves1 = meth1.moves();
            Move[] moves2 = meth2.moves();
    
            for ( int i = 0; i < _cookieShapes.length; i++ )
            {
                RET[i] = moves1[i];
                if ( moves2[i].GetRightBoundary() < RET[i].GetRightBoundary() )
                    RET[i] = moves2[i];
            }
            
        }

        return RET;
    }

}

// ==================================================
// Various method implementations
// ==================================================

abstract class Method 
{
    CookieCutter _cutter;

    Vertex[][]   _cookieShapes;
    int          _cookieCopies;

    public Method( CookieCutter _cookiecutter ) throws Exception
    {
        _cutter = _cookiecutter;
        _cookieShapes = _cutter.cookieshapes();
        _cookieCopies  = _cutter.cookieCopies();
    }

    abstract public Move[] moves() throws Exception;
}

class SimpleRectangularMethod extends Method
{

    public SimpleRectangularMethod( CookieCutter _cookiecutter ) throws Exception
    {
        super(_cookiecutter);
    }

    public Move[] moves() throws Exception
    {
        Move[] RET = new Move[_cookieShapes.length];


        for ( int i = 0; i < _cookieShapes.length; i++ )
        {
            RET[i] = new Move(_cookieCopies);

            double height = Lib.cookieHeight( _cookieShapes[i] ) + Lib.epsilon;
            double width = Lib.cookieWidth( _cookieShapes[i] ) + Lib.epsilon;

            int vFit = (int)(1.0 / height);

            for ( int j = 0; j < _cookieCopies; j++ )
            {
                double centerXcopy = ((j / vFit) + 0.5) * width;
                double centerYcopy = ((j % vFit) + 0.5) * height;
                double[] centroid = centerToCentroid(i, 0, centerXcopy, centerYcopy);
                double centroidX = centroid[0];
                double centroidY = centroid[1];

                RET[i].setCookiePosition( j, new Vertex( centroidX, centroidY ), 0 );
            }

            RET[i].setRightBoundary( width * ((_cookieCopies-1) / vFit + 1.0) );
        }
        
        return RET;    
    }

    double[] centerToCentroid( int shapeIndex, double angle, double X, double Y ) throws Exception
    {
        double[] RET = new double[2];

        Vertex[] rotated = Lib.rotateShape( _cookieShapes[shapeIndex], angle );

        Vertex centroid = _cutter.centroid( rotated );

        double height = Lib.cookieHeight( rotated );
        double width = Lib.cookieWidth( rotated );
        double centerX = width / 2.0;
        double centerY = height / 2.0;

        double xTranslation = centroid.xpos() - centerX;
        double yTranslation = centroid.ypos() - centerY;

        RET[0] = X + xTranslation;
        RET[1] = Y + yTranslation;

        return RET;
    }
}

class RotateRectangularMethod extends SimpleRectangularMethod 
{
    public RotateRectangularMethod( CookieCutter _cookiecutter ) throws Exception
    {
        super(_cookiecutter);
    }

    public Move[] moves() throws Exception
    {
        Move[] RET = new Move[_cookieShapes.length];

        for ( int i = 0; i < _cookieShapes.length; i++ )
        {
            RET[i] = new Move(_cookieCopies);

            double[][] samples = Lib.sampleRotations( _cookieShapes[i], 90 );
            int minIndex = findMin(samples, 2); // 2 is the index of the array element that contains the cookie width
            double angle = samples[minIndex][0];
            Vertex[] rotated = Lib.rotateShape( _cookieShapes[i], angle );

            double width = Lib.cookieWidth( rotated ) + Lib.epsilon;
            double height = Lib.cookieHeight( rotated ) + Lib.epsilon;

            // shift the newly rotated shape up against the upper left corner of the board
            Vertex centroid = Lib.upperLeftCentroid( rotated );
            centroid.setXpos( centroid.xpos() + (Lib.epsilon / 2) );
            centroid.setYpos( centroid.ypos() + (Lib.epsilon / 2) );

            int vFit = (int)(1.0 / height);
            vFit = vFit < 1 ? 1 : vFit;

            for ( int j = 0; j < _cookieCopies; j++ )
            {
                double X = (j / vFit) * width + centroid.xpos();
                double Y = (j % vFit) * height + centroid.ypos();

                RET[i].setCookiePosition( j, new Vertex( X, Y ), angle );
            }

            RET[i].setRightBoundary( width * ((_cookieCopies-1) / vFit + 1.0) );
        }
        
        return RET;    
    }


    // Returns the index of the record with the smallest nth value, each record having multiple values
    // Same as the findMin in Lib except it has a built in check to make sure that the cookie height is
    // less than 1.0 so that the cookie fits inside the bounding box
    // This method is used in conjunction with the sampleRotations method solely for this "Method"
    int findMin( double[][] data, int n )
    {
        double smallest = data[0][n];
        int index = 0;

        for ( int i = 0; i < data.length; i++ )
        {
            if ( data[i][n] < smallest && data[i][1] < (1.0 - Lib.epsilon) )
            {
                smallest = data[i][n];
                index = i;
            }
        }

        return index;
    }
    
}

class CompactRotateRectangularMethod extends RotateRectangularMethod
{

    public CompactRotateRectangularMethod( CookieCutter _cookiecutter ) throws Exception
    {
        super(_cookiecutter);
    }

    public Move[] moves() throws Exception
    {
        Move[] RET = new Move[_cookieShapes.length];

        // An array containing the centroids of all the cookie copies that have already been placed
        Vertex[] placed = null;

        for ( int i = 0; i < _cookieShapes.length; i++ )
        {
            RET[i] = new Move(_cookieCopies);

            placed = new Vertex[_cookieCopies];
            double rightMost = 0.0;

            double[][] samples = Lib.sampleRotations( _cookieShapes[i], 90 );
            int minIndex = findMin(samples, 2); // 2 is the index of the array element that contains the cookie width
            double angle = samples[minIndex][0];
            Vertex[] rotated = Lib.rotateShape( _cookieShapes[i], angle );

            double width = Lib.cookieWidth( rotated ) + Lib.epsilon;
            double height = Lib.cookieHeight( rotated ) + Lib.epsilon;

            // shift the newly rotated shape up against the upper left corner of the board
            Vertex centroid = Lib.upperLeftCentroid( rotated );

            // pad it
            centroid.setXpos( centroid.xpos() + (Lib.epsilon / 2) );
            centroid.setYpos( centroid.ypos() + (Lib.epsilon / 2) );

            int vFit = (int)(1.0 / height);
            vFit = vFit < 1 ? 1 : vFit;

            Vertex[] prevShape = null;

            for ( int j = 0; j < _cookieCopies; j++ )
            {
                double X = (j / vFit) * width + centroid.xpos();
                double Y = (j % vFit) * height + centroid.ypos();
                Vertex newCentroid = new Vertex(X, Y);

                // Try to compact the shapes by shifting to the left until overlap occurs
                double dx = -0.01; 

                if ( j > 0 )
                {
                    if ( j - vFit >= 0 )
                    {
                        prevShape = Lib.shiftedShape( _cookieShapes[i], placed[j-vFit], angle );
                    
                        while ( !Lib.overlap( prevShape, Lib.shiftedShape(_cookieShapes[i], new Vertex(newCentroid.xpos()+dx, newCentroid.ypos()), angle) ) )
                        {
                            newCentroid.setXpos( newCentroid.xpos() + dx );
                        }
                    }
                }

                RET[i].setCookiePosition( j, newCentroid, angle );
                placed[j] = newCentroid;

                double rightEdge = Lib.rightMost( Lib.shiftedShape(_cookieShapes[i], newCentroid, angle) ).xpos(); 
                if ( rightEdge > rightMost )
                {
                    rightMost = rightEdge;
                }
                
            }

            RET[i].setRightBoundary( rightMost + Lib.epsilon );
        }

        return RET;    
    }

}


class PureForceMethod extends Method
{
    public PureForceMethod( CookieCutter _cookiecutter ) throws Exception
    {
        super(_cookiecutter);
    }

    public Move[] moves() throws Exception
    {
        Move[] RET = new Move[_cookieShapes.length];

        // Keeps track of all previously placed shapes
        // 2nd index tells what piece of information is stored in that slot:
        // 1 = centroid x, 2 = centroid y, 0 = angle
        double[][] placed = null;
        Vertex[][] placedShapes = null;

        double dAngle = 3.0;
        double dx = 0.1;
        double dy = 0.1;

//        double rightBorder = 0.0;  // This limits how far we sample translations to the right

        for ( int i = 0; i < _cookieShapes.length; i++ )
        {
            RET[i] = new Move(_cookieCopies);

            placed = new double[_cookieCopies][3];
            placedShapes = new Vertex[_cookieCopies][];
            double rightMost = 0.0;
            double width = Lib.cookieWidth( _cookieShapes[i] );
            Vertex origCentroid = CookieCutter.centroid( _cookieShapes[i] );
            double centroidToTop = origCentroid.ypos() - Lib.upperMost(_cookieShapes[i]).ypos();
            double centroidToLeft = origCentroid.xpos() - Lib.leftMost(_cookieShapes[i]).xpos();
            double centroidToBottom = Lib.lowerMost(_cookieShapes[i]).ypos() - origCentroid.ypos();
            double centroidToRight = Lib.rightMost(_cookieShapes[i]).xpos() - origCentroid.xpos();
            origCentroid.setXpos( centroidToLeft + Lib.epsilon / 2 );
            origCentroid.setYpos( centroidToTop + Lib.epsilon / 2 );

            // Place the first copy
            RET[i].setCookiePosition( 0, origCentroid, 0 );

            double angle0 = 0.0;
            placed[0][0] = angle0;
            placed[0][1] = origCentroid.xpos();
            placed[0][2] = origCentroid.ypos();
            placedShapes[0] = Lib.shiftedShape( _cookieShapes[i], origCentroid, angle0 );

            double rightX = Lib.rightMost( Lib.shiftedShape( _cookieShapes[i], origCentroid, angle0 ) ).xpos();
            rightMost = (rightX > rightMost) ? rightX : rightMost;

            // Place the remaining copies
            for ( int j = 1; j < _cookieCopies; j++ )
            {

                int angleSamples = (int)(360.0 / dAngle);
                int xSamples = (int)((rightMost + width) / dx);
                int ySamples = (int)(1.0 / dy);
                
//                Vertex[][] rSamples = Lib.sampleRotationShapes( _cookieShapes[i], angleSamples );

                double[][] sampleData = new double[ angleSamples * xSamples * ySamples ][3];
                int dataSize = 0;

                for ( int u = 0; u < xSamples; u++ )
                {
                    double sampleX = u * dx;

                    Shifting:
                    for ( int v = 0; v < ySamples; v++ )
                    {
                        double sampleY = v * dy;

                        Vertex tmpCentroid = new Vertex( sampleX, sampleY );
            
                        for ( int z = j-1; z >= 0; z-- )
                        {
                            if ( Lib.insidePolygon( placedShapes[z], tmpCentroid ) )
                            {
                                continue Shifting;
                            }
                        }
                        
                        for ( int w = 0; w < angleSamples; w++ )
                        {
                            Vertex[] tmpShape = Lib.shiftedShape( _cookieShapes[i], tmpCentroid, w * dAngle );
                            boolean overlap = false;

                            Overlap:
                            for ( int z = j-1; z >= 0; z-- )
                            {
                                if ( Lib.outOfBounds( tmpShape ) || Lib.overlap( placedShapes[z], tmpShape ) )
                                {
                                    overlap = true;
                                    break Overlap;
                                }
                            }

                            if ( !overlap )
                            {
                                sampleData[dataSize][0] = w * dAngle;
                                sampleData[dataSize][1] = sampleX;
                                sampleData[dataSize][2] = sampleY;
                                dataSize++;
                            }
                        }
                    }
                }

                double X = sampleData[0][1];
                double Y = sampleData[0][2];
                double angle = sampleData[0][0];

                double xTolerance = 0.0;

                for ( int z = 0; z < dataSize; z++ )
                {
                    if ( sampleData[z][1] < X || ( Math.abs(sampleData[z][1]-X) < xTolerance && sampleData[z][2] < Y ) ) 
                    {
                        X = sampleData[z][1];
                        Y = sampleData[z][2];
                        angle = sampleData[z][0];
                    }
                }
                
                Vertex newCentroid = new Vertex(X, Y);
                RET[i].setCookiePosition( j, newCentroid, angle );
                placed[j][0] = angle;
                placed[j][1] = X;
                placed[j][2] = Y;
                placedShapes[j] = Lib.shiftedShape( _cookieShapes[i], newCentroid, angle );

                rightX = Lib.rightMost( Lib.shiftedShape( _cookieShapes[i], newCentroid, angle ) ).xpos();
                rightMost = (rightX > rightMost) ? rightX : rightMost;
            }

            RET[i].setRightBoundary( rightMost + Lib.epsilon );
        }
        
        return RET;
    }

}


//class RepulsionMethod extends Method
//{
//    public RepulsionMethod( CookieCutter _cookiecutter ) throws Exception
//    {
//        super(_cookiecutter);
//    }
//
//    public Move[] moves() throws Exception
//    {
//        Move[] RET = new Move[_cookieShapes.length];
//
//
//        for ( int i = 0; i < _cookieShapes.length; i++ )
//        {
//            
//            double width = Lib.cookieWidth(_cookieShapes[i]);
//            
//            for ( int j = 0; j < _cookieCopies; j++ )
//            {
//                
//            }
//        }
//
//        return RET;
//    }
//    
//}

class CompressionMethod extends Method
{

    public CompressionMethod( CookieCutter _cookiecutter ) throws Exception
    {
        super(_cookiecutter);
    }


    public Move[] moves() throws Exception
    {
        Move[] RET = new Move[_cookieShapes.length];

        Vertex[][] placedShapes = null;
        int placedCount = 0;

        for ( int i = 0; i < _cookieShapes.length; i++ )
        {
            RET[i] = new Move(_cookieCopies);

            // Some info about the current cookie shape
            Vertex origCentroid = CookieCutter.centroid(_cookieShapes[i]);
            double height = Lib.cookieHeight(_cookieShapes[i]);
            double width = Lib.cookieWidth(_cookieShapes[i]);
            double centroidToTop = origCentroid.ypos() - Lib.upperMost(_cookieShapes[i]).ypos();
            double centroidToLeft = origCentroid.xpos() - Lib.leftMost(_cookieShapes[i]).xpos();
            double centroidToBottom = Lib.lowerMost(_cookieShapes[i]).ypos() - origCentroid.ypos();
            double centroidToRight = Lib.rightMost(_cookieShapes[i]).xpos() - origCentroid.xpos();
            
            // Keep track of already placed shapes
            placedShapes = new Vertex[_cookieCopies][];
            placedCount = 0;

            // Keep track of right most point
            double rightMost = 0.0;
            
            // Place first copy
            origCentroid.setXpos(centroidToLeft + Lib.epsilon); 
            origCentroid.setYpos(centroidToTop + Lib.epsilon); 
            RET[i].setCookiePosition(0, origCentroid, 0);
            rightMost = Lib.rightMost(_cookieShapes[i]).xpos();
            placedShapes[0] = Lib.shiftedShape(_cookieShapes[i], origCentroid, 0);
            placedCount++;


            double dy = 0.005;

            // Place remaining copies
            for ( int j = 1; j < _cookieCopies; j++ )
            {
                double X = rightMost + width + Lib.epsilon;
                double centroidX = X + Lib.epsilon;
                double centroidY = centroidToTop + Lib.epsilon;
                double xDist = 0.0;

                for ( double Y = centroidToTop + Lib.epsilon; Y < 1.0 - centroidToBottom - Lib.epsilon * 2; Y = Y + dy )
                {
                    Vertex[] tmpShape = Lib.shiftedShape(_cookieShapes[i], new Vertex(X, Y), 0);
                    
                    // Find the distance from tmpShape to all the already placed shapes
                    double shapeDist = Lib.leftMost(tmpShape).xpos();
                    for ( int z = 0; z < placedCount; z++ )
                    {
                        double tmpDist = Lib.xDistShapeShape( placedShapes[z], tmpShape );
                        if ( tmpDist < shapeDist )
                        {
                            shapeDist = tmpDist;
                        }
                    }
                    
                    if ( shapeDist > xDist )
                    {
                        centroidX = X - shapeDist + 2 * Lib.epsilon;
                        centroidY = Y + Lib.epsilon;
                        xDist = shapeDist;
                    }
                    
                }
                
                RET[i].setCookiePosition(j, new Vertex(centroidX, centroidY), 0);
                Vertex[] placedShp = Lib.shiftedShape(_cookieShapes[i], new Vertex(centroidX, centroidY), 0);
                double rightBorder = Lib.rightMost(placedShp).xpos();
                if ( rightBorder > rightMost )
                {
                    rightMost = rightBorder;
                }
                placedShapes[j] = placedShp;
                placedCount++;
                
            }

            RET[i].setRightBoundary( rightMost + Lib.epsilon * 2);
        }

        return RET;
    }


}


// ==================================================
// Various utility methods
// ==================================================

class Lib
{
    static double epsilon = 0.000001;

    
    static Vertex leftMost( Vertex[] vertices ) throws Exception
    {
        if ( vertices != null )
        {
            Vertex RET = vertices[0];
            int length = vertices.length;
            for ( int i = 0; i < length; i++ )
            {
                if ( RET.xpos() > vertices[i].xpos() )
                {
                    RET = vertices[i];
                }
            }
            return RET;
        }
        else
            return null;
    }

    
    static Vertex rightMost( Vertex[] vertices ) throws Exception
    {
        if ( vertices != null )
        {
            Vertex RET = vertices[0];
            int length = vertices.length;
            for ( int i = 0; i < length; i++ )
            {
                if ( RET.xpos() < vertices[i].xpos() )
                {
                    RET = vertices[i];
                }
            }
            return RET;
        }
        else
            return null;
    }

    
    static Vertex upperMost( Vertex[] vertices ) throws Exception
    {
        if ( vertices != null )
        {
            Vertex RET = vertices[0];
            int length = vertices.length;
            for ( int i = 0; i < length; i++ )
            {
                if ( RET.ypos() > vertices[i].ypos() )
                {
                    RET = vertices[i];
                }
            }
            return RET;
        }
        else
            return null;
    }

    
    static Vertex lowerMost( Vertex[] vertices ) throws Exception
    {
        if ( vertices != null )
        {
            Vertex RET = vertices[0];
            int length = vertices.length;
            for ( int i = 0; i < length; i++ )
            {
                if ( RET.ypos() < vertices[i].ypos() )
                {
                    RET = vertices[i];
                }
            }
            return RET;
        }
        else
            return null;
    }

    
    static double cookieHeight( Vertex[] vertices ) throws Exception
    {
        if ( vertices != null )
        {
            return lowerMost(vertices).ypos() - upperMost(vertices).ypos();
        }
        else
            return 0;
    }

    static double cookieWidth( Vertex[] vertices ) throws Exception
    {
        if ( vertices != null )
        {
            return rightMost(vertices).xpos() - leftMost(vertices).xpos();
        }
        else
            return 0;
    }

    static Vertex rotateVertex( Vertex _origvert, Vertex origcenter, Vertex center, double angle ) throws Exception
    {
        double dx = _origvert.xpos() - origcenter.xpos();
        double dy = _origvert.ypos() - origcenter.ypos();
    
        double newdx = dx*Math.cos(angle) - dy*Math.sin(angle);
        double newdy = dx*Math.sin(angle) + dy*Math.cos(angle);
    
        return new Vertex(center.xpos() + newdx, center.ypos() + newdy);
    }

    // Returns the vertices of the shape after being rotated by angle degrees about the centroid
    static Vertex[] rotateShape( Vertex[] vertices, double angle ) throws Exception
    {
        Vertex[] RET = new Vertex[ vertices.length ];

        for ( int i = 0; i < vertices.length; i++ )
        {
            RET[i] = rotateVertex( vertices[i], CookieCutter.centroid(vertices), CookieCutter.centroid(vertices), angle );
        }

        return RET;
    }

    // Returns the index of the record with the smallest nth value, each record having multiple values
    static int findMin( double[][] data, int n )
    {
        double smallest = data[0][n];
        int index = 0;

        for ( int i = 0; i < data.length; i++ )
        {
            if ( data[i][n] < smallest )
            {
                smallest = data[i][n];
                index = i;
            }
        }

        return index;
    }

    // Takes a shape regardless of where it is and returns the centroid coordinates for that shape if it were
    // to be placed in the upper left corner of the board using its bounding rectangle
    static Vertex upperLeftCentroid( Vertex[] vertices ) throws Exception
    {
        Vertex RET = CookieCutter.centroid(vertices);
        double dx = 0.0 - leftMost(vertices).xpos();
        double dy = 0.0 - upperMost(vertices).ypos();

        RET.setXpos( RET.xpos() + dx );
        RET.setYpos( RET.ypos() + dy );

        return RET;
    }

    static boolean isValidPlacement( Vertex[][] shapes ) throws Exception
    {
        boolean RET = true;

        return RET;
    }

    static double overlapArea( Vertex[] shapeA, Vertex[] shapeB ) throws Exception
    {
        double RET = 0.0;

        return RET;
    }

    static boolean overlap( Vertex[] shapeA, Vertex[] shapeB ) throws Exception
    {
        boolean RET = false;

        // Check each line of shapeA against each line of shapeB for intersection
        Search:  
        for ( int i = 0; i < shapeA.length; i++ )
        {
            int ii = (i+1) == shapeA.length ? 0 : (i+1);

            for ( int j = 0; j < shapeB.length; j++ )
            {
                int jj = (j+1) == shapeB.length ? 0 : (j+1);

                if ( CookieCutter.intersects( shapeA[i], shapeA[ii], shapeB[j], shapeB[jj] ) )
                {
                    RET = true;
                    break Search;
                    
                }
            }
        }
        
        return RET;
    }

    static boolean outOfBounds( Vertex[] vertices ) throws Exception
    {
        boolean RET = false;

        for ( int i = 0; i < vertices.length; i++ )
        {
            if ( vertices[i].xpos() < 0 || vertices[i].ypos() < 0 || vertices[i].ypos() > 1 )
            {
                RET = true;
                break;
            }
        }

        return RET;
    }
    
    static Vertex[] shiftedShape( Vertex[] vertices, Vertex newCentroid, double angle ) throws Exception
    {
        Vertex[] RET = new Vertex[vertices.length];

        Vertex oldCentroid = CookieCutter.centroid( vertices );

        double dx = newCentroid.xpos() - oldCentroid.xpos();
        double dy = newCentroid.ypos() - oldCentroid.ypos();

        for ( int i = 0; i < vertices.length; i++ )
        {
            RET[i] = new Vertex( vertices[i].xpos() + dx, vertices[i].ypos() + dy );
        }

        if ( angle != 0 )
        {
            RET = rotateShape( RET, angle );
        }

        return RET;
    }

    // Take sampleSize samples of the given shape rotated 360 degrees about its centroid
    // and return the angle of rotation, height, and width of each sample
    static double[][] sampleRotations( Vertex[] vertices, int sampleSize ) throws Exception
    {
        double[][] RET = new double[sampleSize][];
        double interval = 360.0 / sampleSize;

        for ( int i = 0; i < sampleSize; i++ )
        {
            double angle = i * interval;
            Vertex[] rotated = rotateShape( vertices, angle );

            RET[i] = new double[3];
            RET[i][0] = angle;
            RET[i][1] = cookieHeight( rotated );
            RET[i][2] = cookieWidth( rotated );
        }

        return RET;
    }


    static Vertex[][] sampleRotationShapes( Vertex[] vertices, int sampleSize ) throws Exception
    {
        Vertex[][] RET = new Vertex[sampleSize][];
        double interval = 360.0 / sampleSize;

        for ( int i = 0; i < sampleSize; i++ )
        {
            double angle = i + interval;
            RET[i] = rotateShape( vertices, angle );
        }

        return RET;
    }


    static boolean insidePolygon( Vertex[] vertices, Vertex point ) throws Exception
    {
        boolean RET = false;

        int count = 0;

        for ( int i = 0; i < vertices.length; i++ )
        {
            int ii = ((i+1) == vertices.length) ? 0 : i+1;

            if ( CookieCutter.intersects( new Vertex(-1.0, point.ypos()), point, vertices[i], vertices[ii] ) )
            {
                count++;
            }
        }

        if ( count % 2 != 0 )
        {
            RET = true;
        }

        return RET;
    }

    // Finds the distance from a point to the point of intersection between a horizontal line
    // through that point and a specified line
    static double xDistPointLine( Vertex point, Vertex v1, Vertex v2 ) throws Exception
    {
        double RET = Double.POSITIVE_INFINITY;

        double xIntersect = 0.0;

        if ( !(point.ypos() > (v1.ypos()+epsilon) && point.ypos() > (v2.ypos()+epsilon)) &&
             !(point.ypos() < (v1.ypos()-epsilon) && point.ypos() < (v2.ypos()-epsilon)) )
        {
            xIntersect = (v1.xpos()-v2.xpos()) * (point.ypos()-v2.ypos()) / (v1.ypos()-v2.ypos()) + v2.xpos();
            RET = Math.abs(point.xpos() - xIntersect);
        }
        
        return RET;
    }

    // Find the shortest horizontal distance between a point and a shape
    static double xDistPointShape( Vertex point, Vertex[] shape ) throws Exception
    {
        double RET = Double.POSITIVE_INFINITY;

        if ( (point.ypos() >= upperMost(shape).ypos()) &&
             (point.ypos() <= lowerMost(shape).ypos()) )
        {
            for ( int i = 0; i < shape.length; i++ )
            {
                int ii = (i+1) == shape.length ? 0 : (i+1);
                double dist = xDistPointLine( point, shape[i], shape[ii] );
                if ( dist < RET )
                {
                    RET = dist;
                }
            }
        }

        return RET;
    }

    // Find the shortest horizontal distance between two shapes
    // assuming shapes A and B do not overlap 
    static double xDistShapeShape( Vertex[] shapeA, Vertex[] shapeB ) throws Exception
    {
        double RET = Double.POSITIVE_INFINITY;

        if ( !(lowerMost(shapeA).ypos() < upperMost(shapeB).ypos()) &&
             !(upperMost(shapeA).ypos() > lowerMost(shapeB).ypos()) )
        {
            for ( int i = 0; i < shapeA.length; i++ )
            {
                double dist = xDistPointShape( shapeA[i], shapeB );
                if ( dist < RET )
                {
                    RET = dist;
                }
            }
            for ( int j = 0; j < shapeB.length; j++ )
            {
                double dist = xDistPointShape( shapeB[j], shapeA );
                if ( dist < RET )
                {
                    RET = dist;
                }
            }
        }

        return RET;
    }
    
}




