import java.io.*;
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.net.URL;
import java.text.*;
import java.util.Vector;
import java.util.Map;
import java.util.Hashtable;
import java.util.jar.Attributes;

// Module 1 Exercise 4
public class Mod3Plot extends Panel implements AdjustmentListener, 
                                               ActionListener {

    private static final long serialVersionUID = 352387;

    private static int NMAX = 5000;  //was (NMAX=10000)
    private static int xscale = 3;   // number of real pixels per "pixel"
    private static int yscale = 3;   // number of real pixels per "pixel"
    private static int numberPixelsPerGrid = 3;  // was 6
    private double nmPerPixel;  // nano meters per pixel
    private double pixelsPerNm;  // pixels per nano meter
    private double pixelsPerNmOver4piepsilon;
    private double gridsPerNmOver4piepsilon;

    int ncharges;  // # of charges in 2D area
    int xpos[], ypos[];  // positions of charges, in nm
    double charge[];  // signed charge of each charge, nColoumbs
    double elemcharge[];  // signed charge of each charge as mult of elem charg
    Point pixelPt[];  // actual pixel point (NOT grid point)
    boolean absorbed[]; // flag for each charge - ignore if true 

    Vector<Charge> chargeVector = new Vector<Charge>();

    int numEquiLines;
    //private static final int EQUI_LINES_INIT = 15;
    //7;  // was 15
    private static final int EQUI_LINES_INIT = 16;   //15-7 X 2

    // the chosen ranges for the plot: (1) is the min, (2) is the max
    //  and (3) is delta for large tics
    double xrange[] = new double[3];
    double yrange[] = new double[3];

    int nxgrid , nygrid;
    double potential[][];
    Vector<ChargeDensityInfo> chDensity;
    double chDensityMax;  // abs value
    static double epsilonOverDeltaY;
    double potentialMax, potentialMin;

    int max_number_charges = NMAX;

    Mod3State state;
    Image im;
    Graphics imG;
    Graphics2D g2d;

    int mainWidth = 630;  // 619
    int mainHeight = 576;
    //plotPanel.setBounds(12,58,635,581);

  int yMin, yMax, yMid;
  int xMin, xMax, xMid;
    int yBorder, yOffset, xBorder, xOffset;
    DecimalFormat dec = new DecimalFormat("###.#");
    DecimalFormat dec2 = new DecimalFormat(".##");
    DecimalFormat dec3 = new DecimalFormat("#.##");
    DecimalFormat dec4 = new DecimalFormat("0.#E0");  //"0.###E0"

    //static Color bgColor = new Color(194,235,255);
    private final Color bgColor = new Color(236,236,236);
    private final Color darkerOrange = new Color(255,100,0);
    private final Color lightBlue = new Color(200,200,255);

    double deltaY, deltaX;
    FontMetrics fm;
    int fontHeight,ticLength;

    public Button small, swap;

    public TextField textRadius;
    private Scrollbar sliderRadius;
    private boolean radiusChanged = false;
    private int ScrollMax = 30;
    private boolean ignoreNextTextEvent = false;

    private double sphereRadius, radius;
    static double piOver180 = Math.PI/180.0;
    //static double deltaThetaScale = 4.0*50.0;  // 4 degrees per 50 pixel radius
    static double deltaThetaScale = 10.0*50.0;  // 10 degrees per 50 pixel radius
    static double deltaThetaScale2 = 30.0*50.0;  // 20 degrees per 50 pixel radius
    double deltaTheta, deltaTheta2;

    private Font chDFonts[] = new Font[5];
    private FontMetrics chDfms[] = new FontMetrics[5];
    private int chDWidths[] = new int[5];

    public static String swapLabel1 = "  View  Image Charges ";
    public static String swapLabel2 = "Return to original view ";


    public Mod3Plot(Mod3State state) {
        super();
        this.state = state;
    }

    public void initialPlot() {
        setLayout(null);
        setBackground(bgColor);
        //im = createImage(mainWidth,mainHeight);
        //imG = im.getGraphics();

        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gs = ge.getDefaultScreenDevice(); 
        GraphicsConfiguration gc = gs.getDefaultConfiguration();
        im = gc.createCompatibleImage(mainWidth, mainHeight, Transparency.TRANSLUCENT);
        imG = im.getGraphics();

        small = new Button("Instructions");
        add(small);
	small.setBackground(Color.white);
	//small.setBounds(7,7,80,20);
	small.setBounds(540,7,80,20);
        small.setFont(TheFonts.sanSerif11);

        swap = new Button(swapLabel1);
        add(swap);
        swap.setBackground(Color.white);
        swap.setBounds(35,7,140,20);
        swap.setFont(TheFonts.sanSerif11);

        //size = numberPixelsPerGrid;    // grid size (?) OR max abscharge value?
        numEquiLines = EQUI_LINES_INIT;

        // where to draw on the screen:
        //plotPanel.setBounds(12,58,494,480);
        yOffset = 10;
        yBorder = 20;
        xOffset = 10;
        xBorder = 20;

        xMin = xBorder+xOffset;
        //xMax = 438;    // for 9.5 nm
        xMax = 589;    // for 9 nm
        xMid = (int)(0.5*(xMax+xMin));
        yMin = yBorder+yOffset; 
        yMax = 546;

        yMid = (int)(0.5*(yMax+yMin));

        deltaY = (yMax - yMin)/12.0;  // these are both the same
        deltaX = deltaY;

        g2d = (Graphics2D)imG;
        //g2d.scale(1.11,1.0);  // b/c vertical pixels larger than horizontal

        nmPerPixel = 1.0/deltaX;   // 1 nanometer per deltaX and deltaY
        pixelsPerNm = deltaX/1.0;   
        pixelsPerNmOver4piepsilon = pixelsPerNm/(4*Math.PI*8.85E-12*1.0E-9);
        gridsPerNmOver4piepsilon = pixelsPerNmOver4piepsilon/
            numberPixelsPerGrid;
        epsilonOverDeltaY = 8.85E-12/(numberPixelsPerGrid*nmPerPixel) *
            1E9;  // last number to go from nm to m

        //ngrid = Math.min(mainWidth-(int)xoff,mainHeight-(int)yoff) /
        //    numberPixelsPerGrid;

        // grid has 12 nm in y direction and 13 nm in x direction:
        nygrid = (int)(12*pixelsPerNm / numberPixelsPerGrid);
        nxgrid = (int)(13*pixelsPerNm / numberPixelsPerGrid);

        potential = new double[nxgrid][nygrid];
        xrange[0] = xMin;
        xrange[1] = xMax;
        xrange[2] = deltaX;  // spacing for axis tic marks
        yrange[0] = yMin;
        yrange[1] = yMax;
        yrange[2] = deltaY;  // spacing for axis tic marks

        //sphereRadius = 50; // initialValue;
        sphereRadius = 100; // initialValue;
        //deltaTheta = 4.0*piOver180;
        //deltaTheta = 10.0*piOver180;  // Nov 15
        deltaTheta = deltaThetaScale*piOver180/sphereRadius;
        deltaTheta2 = deltaThetaScale2*piOver180/sphereRadius;

        radius = rounder(sphereRadius*nmPerPixel,1);

        textRadius = new TextField(3);
        textRadius.setFont(state.ttfFont.deriveFont(14f));
	textRadius.setText(""+dec3.format(radius));

        sliderRadius = new Scrollbar(Scrollbar.VERTICAL,0,1,0,ScrollMax+1);
        sliderRadius.setValue((int)(ScrollMax - 
                                    (ScrollMax*(radius-state.radiusMin)
                                     /(state.radiusMax-state.radiusMin)) ));
        add(textRadius);
        add(sliderRadius);

        textRadius.setBounds(300,2,60,20);
        //sliderRadius.setBounds((int)((xMax+20)*1.1),75,15,75);
        //sliderRadius.setBounds(xMax+30,75,15,75);
        sliderRadius.setBounds(xMax+20,75,15,75);

        textRadius.addActionListener(this);
        sliderRadius.addAdjustmentListener(this);

        // Get font sizes:
        //setFont(TheFonts.bold16);
        setFont(TheFonts.sanSerif12);  // for axis labels
        fm = getFontMetrics(getFont());
        fontHeight = fm.getHeight();
        ticLength  = fm.stringWidth("w") - 4; // just a wide letter.


        //chDFonts[0] = TheFonts.sanSerif9;
        chDFonts[0] = TheFonts.sanSerif10;
        chDFonts[1] = TheFonts.sanSerif12;
        chDFonts[2] = TheFonts.sanSerif14;
        chDFonts[3] = TheFonts.sanSerif16;
        chDFonts[4] = TheFonts.sanSerif18;
        for (int i = 0; i < 5; i++) {
            chDfms[i] = getFontMetrics(chDFonts[i]);
            chDWidths[i] = chDfms[i].stringWidth("+");
        }
                

        paintImage();
        repaint();
  }



    public void adjustmentValueChanged(AdjustmentEvent evt){//NEED HERE
        if (evt.getSource()==sliderRadius) {
            if(ScrollMax-sliderRadius.getValue()>=ScrollMax){
                radius=state.radiusMax;
                //sliderRadius.setValue((int)((radius-state.radiusMin)*
                //                       ScrollMax/(state.radiusMax-state.radiusMin)));
                sliderRadius.setValue(0);
	    } else {
		radius = state.radiusMin + 
		    (state.radiusMax-state.radiusMin)*
                    (ScrollMax-sliderRadius.getValue())/ScrollMax;
                radius = rounder(radius,2);
	    }
	    textRadius.setText(""+dec3.format(radius));
            ignoreNextTextEvent = true;
            sphereRadius = radius/nmPerPixel;
            radiusChanged = true;
            checkChargeAbsorption();
            drawPlot();
        } 
    }


    private double rounder(double dval, int places) {
        int multiplier = 1;
        for (int i=0; i<places ; i++)
            multiplier *= 10;
        int value = (int)(dval*multiplier);
        double newDval = 1.0*value/multiplier;
        return newDval;
    }


    public void readTextUpdateSlider() {
        if (ignoreNextTextEvent) {
            ignoreNextTextEvent = false;
            return;
        }
        radiusChanged = true;
        try { if(Double.valueOf(textRadius.getText()).doubleValue()<=state.radiusMax &&
                 Double.valueOf(textRadius.getText()).doubleValue()>=state.radiusMin){
                radius=Double.valueOf(textRadius.getText()).doubleValue();
                sliderRadius.setValue((int)(ScrollMax - 
                                            (ScrollMax*(radius-state.radiusMin)
                                             /(state.radiusMax-state.radiusMin)) ));
            } else if(Double.valueOf(textRadius.getText()).doubleValue()>state.radiusMax){
                  radius = state.radiusMax;
                  textRadius.setText(""+dec3.format(state.radiusMax));
                  sliderRadius.setValue(0);
              }
              else if(Double.valueOf(textRadius.getText()).doubleValue()<state.radiusMin){
                  radius = state.radiusMin;
                  textRadius.setText(""+dec3.format(state.radiusMin));
                  sliderRadius.setValue(ScrollMax);
              }
        } catch(NumberFormatException e){
            radius =  0.0;
        }
        sphereRadius = radius/nmPerPixel;
        checkChargeAbsorption();
        drawPlot();
    }


    public void actionPerformed(ActionEvent evt){
        if (evt.getSource()==textRadius) readTextUpdateSlider();
        return;
    }


    private void checkChargeAbsorption() {
        Charge c, c2;
        for (int i = 0; i < chargeVector.size(); i++) {
            c = chargeVector.elementAt(i);


            /*
            System.out.println(" 2 charge = "+String.valueOf(c.elemcharge)+
                               " reflected = "+String.valueOf(c.reflectedCharge)+
                               " absorbed = "+String.valueOf(c.absorbed)+
                               " sphereRadius = "+
                               String.valueOf(c.sphereRadius));
            */

            if (ptInsideSphere(c.pixelPt) && !c.reflectedCharge) {
                c2 = findReflectedChargeOf(c);
                c.absorbed = true;
                c.sphereRadius = sphereRadius;
                if (c2 == null) continue;
                chargeVector.removeElement(c2);
                //c2 = createReflectedCharge(c);
                //c2.absorbed = true;
                //chargeVector.addElement(c2);
            } else if (!ptInsideSphere(c.pixelPt) && !c.reflectedCharge) {
                c2 = findReflectedChargeOf(c);
                c.absorbed = false;
                c.sphereRadius = sphereRadius;

                //if (c2 == null) continue;
                //chargeVector.removeElement(c2);
                if (c2 != null) chargeVector.removeElement(c2);

                c2 = createReflectedCharge(c);
                c2.absorbed = false;
                chargeVector.addElement(c2);
            }
        }
        deltaTheta = deltaThetaScale*piOver180/sphereRadius;
        deltaTheta2 = deltaThetaScale2*piOver180/sphereRadius;

        /*
        for (int i = 0; i < chargeVector.size(); i++) {
            c = (Charge) chargeVector.elementAt(i);

            System.out.println(" 3 charge = "+String.valueOf(c.elemcharge)+
                               " reflected = "+String.valueOf(c.reflectedCharge)+
                               " absorbed = "+String.valueOf(c.absorbed)+
                               " sphereRadius = "+
                               String.valueOf(c.sphereRadius));
        }
        */
    }



    // want sliderVal to go from 15 to 1
    public boolean changeDeltaV(int sliderVal) {
        int newNumEquiLines;
        //newNumEquiLines = (int) ((sliderVal+1) * 1.5);
        newNumEquiLines = (sliderVal+1) * 2;
        if (newNumEquiLines != numEquiLines) {
            numEquiLines = newNumEquiLines;
            return true;
        }
        return false;
    }



    /*
    public boolean changeDeltaV(int sliderVal) {
        int newNumEquiLines;
        int i = sliderVal - 10;
        if (i == 0) newNumEquiLines = EQUI_LINES_INIT;
        //else if (i > 0) newNumEquiLines = (int)(EQUI_LINES_INIT*1.2*i);
        //else newNumEquiLines = (int) (EQUI_LINES_INIT/(1.2*Math.abs(i)));
        else newNumEquiLines = (int) (EQUI_LINES_INIT - (1.5*i));
        if (newNumEquiLines != numEquiLines) {
            numEquiLines = newNumEquiLines;
            return true;
        }
        return false;
    }
    */


    public void getVoltageAtCursor(Point pt) {
        // convert pt from pixels to "places" on this big plot
        // get voltage AND electric field at this place
        //pt.x/=1.11;
        if (state.currentPlotType == state.plotWithGroundSphere &&
            ptInsideSphere(pt)) {
            state.voltageAtCursor = 0.0;
            state.efieldAtCursor = 0.0;
            state.efieldThetaAtCursor = 0.0;
            if (ptNearSphere(pt))
                state.chargeDensityAtCursor = getChargeDensityAtPoint(pt);
            else
                state.chargeDensityAtCursor = 0.0;
        } else {
            Point gridPt = getGridPointFromPixelPoint(pt);
            state.voltageAtCursor = getPotentialValueAtPoint(gridPt);
            //state.efieldAtCursor = getEfieldValueAtPoint(gridPt);
            ActualVector efield = getEfieldValueAtPoint(gridPt);
            state.efieldAtCursor = efield.ee;
            state.efieldThetaAtCursor = efield.theta;
            if (ptNearSphere(pt))
                state.chargeDensityAtCursor = getChargeDensityAtPoint(pt);
            else
                state.chargeDensityAtCursor = 0.0;
        }
        return;
    }

    public boolean moveCharge() {
        // convert state.moveFromPoint and state.moveToPoint to
        // the "places" (grids) on this big plot

        if (ptInsideSphere(state.moveToPoint)) {
            state.moveToPoint = getSphereCrossingPoint(state.moveFromPoint);
            return false;
        }
        if (state.moveFromPoint == state.moveToPoint)
            return false;

        //if (ptInsideMovingSphere(state.moveFromPoint) ||
        //    ptInsideMovingSphere(state.moveToPoint)) return false; // invalid command
        Point fromGridPt, toGridPt, fromGridPt2, toGridPt2;
        /*
        Point fromPt = new Point((int)(1.0*state.moveFromPoint.x/1.11),
                                 state.moveFromPoint.y);
        Point toPt = new Point((int)(1.0*state.moveToPoint.x/1.11),
                               state.moveToPoint.y);
        */

        Point fromPt = new Point(state.moveFromPoint.x,state.moveFromPoint.y);
        Point toPt = new Point(state.moveToPoint.x,state.moveToPoint.y);

        fromGridPt = getGridPointFromPixelPoint(fromPt);
        toGridPt = getGridPointFromPixelPoint(toPt);
        Charge c1 = findClosestCharge(fromGridPt);
        if (c1 == null) return false;
        Charge c2 = findReflectedChargeOf(c1);
        if (c2 == null) return false;  // SHOULD NEVER HAPPEN!
        chargeVector.removeElement(c1);
        chargeVector.removeElement(c2);
        //c1.pixelPt = new Point((int)(state.moveToPoint.x/1.11),
        //                       state.moveToPoint.y);
        c1.pixelPt = new Point(state.moveToPoint.x,state.moveToPoint.y);
        c1.xpos = toGridPt.x;  c1.ypos = toGridPt.y;
        c2 = createReflectedCharge(c1);
        chargeVector.addElement(c1);
        chargeVector.addElement(c2);
        //doDebug2();
        return true;  // move successful
    }




    private Point getSphereCrossingPoint(Point pt) {
        double deltaX, deltaY, theta, dx, dy;
        int x, y;
        deltaX = pt.x - xMid;
        deltaY = yMid - pt.y;
        theta = Math.atan2(deltaY,deltaX);
        dx = sphereRadius*Math.cos(theta);
        dy = sphereRadius*Math.sin(theta);
        x = (int)(deltaX - dx);
        y = (int)(dy - deltaY);
        x += xMid;
        y = yMid - y;
        return new Point(x,y);
    }



    private void doDebug2() {
        for (int i = 0; i < chargeVector.size(); i++) {
            Charge c = chargeVector.elementAt(i);

            System.out.println(" 1 charge = "+String.valueOf(c.elemcharge)+
                               " reflected = "+String.valueOf(c.reflectedCharge)+
                               " absorbed = "+String.valueOf(c.absorbed)+
                               " sphereRadius = "+
                               String.valueOf(c.sphereRadius));
            /*
            System.out.println("charge.xpos = "+String.valueOf(c.xpos)+
                               " charge.ypos = "+String.valueOf(c.ypos)+
                               " charge.pixelPt.x = "+
                               String.valueOf(c.pixelPt.x) +
                               " charge.pixelPt.y = "+
                               String.valueOf(c.pixelPt.y));
            */
        }
    }



    private Charge findClosestCharge(Point pt) {
        Charge c, cFound;
        int x, y, sumdiff, diff;
        sumdiff = 1000;
        cFound = null;
        for (int i = 0; i < chargeVector.size(); i++) {
            c =  chargeVector.elementAt(i);
            if (c.absorbed || c.reflectedCharge) continue;
            x = c.xpos;  y = c.ypos;
            if (withinReasonableRange(x,y,pt.x,pt.y)) {
                diff = Math.abs(x-pt.x) + Math.abs(y-pt.y);
                if (diff < sumdiff) {
                    cFound = c;
                    sumdiff = diff;
                }
            }
        }
        return cFound;
    }


    private boolean withinReasonableRange(int x1, int y1, int x2, int y2) {
        // NOTE: assumed that these coordinates are for GRIDS not PIXELS
        if (x2 >= (x1 - 3) && x2 <= (x1 + 3))
            if (y2 >= (y1 - 3) && y2 <= (y1 + 3)) return true;
        return false;
    }


    public void reset() {
        numEquiLines = EQUI_LINES_INIT;
        chargeVector = new Vector<Charge>();
        state.moveFromPoint = new Point(-1,-1);
        state.moveToPoint = new Point(-1,-1);
        //imG.clearRect(0,0,mainWidth,mainHeight);
        imG.setColor(bgColor);
        imG.fillRect(0,0,mainWidth,mainHeight);
    }

    public void placeCharge(Point pt) {
        // convert pt from pixels to "places" on this big plot
        // place the current charge at this "place" AND also the
        // relected charge at the appropriate spot in the sphere.
        //pt.x /= 1.11;
        if (ptInsideSphere(pt)) return; // invalid command
        Point gridPt;
        gridPt = getGridPointFromPixelPoint(pt);
        Charge c = new Charge();
        c.xpos = gridPt.x;   c.ypos = gridPt.y;
        c.charge = state.chargeToAdd*state.elementaryCharge;
        c.elemcharge = state.chargeToAdd;
        c.pixelPt = pt; c.absorbed = false;  c.reflectedCharge = false;
        c.sphereRadius = sphereRadius;
        chargeVector.addElement(c);
        Charge c2 = createReflectedCharge(c);
        chargeVector.addElement(c2);

        //imG.setColor(Color.black);
        //imG.fillOval(pt.x-3,pt.y-3,7,7);
        //imG.fillOval(pt2.x-3,pt2.y-3,7,7);
        //repaint();

        //doDebug2();
        return;
    }

    public void removeCharge(Point pt) {
        // convert pt from pixels to "places" on this big plot
        // remove charge (or more? if a slider ...) at this "place"
        //pt.x /= 1.11;
        if (ptInsideSphere(pt)) return; // invalid command
        Point gridPt, gridPt2;
        gridPt = getGridPointFromPixelPoint(pt);
        // note: ALSO, null will be returned if reflected or absorbed (is only
        //  one(s) close enough)
        Charge c1 = findClosestCharge(gridPt);
        if (c1 == null) return;
        chargeVector.removeElement(c1);
        Charge c2 = findReflectedChargeOf(c1);
        if (c2 != null)
            chargeVector.removeElement(c2);
        return;
    }


    public void changeCharge(Point pt) {
        // convert pt from pixels to "places" on this big plot
        // remove charge (or more? if a slider ...) at this "place"
        //pt.x /= 1.11;
        if (ptInsideSphere(pt)) return; // invalid command
        Point gridPt, gridPt2;
        gridPt = getGridPointFromPixelPoint(pt);
        // note: ALSO, null will be returned if reflected or absorbed (is only
        //  one(s) close enough)
        Charge c1 = findClosestCharge(gridPt);
        if (c1 == null) return;
        Charge c2 = findReflectedChargeOf(c1);
        if (c2 == null) return;
        c1.charge = state.chargeToAdd * state.elementaryCharge;
        c1.elemcharge = state.chargeToAdd;
        c2 = findReflectedChargeOf(c1);
        Charge c1Refl = createReflectedCharge(c1);  // get NEW refl ch values
        c2.charge = c1Refl.charge;
        c2.elemcharge = c1Refl.elemcharge;
        return;
    }


    private boolean ptInsideSphere(Point pt) {
        int x = pt.x - xMid;
        int y = yMid - pt.y;
        if (Math.sqrt(x*x+y*y) <= sphereRadius)
            return true;
        return false;
    }


    private boolean ptNearSphere(Point pt) {
        int x = pt.x - xMid;
        int y = yMid - pt.y;
        double r = Math.sqrt(x*x+y*y);
        if (r <= (sphereRadius + 5) && r >= (sphereRadius - 5))
            return true;
        return false;
    }


    private boolean ptNearSphereAtAll(Point pt) {
        int x = pt.x - xMid;
        int y = yMid - pt.y;
        double r = Math.sqrt(x*x+y*y);
        if (r <= (sphereRadius + 20))
            return true;
        return false;
    }


    private Charge findReflectedChargeOf(Charge c1) {
        Charge c;
        int x2, y2;
        int deltaX = c1.pixelPt.x - xMid;
        int deltaY = yMid - c1.pixelPt.y;
        double dist = Math.sqrt(deltaX*deltaX + deltaY*deltaY);
        double radOverDist = c1.sphereRadius/dist;
        double radOverDistSq = radOverDist*radOverDist;
        x2 = (int)(deltaX*radOverDistSq) + xMid;
        y2 = yMid - (int)(deltaY*radOverDistSq);
        //double elemcharge2 = -1.0*c1.charge*radOverDist;
        for (int i = 0; i < chargeVector.size(); i++) {
            c = chargeVector.elementAt(i);
            if (c.reflectedCharge)
                if (c.pixelPt.x == x2 && c.pixelPt.y == y2)
                    return c;
        }
        return (Charge)null;
    }


    private Charge createReflectedCharge(Charge c1) {
        double dist;
        int deltaX, deltaY;
        deltaX = c1.pixelPt.x - xMid;
        deltaY = yMid - c1.pixelPt.y;
        Charge c2 = new Charge();
        dist = Math.sqrt(deltaX*deltaX + deltaY*deltaY);
        double radOverDist = c1.sphereRadius/dist;
        double radOverDistSq = radOverDist*radOverDist;
        c2.charge = -1.0*c1.charge*radOverDist;
        c2.elemcharge = -1.0*c1.elemcharge*radOverDist;
        //int x = (int)(c1.pixelPt.x*radOverDistSq);
        //int y = (int)(c1.pixelPt.y*radOverDistSq);
        int x = (int)(deltaX*radOverDistSq);
        int y = (int)(deltaY*radOverDistSq);
        c2.pixelPt = new Point(x+xMid,yMid-y);
        Point gridPt = getGridPointFromPixelPoint(c2.pixelPt);
        c2.xpos = gridPt.x;
        c2.ypos = gridPt.y;
        c2.absorbed = false;
        c2.reflectedCharge = true;
        c2.sphereRadius = c1.sphereRadius;
        return c2;
    }
        

    private Point getGridPointFromPixelPoint(Point pt) {
        Point newPt = new Point(0,0);
        newPt.x = (int) ((pt.x - xrange[0])/numberPixelsPerGrid);
        newPt.y = (int) ((pt.y - yrange[0])/numberPixelsPerGrid);
        if (newPt.x >= nxgrid) newPt.x = nxgrid-1;
        else if (newPt.x < 0) newPt.x = 0;
        if (newPt.y >= nygrid) newPt.y = nygrid-1;
        else if (newPt.y < 0) newPt.y = 0;
        return newPt;
    }

    private Point getPixelPointFromGridPoint(Point pt) {
        Point newPt = new Point(0,0);
        newPt.x = (int)((pt.x*numberPixelsPerGrid)+xrange[0]);
        newPt.y = (int)((pt.y*numberPixelsPerGrid)+yrange[0]);
        return newPt;
    }

    private double getPotentialValueAtPoint(Point pt) {
        // pt already a grid pt, NOT a pixel point
        return potential[pt.x][pt.y];
    }

    private ActualVector getEfieldValueAtPoint(Point pt) {
        double exi, eyi, ex, ey, ee, ri, riSquared, ei;
        double vmax = state.vmax;
        int x = pt.x;
        int y = pt.y;
        ex = 0.0;  ey = 0.0;
        double arbitraryMax = 50.0; // to simulate infinity
        for (int k = 0; k < ncharges; k++) {
            if (x == xpos[k] && y == ypos[k]) {
                if (charge[k] >= 0.0) {exi = vmax; eyi = vmax;}
                else {exi = -1.0*vmax; eyi = -1.0*vmax;}
            } else {
                riSquared = square(x-xpos[k])+square(y-ypos[k]);
                ei = charge[k]/riSquared;
                ri = Math.sqrt(riSquared);
                exi = ((x-xpos[k])/ri) * ei;
                eyi = ((y-ypos[k])/ri) * ei;
            }
            
            if (Math.abs(exi) < vmax)
                ex = ex + exi;
            else {
                if (exi > 0.0) 
                    ex = vmax;
                else ex = -1.0*vmax;
            }
            if (Math.abs(eyi) < vmax)
                ey = ey + eyi;
            else {
                if (eyi > 0.0) 
                    ey = vmax;
                else ey = -1.0*vmax;
            }
        }
        ex *= gridsPerNmOver4piepsilon;
        ey *= gridsPerNmOver4piepsilon;
        //if (Math.abs(ex) > state.efieldMax) {
        if (Math.abs(ex) > arbitraryMax) {
            if (ex > 0.0) ex = arbitraryMax;
            else ex = -1.0*arbitraryMax;
        }
        //if (Math.abs(ey) > state.efieldMax) {
        if (Math.abs(ey) > arbitraryMax) {
            if (ey > 0.0) ey = arbitraryMax;
            else ey = -1.0*arbitraryMax;
        }
        ee = Math.sqrt(square(ex) + square(ey));
        //System.out.println("ee = "+String.valueOf(ee));
        //return ee;
        double theta = Math.atan2(-ey,ex);
        ActualVector efield = new ActualVector(ex, ey, ee,theta);
        return efield;
    }





    private double getChargeDensityAtPoint(Point pt) {
        // pt is a pixel point
        double theta = Math.atan2((yMid-pt.y),(pt.x-xMid));
        double x, y;
        x = sphereRadius*Math.cos(theta) + xMid;
        y = yMid - sphereRadius*Math.sin(theta);
        Point point = new Point((int)x,(int)y);
        Point gridPt = getGridPointFromPixelPoint(point);
        ActualVector efield = getEfieldValueAtPoint(gridPt);
        double exValue = efield.ex;
        double eyValue = -efield.ey;   // NOTE!
        double chargeDensity = epsilonOverDeltaY*
            (exValue*Math.cos(theta) + eyValue*Math.sin(theta));
        return chargeDensity;
    }



    public synchronized void drawPlot() {
        //CALL draw_labelled_axes(ncharges, xpos, ypos, xrange, yrange,
        //&     outfile,xoff, yoff, size)
        Object obj;

        if (state.currentAction == state.placeCharge ||
            state.currentAction == state.removeCharge ||
            state.currentAction == state.moveCharge ||
            state.currentAction == state.changeChargeValue ||
            radiusChanged) {

            ncharges = chargeVector.size();
            xpos = new int[ncharges];
            ypos = new int[ncharges];
            charge = new double[ncharges];
            elemcharge = new double[ncharges];
            pixelPt = new Point[ncharges];
            absorbed = new boolean[ncharges];
            Charge c;
            for (int i=0; i<ncharges; i++) {
                c = chargeVector.elementAt(i);
                xpos[i] = c.xpos;
                ypos[i] = c.ypos;
                pixelPt[i] = c.pixelPt;
                charge[i] = c.charge;
                elemcharge[i] = c.elemcharge;
                absorbed[i] = c.absorbed;
            }
        }

        //comment out for now; debug one at a time:
        //imG.clearRect(0,0,mainWidth,mainHeight);

        imG.setColor(bgColor);
        imG.fillRect(xMin,yMin,xMax-xMin,yMax-yMin);

        if (state.currentPlotType == state.plotWithoutGroundSphere) {
            imG.setColor(lightBlue);
            imG.fillOval((int)(xMid-sphereRadius),(int)(yMid-sphereRadius),
                         (int)(2*sphereRadius),(int)(2*sphereRadius));
        }



        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON);

        if (state.currentAction == state.placeCharge ||
            state.currentAction == state.removeCharge ||
            state.currentAction == state.moveCharge ||
            state.currentAction == state.changeChargeValue ||
            radiusChanged) {
            calculate_potential_field();
            radiusChanged = false;
        }
        if (state.displayPotentialField) 
            draw_potential_field();
        if (state.displayEquiPotentialLines) 
            draw_isopotential_lines();


        // NEW: Nov 15
        if (state.currentPlotType == state.plotWithGroundSphere) {
            // draw the ground sphere
            imG.setColor(Color.gray.brighter());
            imG.fillOval((int)(xMid-sphereRadius),(int)(yMid-sphereRadius),
                         (int)(2*sphereRadius),(int)(2*sphereRadius));
            imG.setColor(Color.black);
            //if (state.displayChargeDensity) plotChargeDensity();
        }


        if (state.displayElectricField) {
            draw_electricfield_vectors();
            draw_ef_vectors_onSphere();
        }
        draw_charges();

        //if (state.currentPlotType == state.plotWithGroundSphere &&
        //    state.displayChargeDensity)
        if (state.displayChargeDensity) {
            calculateChargeDensity();
            plotChargeDensity();  // NEW: Nov. 15
        }
        paintImage();
        repaint();
    }


    private void calculate_potential_field() {
        double x, y;
        int i, j, k;
        double v, vi;
        //double vmax = 50;
        //double vmax = 10;
        //double vmax = 6*state.elementaryCharge;
        double vmax = state.vmax;

        potentialMax = -10000.0;
        potentialMin = 10000.0;

        for (i = 0; i < nxgrid; i++) {
            x = i;
            for (j = 0; j < nygrid; j++) {
                y = j;
                v = 0.0;
                for (k = 0; k < ncharges; k++) {
                    if (absorbed[k]) continue;  // IGNORE THIS CHARGE
                    if (x == xpos[k] && y == ypos[k]) {
                        //if (charge[k] >= 0.0) vi = vmax;
                        //else vi = -1.0*vmax;
                        vi = charge[k];
                    } else {
                        vi = charge[k]/Math.sqrt(square(x-xpos[k])+
                                                 square(y-ypos[k]));
                    }
                    
                    if (Math.abs(vi) < vmax)
                        v = v + vi;
                    else {
                        if (vi > 0.0) 
                            v = vmax;
                        else v = -1.0*vmax;
                    }
               }
                v *= gridsPerNmOver4piepsilon;
                if (Math.abs(v) > state.potentialMax) {
                  if (v > 0.0) v = state.potentialMax;
                  else v = -1.0*state.potentialMax;
                }
                potential[i][j] = v;
                if (potential[i][j] > potentialMax)
                    potentialMax = potential[i][j];
                if (potential[i][j] < potentialMin)
                    potentialMin = potential[i][j];
            }
        }

        return;
    }

    
    
    private void draw_potential_field() {
        int i, j;
        double colorValue;
        int index;
        Color color;

        Graphics2D g2d = (Graphics2D)imG;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_OFF);
        
        // NOTE: potential is -100 to +100
        for (i=0; i<nxgrid; i++)
            for (j=0; j<nygrid; j++)
                if (potential[i][j] >= 0.0) {
                    index = (int)(10*potential[i][j]);
                    colorValue = state.lookup[index];
                    /* green
                    color = new Color(1-(float)(colorValue),
                                      (float)1.0,
                                      1-(float)(colorValue));
                    */

                    color = new Color(1-(float)(colorValue),
                                      1-(float)(colorValue),
                                      (float)1.0);

                    imG.setColor(color);
                    g2d.fillRect((int)xrange[0]+i*numberPixelsPerGrid,
                                 (int)yrange[0]+j*numberPixelsPerGrid,
                                 numberPixelsPerGrid,
                                 numberPixelsPerGrid);
                } else {
                    index = (int)(-10.0*potential[i][j]);
                    colorValue = state.lookup[index];
                    color = new Color((float)1.0,
                                      1-(float)(colorValue),
                                      1-(float)(colorValue));
                    imG.setColor(color);
                    imG.fillRect((int)xrange[0]+i*numberPixelsPerGrid,
                                 (int)yrange[0]+j*numberPixelsPerGrid,
                                 numberPixelsPerGrid,
                                 numberPixelsPerGrid);
                }
        
        return;
    }
        
        
        
    private void draw_isopotential_lines() {
        double deltav;
        double x[] = new double[1000];
        double y[] = new double[1000];
        double rmax, rmin;
        int i, j;
        double contours[] = new double[1000];
        int ncontours;
        Color lineColor = Color.orange;

        
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON);


        for (i=0; i<nxgrid; i++)
            //x[i] = xrange[0] + i*dx;
            x[i] = i;
        for (j=0; j<nygrid; j++)
            //y[j] = yrange[0] + j*dy;
            y[j] = j;

        rmax = potentialMax;
        rmin = potentialMin;
        deltav = (rmax - rmin)/(numEquiLines-1);
        //rmax = potential[0][0];
        //rmin = rmax;
        //for (i=0; i<nxgrid; i++)
        //    for (j=0; j<nygrid; j++) {
        //        if (potential[i][j] > rmax) rmax = potential[i][j];
        //        if (potential[i][j] < rmin) rmin = potential[i][j];
        //    }

        ncontours = 0;
        contours[ncontours] = 0.0;
        while (contours[ncontours] < rmax && ncontours < 1000) {
            ncontours++;
            if (ncontours < 1000)
                contours[ncontours] = contours[ncontours-1]+deltav;
        }
        if (contours[ncontours] <= rmax) ncontours++;

        if (ncontours < 1000)
            contours[ncontours] = contours[0]-deltav;
        while (contours[ncontours] > rmin && ncontours < 1000) {
            ncontours++;
            if (ncontours < 1000)
                contours[ncontours] = contours[ncontours-1]-deltav;
        }
        if (contours[ncontours] < rmin) ncontours--;

        // redo contour values to be logarithmic rather than linear
        int index;
        double zeroToOne;
        double scaler1 = 1000/rmax;
        double scaler2 = 1000/rmin;
        for (i=0; i<=ncontours; i++) {
            if (contours[i] > 0) {
                index = (int)(1000 - contours[i]*scaler1);
                zeroToOne = state.lookup[index];
                contours[i] = (1.0-zeroToOne)*rmax;
            } else {
                index = (int)(1000 - contours[i]*scaler2);
                zeroToOne = state.lookup[index];
                contours[i] = (1.0-zeroToOne)*rmin;
            }
        }
        
        plot_contours(x,y,contours,ncontours);
        return;
    }



    private void draw_electricfield_vectors() {
        //int stride = 15;
        int stride = 10;
        double x, y;
        int i, j, k;
        int ii,jj, len1,len2;
        double ex, ey, exi, eyi;
        double riSquared, ri, ei;
        double vmax = state.vmax;
        double efieldx[][], efieldy[][];
        double Emax;

        
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON);

        len1 = nxgrid/stride +1;
        len2 = nygrid/stride +1;
        efieldx = new double[len1][len2];
        efieldy = new double[len1][len2];

        Emax = 0.0;
        ii = 0;
        for (i = 3; i < nxgrid && ii<len1; i+=stride) {
            x = i;
            jj = 0;
            for (j = 3; j < nygrid && jj<len2; j+=stride) {
                y = j;
                ex = 0.0;  ey = 0.0;
                for (k = 0; k < ncharges; k++) {
                    if (x == xpos[k] && y == ypos[k]) {
                        if (charge[k] >= 0.0) {exi = vmax; eyi = vmax;}
                        else {exi = -1.0*vmax; eyi = -1.0*vmax;}
                    } else {
                        riSquared = square(x-xpos[k])+square(y-ypos[k]);
                        ei = charge[k]/riSquared;
                        ri = Math.sqrt(riSquared);
                        exi = ((x-xpos[k])/ri) * ei;
                        eyi = ((y-ypos[k])/ri) * ei;
                    }
                    
                    if (Math.abs(exi) < vmax)
                        ex = ex + exi;
                    else {
                        if (exi > 0.0) 
                            ex = vmax;
                        else ex = -1.0*vmax;
                    }
                    if (Math.abs(eyi) < vmax)
                        ey = ey + eyi;
                    else {
                        if (eyi > 0.0) 
                            ey = vmax;
                        else ey = -1.0*vmax;
                    }
                }
                ex *= gridsPerNmOver4piepsilon;
                ey *= gridsPerNmOver4piepsilon;
                if (Math.abs(ex) > state.efieldMax) {
                    if (ex > 0.0) ex = state.efieldMax;
                    else ex = -1.0*state.efieldMax;
                }
                if (Math.abs(ey) > state.efieldMax) {
                    if (ey > 0.0) ey = state.efieldMax;
                    else ey = -1.0*state.efieldMax;
                }
                efieldx[ii][jj] = ex;
                efieldy[ii][jj] = ey;

                if (Math.abs(efieldx[ii][jj]) > Emax)
                    Emax = Math.abs(efieldx[ii][jj]);
                if (Math.abs(efieldy[ii][jj]) > Emax)
                    Emax = Math.abs(efieldy[ii][jj]);
                
                jj++;
            }
            ii++;
        }

        double from[] = new double[2];
        double to[] = new double[2];
        double invEmax;
        double ee;
        int index;
        double colorValue;
        double color[] = new double[3];
        double invnxgrid, invnygrid;
        double linewidth;

        //linewidth=15.0;
        //linewidth=7.0;
        linewidth=10.0;
        invnxgrid = 1.0/nxgrid;
        invnygrid = 1.0/nxgrid;

        invEmax = 1.0/Emax;

        /*
        from[0] = -20+8; // 40X40 box to display the arrow
        to[0] = 20-8;    // line is -12 to +12 long
        */
        /*
        from[0] = -10+4; // 20X20 box to display the arrow
        to[0] = 10-4;    // line is -6 to +6 long
        */
        from[0] = -12+4; // 20X20 box to display the arrow
        to[0] = 12-4;    // line is -8 to +8 long


        from[1] = 0;     // y coordinates; will rotate arrow later
        to[1] = 0;

        Point pixelPt;
        double theta;
        ii = 0;
        for (i=3; i<nxgrid && ii < len1; i+=stride) {
            jj = 0;
            for (j=3; j<nygrid && jj < len2; j+=stride) {
                pixelPt = getPixelPointFromGridPoint(new Point(i,j));


                // NEW: Nov 15
                if (ptNearSphereAtAll(pixelPt)) {
		    jj++;
		    continue;
		}



                theta = Math.atan2(-efieldy[ii][jj],efieldx[ii][jj]);
                
                // color is deeper blue if near EMax, white if near zero
                //ee = 0.5*invEmax*Math.sqrt(square(efieldx[ii][jj]) + 
                //                       square(efieldy[ii][jj]));
                //index = (int)(1000.0*ee);
                //colorValue = state.lookup[index];
                //color[0] = 1.0-colorValue;
                //color[1] = 1.0-colorValue;
                //color[2] = 1.0;
        
                //if (ee > 1.0E-10)
                //    plot_arrowline_local(linewidth,color,from,to,theta,pixelPt);
                plot_arrowline_local(linewidth,from,to,theta,pixelPt);
                jj++;
            }
            ii++;
        }
        return;
    }




    private void draw_ef_vectors_onSphere() {
        Point pt;
        int numberIterations = (int) (piOver180*360.0/deltaTheta2) + 1;
        double x, y;
        Point gridPt;
        double exValue, eyValue, etheta;
        double theta = 0.0;
        ActualVector efield;
        //imG.setColor(Color.magenta);
        double xComponent, yComponent;


        // NEW: Nov. 15
        double from[] = new double[2];
        double to[] = new double[2];
        double linewidth;
        linewidth=10.0;

        from[0] = -12+4; // 20X20 box to display the arrow
        to[0] = 12-4;    // line is -8 to +8 long

        from[1] = 0;     // y coordinates; will rotate arrow later
        to[1] = 0;

        double pi2 = Math.PI*2;


        for (int i=0; i<numberIterations; i++) {
            x = sphereRadius*Math.cos(theta) + xMid;
            y = yMid - sphereRadius*Math.sin(theta);
            pt = new Point((int)x,(int)y);

            //imG.fillOval(pt.x-1,pt.y-1,3,3);

            gridPt = getGridPointFromPixelPoint(pt);
            efield = getEfieldValueAtPoint(gridPt);
            exValue = efield.ex;
            eyValue = -efield.ey;   // NOTE!


            // NOW DRAW THE VECTOR:
            double xcomp = pt.x - xMid;
            double ycomp = yMid - pt.y;
            double val = exValue*xcomp + eyValue*ycomp;
            if (val > 0) etheta = theta;    // points out
            else etheta = theta - Math.PI;  // points in
            plot_arrowline_local(linewidth,from,to,etheta,pt);            

            theta += deltaTheta2;
        }


        //repaint();

        return;
    }




    private void draw_charges() {
        int scale;
        String params = new String();
        double location[] = new double[2];
        String string = new String();
        double rmax;
        int i;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON);
        
        rmax = xrange[1]-xrange[0];
        if (yrange[1]-yrange[0] > rmax) rmax = yrange[1]-yrange[0];

        scale = numberPixelsPerGrid;

        for (i=0; i<ncharges; i++) {
            if (absorbed[i]) continue;  // IGNORE THIS CHARGE
            Point pt = pixelPt[i];

            if (state.currentPlotType == state.plotWithGroundSphere && 
                ptInsideSphere(pt)) continue;  // don't label pts inside sphere

            if (!ptInsideSphere(pt))
                imG.setColor(Color.orange);
            else
                imG.setColor(darkerOrange);
            imG.fillOval(pt.x-6,pt.y-6,13,13);
            imG.setColor(Color.black);
            imG.drawOval(pt.x-6,pt.y-6,13,13);

            if (TheFonts.PC_OS) {
                if (elemcharge[i] >= 0.0) 
                    imG.drawString("+",pt.x-3,pt.y+7);
                else
                    imG.drawString("-",pt.x-3,pt.y+5);
            } else {
                
                if (elemcharge[i] >= 0.0) 
                    imG.drawString("+",pt.x-5,pt.y+5);
                else
                    imG.drawString("-",pt.x-4,pt.y+5);
            }

            if (Math.abs(elemcharge[i]) >= 0.1)
                imG.drawString(dec3.format(Math.abs(elemcharge[i])),
                               pt.x+9,pt.y+5);
            else
                imG.drawString(dec4.format(Math.abs(elemcharge[i])),
                               pt.x+9,pt.y+5);

        }
        return;
    }


    
    
    //private void plot_arrowline_local(double linewidth, double color[],
    //                                  double from[], double to[],
    //                                  double theta, Point pixelPt) {
    private void plot_arrowline_local(double linewidth, 
                                      double from[], double to[],
                                      double theta, Point pixelPt) {
        // color(3)          !  0=none, 1=lots
        // both these coordinates are in inches from lower left corner:
        // from(2)           !  x,y coords in inches
        // to(2)             !  x,y coords in inches

        // Draws a straight line from 'from' to 'to' (x,y coords in inches),
        // and puts an arrowhead on the 'to' end.
        // uses linewidth to scale the linewidth and the arrowhead.

        double x[] = new double[7];
        double y[] = new double[7];
        double dto[] = new double[2];

        double L, lw, hw;
        // lw is linewidth in inches
        // hw is (headwidth-1)/2
            
        int i;
        double headwidth;    // multiple of linewidth

        /*
        //calculate the outline of the arrow-line and arrow-head
        // then just fill it in.
        //
        //        y-axis
        //         /|\                         3
        //          |                          |\
        //          |                          | \
        //          |                          |  \
        //          1--------------------------2   \
        //          |                               \
        //          |                                \                  \
        // -------------------------------------------4------------------
        //          |                                /           x-axis /
        //          |                               /
        //          7--------------------------6   /
        //          |                          |  /
        //          |                          | /
        //          |                          |/
        //          |                          5
        //          |
        //          |
        //  points along the path are numbered.
        //
        //-------------------------------------------------------------
        */

        /*
        linewidth=3.0;
        headwidth=5.0;
        */

        /*   didn't look right even for arrow length = 7
        linewidth=1.5;
        headwidth=2.5;
        */

        linewidth=2;
        headwidth=4;


        lw = linewidth;    //linewidth in pixels
        hw = (headwidth-1.0)*0.5;

        // angle of the arrowhead:
        dto[0] = to[0] - from[0];
        dto[1] = to[1] - from[1];
        //theta = Math.atan2(dto[1],dto[0]);

        // calc the arrowhead starting at 0,0, and going to L,0
        L = Math.sqrt(square(dto[0]) + square(dto[1]));

        x[0] = 0.0;
        y[0] = 0.5*lw;
        x[1] = L-(headwidth-1.0)*lw;
        y[1] = y[0];
        x[2] = x[1];
        y[2] = y[1]+hw*lw*0.6;
        x[3] = L;
        y[3] = 0.0;
        // mirror the top to the bottom:
        x[4] = x[2];
        y[4] = -1.0*y[2];
        x[5] = x[1];
        y[5] = -1.0*y[1];
        x[6] = x[0];
        y[6] = -1.8*y[0];
        
        //original:
        //rotate_polyline(x,y,theta,12.0,1.5); // half of xlen and half of lw
        //translate_polyline(x,y,pixelPt.x-12,pixelPt.y);

        // lep:
        double lepxcent= 0.5*L;
        double lepycent= 0.0;
        rotate_polyline(x,y,theta,lepxcent,lepycent);
        translate_polyline(x,y,pixelPt.x-lepxcent,pixelPt.y-lepycent);

        // Draw the arrow (fill the polygon):
        //imG.setColor(new Color((float)color[0],(float)color[1],(float)color[2]));
        //imG.setColor(Color.blue);
        imG.setColor(Color.orange.darker());
        int ix[] = new int[x.length];
        int iy[] = new int[y.length];
        for (i=0; i<x.length; i++) {
            ix[i] = (int)x[i];
            iy[i] = (int)y[i];
        }
        imG.fillPolygon(ix,iy, x.length);
        // Outline the arrow:
        //imG.setColor(Color.black);
        //imG.drawPolygon(ix,iy, x.length);

        return;
    }


    private void rotate_polyline(double x[], double y[], double theta,
                                 double xcent, double ycent) {
        //  x(n),y(n) are coordinates of each point
        //  theta is the amount to rotate points counterclockwise, radians
        // xcent, ycent  is the center of rotation
        double xx, yy;
        int i;
        for (i=0; i<x.length; i++) {
            xx = (x[i]-xcent)*Math.cos(theta) - (ycent-y[i])*Math.sin(theta);
            yy = (x[i]-xcent)*Math.sin(theta) + (ycent-y[i])*Math.cos(theta);
            x[i] = xx+xcent;
            y[i] = ycent-yy;
        }
        return;
    }

    
    private void translate_polyline(double x[], double y[], 
                                    double dx, double dy) {
        // x(n),y(n) are coordinates of each point
        // dx,dy     are amount to translate points
        
        int i;
        for (i=0; i<x.length; i++) {
            x[i] = x[i] + dx;
            y[i] = y[i] + dy;
            //y[i] = dy - y[i];
        }
        return;
    }


  public void paintImage() {
      g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                           RenderingHints.VALUE_ANTIALIAS_ON);

      // clear the borders:
      //imG.clearRect(0,0,xMin,mainHeight);
      //imG.clearRect(0,0,mainWidth,yMin);
      //imG.clearRect(0,yMax,mainWidth,mainHeight-yMax);
      //imG.clearRect(xMax,0,mainWidth-xMax,mainHeight);

      imG.setColor(bgColor);
      imG.fillRect(0,0,xMin,mainHeight);
      imG.fillRect(0,0,mainWidth,yMin);
      imG.fillRect(0,yMax,mainWidth,mainHeight-yMax);
      imG.fillRect(xMax,0,mainWidth-xMax,mainHeight);

      if (state.currentPlotType == state.plotWithGroundSphere &&
          ncharges == 0) {

          // draw the ground sphere
          imG.setColor(Color.gray.brighter());
          imG.fillOval((int)(xMid-sphereRadius),(int)(yMid-sphereRadius),
                       (int)(2*sphereRadius),(int)(2*sphereRadius));
          imG.setColor(Color.black);
          //if (state.displayChargeDensity) plotChargeDensity();  Nov. 15
      } else {
          //  mirrored charges will show up inside (invisible) sphere
          //imG.setColor(Color.gray.brighter().brighter());
          //imG.drawOval((int)(xMid-sphereRadius),(int)(yMid-sphereRadius),
          //             (int)(2*sphereRadius),(int)(2*sphereRadius));
          //imG.setColor(Color.black);

          //if (state.displayChargeDensity) plotChargeDensity();
      }


      imG.setColor(Color.black);
      imG.setFont(TheFonts.sanSerif14);
      String s = "r";
      //int endingX = STR.displayString(s+" = ",imG,143,18);
      int endingX = STR.displayString(s+" = ",imG,258,18);
      //endingX = STR.displayString(s,imG,428,60);
      endingX = STR.displayString(s,imG,613,60);
      //xMax = 589;    // for 9 nm

      drawAxis(imG);
      drawAxisTicks(imG);
  }


    private void calculateChargeDensity() {
        Point pt;
        //int numberIterations = (int)(360.0/(deltaThetaScale/sphereRadius))+1;
        int numberIterations = (int) (piOver180*360.0/deltaTheta) + 1;
        double x[] = new double[numberIterations];
        double y[] = new double[numberIterations];
        Point gridPt;
        ChargeDensityInfo chD;
        double chargeDensity;
        double exValue, eyValue;
        chDensity = new Vector<ChargeDensityInfo>();
        chDensityMax = 0.0;
        double theta = 0.0;
        ActualVector efield;
        //imG.setColor(Color.magenta);
        double xComponent, yComponent;
        for (int i=0; i<x.length; i++) {
            x[i] = sphereRadius*Math.cos(theta) + xMid;
            y[i] = yMid - sphereRadius*Math.sin(theta);
            pt = new Point((int)x[i],(int)y[i]);

            //imG.fillOval(pt.x-1,pt.y-1,3,3);

            gridPt = getGridPointFromPixelPoint(pt);
            efield = getEfieldValueAtPoint(gridPt);
            exValue = efield.ex;
            eyValue = -efield.ey;   // NOTE!

            chargeDensity = epsilonOverDeltaY*
                (exValue*Math.cos(theta) + eyValue*Math.sin(theta));
            chD = new ChargeDensityInfo(pt.x, pt.y, chargeDensity, gridPt,
                                        theta);
            chDensity.addElement(chD);
            if (Math.abs(chargeDensity) > chDensityMax)
                chDensityMax = Math.abs(chargeDensity);
            theta += deltaTheta;
        }


        //repaint();

        return;
    }



    private void plotChargeDensity() {
        if (chDensity == null) return;
        if (chDensity.size() == 0) return;
        ChargeDensityInfo chD;
        Point pt;
        int x, y;
        double theta;

        double chDLevels[] = new double[5];
        double scale = chDensityMax/5.0;
        double level = 0;
        int index;
        Graphics2D g2d = (Graphics2D)imG;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON);
        for (int i=0; i<5; i++) {
            chDLevels[i] = level;
            level += scale;
        }

        double xScaler, yScaler;
        imG.setColor(Color.red);
        for (int i=0; i<chDensity.size(); i++) {
            chD = chDensity.elementAt(i);
            x = chD.x;  y = chD.y;

            theta = chD.theta;
            index = getIndexForChDensity(chD.chargeDensity,chDLevels);
            imG.setFont(chDFonts[index]);

            //x += chDWidths[index]*Math.cos(theta);
            //y -= chDWidths[index]*Math.sin(theta);
            xScaler = Math.cos(theta);
            yScaler = Math.sin(theta);
            if (xScaler < 0)
                x += chDWidths[index]*xScaler;
            if (yScaler < 0)
                y -= chDWidths[index]*yScaler;

            if (chD.chargeDensity > 0)
                imG.drawString("+",x,y);
            else
                imG.drawString("-",x,y);
        }
        imG.setColor(Color.black);
    }


    private int getIndexForChDensity(double val, double levels[]) {
        double n = Math.abs(val);
        int i;
        for (i = 0; i < levels.length - 1; i++)
            if (n > levels[i] && n < levels[i+1]) break;
        return i;
    }


    /*
    private void OLDplotChargeDensity() {
        if (chDensity == null) return;
        if (chDensity.size() == 0) return;
        ChargeDensityInfo chD;
        Point pt;

        double theta;
        int x[] = new int[chDensity.size()];
        int y[] = new int[chDensity.size()];
        double scaleFactor = 40/chDensityMax;
        double chargeDensity;
        for (int i=0; i<chDensity.size(); i++) {
            chD = (ChargeDensityInfo) chDensity.elementAt(i);
            chargeDensity = chD.chargeDensity*scaleFactor;
            theta = chD.theta;
            //System.out.println("chrgD= "+dec.format(chargeDensity)+
            //                   " theta= "+dec.format(theta)+
            //                   " sin(theta)= "+dec.format(Math.sin(theta)));
            x[i] = chD.x + (int)(chargeDensity*Math.cos(theta));
            y[i] = chD.y - (int)(chargeDensity*Math.sin(theta));
        }
        Graphics2D g2d = (Graphics2D)imG;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON);
        imG.setColor(Color.orange);
        //drawPolyline(g,x,y,x.length);
        drawPolyline(g2d,x,y,x.length);

        
        // Labelled axis
        imG.setColor(Color.black);
        int radius = (int)sphereRadius;
        drawLine(imG,xMid,yMid-radius,xMid,yMid-radius-50);
        imG.drawLine(xMid-4,yMid-radius-40,xMid+4,yMid-radius-40);
        if (radius > 40) {
            drawLine(imG,xMid,yMid-radius+40,xMid,yMid-radius);
            imG.drawLine(xMid-4,yMid-(radius-40),xMid+4,yMid-(radius-40));
        }

        g2d.setColor(Color.black);
        //g2d.setPaint(Color.lightGray.brighter());
        g2d.setPaint(Color.black);
        g2d.setStroke(new BasicStroke((float)0.25));
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON);
        Arc2D.Double arc = new Arc2D.Double(xMid-(radius+40)+1,
                                                  yMid-(radius+40)+1,
                                                  2*(radius+40),2*(radius+40),
                                                  0,360,0);
        g2d.draw(arc);
        if (radius > 40) {
            arc = new Arc2D.Double(xMid-(radius-40)+1,yMid-(radius-40)+1,
                                   2*(radius-40),2*(radius-40),0,360,0);
            g2d.draw(arc);
        }
        g2d.setStroke(new BasicStroke(1));



        imG.setFont(TheFonts.sanSerif11);
        String s = "\u03c1";
        int endingX = STR.displayString(s,imG,xMid+2,yMid-(radius+60));

        s = dec2.format(chDensityMax);
        endingX = STR.displayString(s,imG,xMid+6,yMid-(radius+35));
        s = dec2.format(-1.0*chDensityMax);
        if (radius > 40)
            endingX = STR.displayString(s,imG,xMid+6,yMid-(radius-40));
        endingX = STR.displayString("0",imG,xMid+6,yMid-radius);

        s = "C";
        endingX = STR.displayString(s,imG,xMid+16,yMid-(radius+65));
        imG.drawLine(xMid+14,yMid-(radius+60),xMid+24,yMid-(radius+60));
        s = "m"+STR.SUP+"2"+STR.ENDSUP;
        endingX = STR.displayString(s,imG,xMid+14,yMid-(radius+50));
    }
    */



  private void drawAxis(Graphics g) {
      g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);

    // Draw x and y axis:
    g.setColor(Color.black);
    drawLine(g,xMin,yOffset,xMin,yMax); // the y-axis
    drawLine(g,xMin,yMax,xMax+xBorder,yMax); // the x-axis
    MyArrows.drawDblUpArrow(g,xMin,yOffset);
    MyArrows.drawDblRightArrow(g,xMax+xBorder,yMax);
  }




  private void drawAxisTicks(Graphics g) {
      g2d = (Graphics2D)g;
      g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);

      // Draw x and y axis:
      g2d.setColor(Color.black);
      g2d.setFont(TheFonts.sanSerif12);

      int x, y;
      // the y-axis:
      for(int i=0;i<13;i++){
          //y = (int)(i*deltaY)+yOffset+yBorder;
          y = (int)(i*deltaY)+yMin;
          g2d.drawLine(xMin-ticLength,y,xMin,y);
          String vs = dec.format(12-i);
          g2d.drawString(vs,xMin-ticLength-fm.stringWidth(vs)-2,
                       y+(int)(0.5*fontHeight));
      }
      //g.drawString("y",xMin+ticLength,(int)(1.5*fontHeight)+yOffset-10);
      //g.drawString("nm",xOffset-2,(int)(1.5*fontHeight)+yOffset-12);
      //g.drawString("y,nm",0,(int)(fontHeight-5));
      g.drawString("y(nm)",0,fontHeight-5);
      

      // On the x-axis:
      for(int i=1;i<14;i++){
          x = (int)(i*deltaX)+xMin;
          g2d.drawLine(x,yMax+ticLength,x,yMax);
          String xs = dec.format(i);
          g2d.drawString(xs,x-(int)(0.5*fm.stringWidth(xs)),
                       yMax+ticLength+fontHeight-2);
      }
      //g.drawString("x",xMax-fm.stringWidth("x"),yMax-2);
      //g.drawString("nm",xMax+fm.stringWidth("nm"),yMax+ticLength+fontHeight-2);
      //g.drawString("x,nm",xMax+fm.stringWidth("m"),yMax+ticLength+fontHeight-2);
      g.drawString(" x(nm)",xMax,yMax+ticLength+fontHeight+6);

      g2d.setFont(TheFonts.bold16);      
      return;
  }


  public void update(Graphics g) { paint(g); }


  public void paint(Graphics g) {
    g.drawImage(im,0,0,this);
  }


  private void drawLine(Graphics g, int x1, int y1, int x2, int y2) {
      if (y1 != y2) {
          g.drawLine(x1,y1,x2,y2);
          g.drawLine(x1+1,y1,x2+1,y2);
      } else {
          g.drawLine(x1,y1,x2,y2);
          g.drawLine(x1,y1+1,x2,y2+1);
      }
  }

    private void drawPolyline(Graphics g, int[] xpts, int[] ypts, int len) {
        int x1, y1, x2, y2;
        for (int i = 0; i < (len-1); i++) {
            x1 = xpts[i]; y1 = ypts[i];
            x2 = xpts[i+1]; y2 = ypts[i+1];
            drawLine(g,x1,y1,x2,y2);
        }
    }
    


    // Draws isolines of the given 2D function at the given function values.
    // Each of the contour lines is made up of a number of segments,
    // each of which goes from one edge of a "pixel" to another.
    // The pixels are defined as the edges of the grid defined by {\tt xvec} 
    //  and {\tt yvec}.  (NOTE: a "pixel" is really multiple pixels - scaled)

    // REAL z(nx*ny) ! arranged: (iy-1)*nx + ix
    // Above no longer true; now using potential[nx][ny]
    public void plot_contours(double xvec[], double yvec[],
                              double contours[], int ncontours) {
        int i, j, jj;
        int L;
        double xdenom, ydenom;
        double xmin1, xmax1, ymin1, ymax1;
        //double xscale, yscale;
        int jstart, jend;

        double zvalue;
        int n;
        double x[];
        double y[];

        int nx = nxgrid;
        int ny = nygrid;
        double xmin = xrange[0];
        double xmax = xrange[1];
        double ymin = yrange[0];
        double ymax = yrange[1];

        int ilep=0;

        //xscale = p_plotsize_inches(1);
        //yscale = p_plotsize_inches(2);

        imG.setColor(Color.black);

        // scale the input vectors
        //xdenom = xmax - xmin;
        //ydenom = ymax - ymin;
        xdenom = nxgrid;
        ydenom = nygrid;
        
        for (i = 0; i < nx; i++) {
            xvec[i] = xvec[i] / (xdenom - 1);  // scale from 0 to 1
        }
        for (i = 0; i < ny; i++)
            yvec[i] = yvec[i] / (ydenom - 1);  // scale from 0 to 1

        for (i=0; i<=ncontours; i++) {
            zvalue = contours[i];
            ContourReturn c = get_contour_line(xvec,yvec,nx,ny,zvalue);
            x = c.getX();
            y = c.getY();
            n = c.n;
            
            //xscale = Math.min(mainWidth-(int)xoff,mainHeight-(int)yoff);
            //yscale = xscale;
            xscale = (int)(xrange[1]-xrange[0]);
            yscale = (int)(yrange[1]-yrange[0]);

            for (j=0; j<x.length-1; j+=2) {
                imG.drawLine((int)(x[j]*xscale+xrange[0]),
                             (int)(y[j]*yscale+yrange[0]),
                             (int)(x[j+1]*xscale+xrange[0]),
                             (int)(y[j+1]*yscale+yrange[0]));
            }
        }
        
        //  Now unscale the input vectors:
        for (i = 0; i < nx; i++)
            xvec[i] *= xdenom;
        for (i = 0; i < ny; i++)
            yvec[i] *= ydenom;
        return;
    }


    // Find all line segments in a contour line.
    // USES:
    //     get_segments
    // CALLED FROM:
    //     plot_contours
    
    // Returns x,y coords of iso-z-line specified by the given zvalue.
    // Also returns number of points in this line: n.
    /*  The line is actually many segments: moveto/lineto.
        Since there could be more than one "connected line segment"
        for any zvalue, we just return these segments.
        Figuring out how the segments are arranged into
        connected line segments is not attempted.  */

    private ContourReturn get_contour_line(double xvec[], double yvec[],
                                           int nx, int ny, double zvalue) {
        int i, j, k;
        int nmax;
        double xs[] = new double[10];
        double ys[] = new double[10];
        int nsegs;
        double x1, x2, y1, y2;
        double z11, z12, z21, z22;

        // OUTPUT PARAMETERS,  INSIDE ContourReturn
        //double x[], y[];  // actually Vectors
        //int n;
        ContourReturn c = new ContourReturn();
        
        nmax = NMAX;
        c.n = 0;   // was 0

        nx = nxgrid; // debug
        ny = nygrid; // debug
        for (i=0; i<(nx-1); i++) {
            x1 = xvec[i];
            x2 = xvec[i+1];
            for (j=0; j<(ny-1); j++) {
                y1 = yvec[j];
                y2 = yvec[j+1];
                //z11 = z[(j  )*nx + i  ];
                //z12 = z[(j+1)*nx + i  ];
                //z21 = z[(j  )*nx + i+1];
                //z22 = z[(j+1)*nx + i+1];
                z11 = potential[i][j];
                z12 = potential[i][j+1];
                z21 = potential[i+1][j];
                z22 = potential[i+1][j+1];
                SegmentReturn s = get_segments(x1,x2,y1,y2,
                                               z11,z12,z21,z22,zvalue);
                for (k=0; k<(s.len-1); k+=2) {
                    //x[n] = xs[k];
                    //y[n] = ys[k];
                    c.x.addElement(new Double(s.xs[k]));
                    c.y.addElement(new Double(s.ys[k]));
                    //x[n] = xs[k+1];
                    //y[n] = ys[k+1];
                    c.x.addElement(new Double(s.xs[k+1]));
                    c.y.addElement(new Double(s.ys[k+1]));
                    c.n++;
                    c.n++;
                } // endfor
            }
        }
        return c;
    }


    // Find all line segments in a pixel.
    //
    // CALLED FROM:
    //     get_contour_line
    //
    /* ! A pixel has 4 edges, defined by the 4 variables {\tt x1}, {\tt x2},
       {\tt y1}, and {\tt y2}.
       The values of the function at each corner of the pixel are defined
       by the variables {\tt z11}, {\tt z12}, {\tt z21}, and {\tt z22}.
       The first subscript refers to the x-coordinate, the second to the y-coordinate.
       This is shown in the figure below:

                    z12            z22
       y2         *---------------*
                  |               |
                  |               |
                  |               |
                  |               |
                  |               |
                  | z11           | z21
       y1         *---------------*
       
                  x1              x2
       
       If the z-value is between any pair of z's on the edges, we note where
       along that edge that would occur.
       If there are 2 of these, we connect them with a line segment and return it.
       If there are 3 or 4 of these, we connect them, somewhat arbitrarily,
       and return multiple line segments.
    */
    
    private SegmentReturn get_segments(double x1,double x2,double y1,double y2,
                                       double z11,double z12,double z21,double z22,
                                       double zvalue) {

        // OUTPUT STUFF:  inside SegmentReturn
        //double xs[] = new double[10];
        //double ys[] = new double[10];
        //int nsegs;
        // END OUTPUT STUFF
        SegmentReturn s = new SegmentReturn();

        double zmin, zmax, m1, m2;
        int n_intercepts;
        double xint[] = new double[10];
        double yint[] = new double[10];
        double frac;

        m1 = Math.min(z11, z12);
        m2 = Math.min(z21, z22);
        zmin = Math.min(m1, m2);
        m1 = Math.max(z11, z12);
        m2 = Math.max(z21, z22);
        zmax = Math.max(m1, m2);


        //System.out.println("get_segments: start");

        if (zvalue < zmin || zvalue > zmax) {
            // contour not possible
            s.nsegs = 0;
            return s;
        }

        // go through each edge of rectangle, finding intercepts with zvalue:
        n_intercepts=-1;

        // 1. bottom edge:
        //zmin = Math.min(z11, z21);
        //zmax = Math.max(z11, z21);
        int minat;
        if (z11 < z21) {
            zmin = z11;
            zmax = z21;
            minat = 1;
        } else {
            zmin = z21;
            zmax = z11;
            minat = 2;
        }
        if (zvalue >= zmin && zvalue <= zmax &&
            Math.abs(zmax-zmin) >= 1.0E-6) {
            n_intercepts= n_intercepts+1;
            frac=(x2-x1)*(zvalue-zmin)/(zmax-zmin);
            //if (Math.abs(zmin-z11) <= 0.1*Math.abs(zmin)) {
            if (minat == 1) {
                // zmin is at x1:
                xint[n_intercepts] = x1 + frac;
            } else {
                // zmin is at x2:
                xint[n_intercepts] = x2 - frac;
            }
            yint[n_intercepts] = y1;
        }

        // 2. left edge:
        //zmin = Math.min(z11,z12);
        //zmax = Math.max(z11,z12);
        if (z11 < z12) {
            zmin = z11;
            zmax = z12;
            minat = 1;
        } else {
            zmin = z12;
            zmax = z11;
            minat = 2;
        }
        if (zvalue >= zmin && zvalue <= zmax &&
            Math.abs(zmax-zmin) >= 1.0E-6) {
            n_intercepts= n_intercepts+1;
            frac=(y2-y1)*(zvalue-zmin)/(zmax-zmin);
            //if (Math.abs(zmin-z11) <= 0.1*Math.abs(zmin)) {
            if (minat == 1) {
                // zmin is at y1:
                yint[n_intercepts] = y1 + frac;
            } else {
                // zmin is at y2:
                yint[n_intercepts] = y2 - frac;
            }
            xint[n_intercepts] = x1;
        }

        // 3. top edge:
        //zmin = Math.min(z12,z22);
        //zmax = Math.max(z12,z22);
        if (z12 < z22) {
            zmin = z12;
            zmax = z22;
            minat = 1;
        } else {
            zmin = z22;
            zmax = z12;
            minat = 2;
        }
        if (zvalue >= zmin && zvalue <= zmax &&
            Math.abs(zmax-zmin) >= 1.0E-6) {
            n_intercepts= n_intercepts+1;
            frac=(x2-x1)*(zvalue-zmin)/(zmax-zmin);
            //if (Math.abs(zmin-z12) <= 0.1*Math.abs(zmin)) {
            if (minat == 1) {
                // zmin is at x1:
                xint[n_intercepts] = x1 + frac;
            } else {
                // zmin is at x2:
                xint[n_intercepts] = x2 - frac;
            }
            yint[n_intercepts] = y2;
        }

        // 4. right edge:
        //zmin = Math.min(z21,z22);
        //zmax = Math.max(z21,z22);
        if (z21 < z22) {
            zmin = z21;
            zmax = z22;
            minat = 1;
        } else {
            zmin = z22;
            zmax = z21;
            minat = 2;
        }
        if (zvalue >= zmin && zvalue <= zmax &&
            Math.abs(zmax-zmin) >= 1.0E-6) {
            n_intercepts= n_intercepts+1;
            frac=(y2-y1)*(zvalue-zmin)/(zmax-zmin);
            //if (Math.abs(zmin-z21) <= 0.1*Math.abs(zmin)) {
            if (minat == 1) {
                // zmin is at y1:
                yint[n_intercepts] = y1 + frac;
            } else {
                // zmin is at y2:
                yint[n_intercepts] = y2 - frac;
            }
            xint[n_intercepts] = x2;
        }

        n_intercepts++; // to put back to count instead of index
        
        if (n_intercepts == 0) {
            s.nsegs = 0;
            s.len = 0;
        } else if (n_intercepts == 1) {
            // NOT POSSIBLE
            s.nsegs = 0;
            s.len = 0;
        } else if (n_intercepts == 2) {
            // connect them with a line:
            s.nsegs = 1;
            s.xs[0] = xint[0];
            s.xs[1] = xint[1];
            s.ys[0] = yint[0];
            s.ys[1] = yint[1];
            s.len = 2;
        } else if (n_intercepts == 3) {
            // connect as 2 lines (a guess)
            s.nsegs = 2;
            s.xs[0] = xint[0];
            s.xs[1] = xint[1];
            s.ys[0] = yint[0];
            s.ys[1] = yint[1];

            s.xs[2] = xint[1];
            s.xs[3] = xint[2];
            s.ys[2] = yint[1];
            s.ys[3] = yint[2];
            s.len = 4;
        }else if (n_intercepts == 4) {
            // connect as 3 lines (a guess)
            s.nsegs = 3;
            s.xs[0] = xint[0];
            s.xs[1] = xint[1];
            s.ys[0] = yint[0];
            s.ys[1] = yint[1];

            s.xs[2] = xint[1];
            s.xs[3] = xint[2];
            s.ys[2] = yint[1];
            s.ys[3] = yint[2];

            s.xs[4] = xint[2];
            s.xs[5] = xint[3];
            s.ys[4] = yint[2];
            s.ys[5] = yint[3];
            s.len = 6;
        }
        return s;
    }


    private double square(double number) {
        double newNumber;
        newNumber = number*number;
        return newNumber;
    }
}



class ContourReturn {
    public Vector<Double> x;
    public Vector<Double> y;
    int n;

    public ContourReturn() {
        super();
        this.x = new Vector<Double>();
        this.y = new Vector<Double>();
        this.n = 0;
    }

    
    public double[] getX() {
        double newX[];
        n = x.size();
        newX = new double[n];
        for (int i=0; i<n; i++)
            newX[i] = (x.elementAt(i)).doubleValue();
        return newX;
    }
    
    public double[] getY() {
        double newY[];
        n = y.size();
        newY = new double[n];
        for (int i=0; i<n; i++)
            newY[i] = (y.elementAt(i)).doubleValue();
        return newY;
    }
}


class SegmentReturn{
    double xs[] = new double[10];
    double ys[] = new double[10];
    int nsegs;
    int len;

    public SegmentReturn() {
        super();
    }
}


class Charge {
    public int xpos, ypos;  // position of charge, in grid coordinates
    public double charge;   // signed charge of each charge, nColoumbs
    public double elemcharge;  // signed charge of each charge as mult of elem charg
    public Point pixelPt;   // actual pixel point (NOT grid point)
    public boolean reflectedCharge;
    public boolean absorbed;
    public double sphereRadius; // in pixels
    
    public Charge() {
        super();
    }

    public Charge(int xpos, int ypos, double charge, double elemcharge,
                  Point pt, double radius) {
        this.xpos = xpos;
        this.ypos = ypos;
        this.charge = charge;
        this.elemcharge = elemcharge;
        this.pixelPt = pt;
        this.reflectedCharge = false;
        this.absorbed = false;
        this.sphereRadius = radius;
    }

    public Charge(Charge ci) {
        super();
        this.xpos = ci.xpos;
        this.ypos = ci.ypos;
        this.charge = ci.charge;
        this.elemcharge = ci.charge;
        this.pixelPt = ci.pixelPt;
        this.reflectedCharge = ci.reflectedCharge;
        this.absorbed = ci.absorbed;
        this.sphereRadius = ci.sphereRadius;
    }
}


class ChargeDensityInfo {
    public int x, y;  // position of chargeDensity, in pixel coordinates
    public double chargeDensity;   // signed chargeDensity
    public Point gridPt;
    public double theta;
    
    public ChargeDensityInfo() {
        super();
    }

    public ChargeDensityInfo(int x, int y, double chargeDensity,
                             Point gridPt, double theta) {
        this.x = x;
        this.y = y;
        this.chargeDensity = chargeDensity;
        this.gridPt = gridPt;
        this.theta = theta;
    }
}


class ActualVector {
    double ex;
    double ey;
    double ee;
    double theta;

    public ActualVector() {
        super();
    }

    public ActualVector(double ex, double ey, double ee, double theta) {
        this.ex = ex;
        this.ey = ey;
        this.ee = ee;
        this.theta = theta;
    }
}
