/*
 * Fred.java
 */

import java.applet.*;
import java.awt.*;
import java.awt.event.*;

/***********************************************************************/
public class Fred extends Applet {
/***********************************************************************/
  Gadgets choices;
  Wireframe mesh;
  Diagram figure;
  Paper graph;
  Spread table;
  double lb, disp, alpha, draft, l1, l2, cp2, phi1, wetted;
  int nvert, nx, ny;
  double [] xcoord, ycoord, zcoord;
  int [] lline, bline;
  boolean calculated;

/***********************************************************************/
  public static void main (String args[]) {
/***********************************************************************
 * from class Fred
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
    Frame f = new Frame ("Fred");
    Fred Tryit = new Fred();
    Tryit.start();
    f.add ("Center", Tryit);
    f.show();
  }
/***********************************************************************/
  public void start() {
/***********************************************************************
 * from class Fred
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
    setLayout (null);
    int iwidth = getSize().width - 1;
    int iheight = getSize().height;
    choices = new Gadgets (this);
    calculated = false;
    choices.setBounds (0, 0, iwidth, 75);
    add (choices);
    int iy = (iheight - 494) / 4;

    Wireframe mesh = new Wireframe (this, choices);
    mesh.setBackground (Color.white);
    mesh.setBounds (0, 75, iwidth, iy);
    add (mesh); this.mesh = mesh;

    Diagram figure = new Diagram (this, choices);
    figure.setBackground (Color.white);
    figure.setBounds (0, 75 + iy, iwidth, iy);
    add (figure); this.figure = figure;

    Paper graph = new Paper (this, choices);
    graph.setBackground (Color.white);
    graph.setBounds (0, 75 + 2 * iy, iwidth, 2* iy);
    add (graph); this.graph = graph;

    Spread table = new Spread (this, choices);
    table.setBackground (Color.white);
    table.setBounds (0, iheight - 420, iwidth, 420);
    add (table); this.table = table;

    choices.setEnabled(true);
  }

/***********************************************************************/
  public void calculate () {
/***********************************************************************
 * from class Fred
 * Purpose: evaluates the Norwood equations 
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   lb = choices.length / choices.beam;
   if (choices.impmet) {
    disp = (choices.weight * 1000000.0) / (Math.pow(choices.length,3) * 2240.0) ;
   } else {
    disp = (choices.weight * 1000000.0) / (Math.pow(choices.length*3.281,3) * 1000.0) ;
   }
   alpha = (choices.cp / disp) * 11200.0 / (lb * lb) ;
   draft = 0.5 * choices.beam / alpha;
   l2 = choices.length * (2 * choices.cp1 - choices.cp) / (2 * choices.cp1) ;
   l1 = choices.length - l2 ;
   cp2 = choices.cp * choices.cp1 / (2 * choices.cp1 - choices.cp) ;
   phi1 = phi (cp2);

   double b2 = choices.beam / 2;
   double omega = l1 / b2;
   double angle = 2 * omega / (omega * omega + 1) ;
   double temp1 =  Math.sqrt ((alpha * alpha + 1) / (2 * alpha * alpha)) ; 
   double temp2 =  Math.asin (angle);
   double temp3 = Math.PI / (8 * b2 * b2) ;
   double temp4 = Math.pow ((l1 * l1 + b2 * b2), 2) ;
   double temp5 = l1 * l1 - b2 * b2 ;
   double aw1 = temp3 * temp1 * (temp4 * temp2 - 2 * b2 * l1 * temp5) ;
   temp5 = (temp4 + phi1 - 1) / (1 - phi1) / (2 * temp4) ;
   double aw2 = 2 * Math.PI * b2 * l2 * temp1 * temp5 ;
   wetted = aw1 + aw2;

   double localx, localx2, localy, localz, shape1;
   int nx = 8, ny = 8, ivert = 0;
   nvert = 2 * nx * 2 * ny;

   xcoord = new double [nvert + 1];
   ycoord = new double [nvert + 1];
   zcoord = new double [nvert + 1];
   lline = new int [2 * nvert];
   bline = new int [2 * nvert];

/* at each station along the length: */
/* entry (front) section */
   shape1 = (l1*l1 - 0.25*choices.beam*choices.beam) / choices.beam;
   double frac1 = 1.0 / Math.pow (nx - 1, 2);
   double frac2 = 0.25 / (double) (ny - 1);
   for (int ix = 1; ix < nx + 1; ix++) {
    localx = frac1 * Math.pow (ix - 1, 2) * l1;
    localy = Math.sqrt (2*localx*l1 - localx*localx + shape1*shape1) - shape1;
    localz = localy / alpha;
    temp1 = Math.pow (0.5 * localy, 2);
    double yc = 0.0;
    temp3 = 0.0;
    for (int iy = 1; iy < 2*ny; iy++) {
     ivert = (ix - 1) * 2 * ny + iy;
     xcoord [ivert] = localx;
     temp3 = frac2 * (ny - iy);
     if (temp3 > 0 ) yc = Math.sqrt (temp3) * localy;
     else yc = -Math.sqrt (-temp3) * localy;
     temp3 = temp1 - yc * yc;
     if (temp3 < 0.0) temp3 = 0.0;
     ycoord [ivert] = 4 * (yc + 0.5 * choices.beam) ;
     zcoord [ivert] = 4 * Math.sqrt (temp3) / alpha;
    }
    ivert = ix * 2 * ny;
    xcoord [ivert] = xcoord [ivert - 1];
    ycoord [ivert] = ycoord [ivert - 1];
    zcoord [ivert] = zcoord [ivert - 1];
   }
/* exit (rear) section */
   for (int ix = nx + 1; ix < 2*nx + 1; ix++) {
    localx2 = frac1 * Math.pow (2 * nx - ix, 2) * l2;
    localx = choices.length - localx2;
/*  draw sections */
    localy = 0.5*choices.beam / (1 - phi1) * (1 - Math.exp (-localx2 * Math.log(1.0/phi1)/l2));
    localz = localy / alpha;
    temp1 = Math.pow (0.5 * localy, 2);
    double yc = 0.0;
    temp3 = 0.0;
    for (int iy = 1; iy < 2*ny; iy++) {
     ivert = (ix - 1) * 2 * ny + iy;
     xcoord [ivert] = localx;
     temp3 = frac2 * (ny - iy);
     if (temp3 > 0 ) yc = Math.sqrt (temp3) * localy;
     else yc = -Math.sqrt (-temp3) * localy;
     temp3 = temp1 - yc * yc;
     if (temp3 < 0.0) temp3 = 0.0;
     ycoord [ivert] = 4 * (yc + 0.5 * choices.beam);
     zcoord [ivert] = 4 * Math.sqrt (temp3) / alpha;
    }
    ivert = ix * 2 * ny;
    xcoord [ivert] = xcoord [ivert - 1];
    ycoord [ivert] = ycoord [ivert - 1];
    zcoord [ivert] = zcoord [ivert - 1];
   }
   int nl = 0, nb = 0;
   for (int iy = 1; iy < 2*ny; iy++) {
    for (int ix = 1; ix < 2*nx+1; ix++) {
     ivert = (ix - 1) * (2 * ny) + iy;
     nl++; lline [nl] = ivert;
    }
   }
   for (int ix = 1; ix < 2*nx+1; ix++) {
    for (int iy = 1; iy < 2*ny; iy++) {
     ivert = (ix - 1) * (2 * ny) + iy;
     nb++; bline [nb] = ivert;
    }
   }
   this.nvert = nvert;
   this.nx = nx;  
   this.ny = ny;  
   for (int i = 1; i < nvert; i++) {
    this.xcoord[i] = xcoord[i];
    this.ycoord[i] = ycoord[i];
    this.zcoord[i] = zcoord[i];
   }
   for (int i = 1; i < nl; i++) this.lline[i] = lline[i];
   for (int i = 1; i < nb; i++) this.bline[i] = bline[i];
   calculated = true;
   mesh.repaint();
   figure.repaint();
   graph.repaint();
   table.repaint();
  }

/***********************************************************************/
  public double phi (double cp) {
/***********************************************************************/
   double temp = (1 - 5 * cp) * Math.log (10);
   double phi1 = Math.exp (temp);
   int itry = 0;
   double a, b, d, cp1 = 0.0;
   while (Math.abs (cp1 - cp) > 1.0e-4 && itry < 100) {
    a = 4 * phi1 - phi1*phi1 + 2 * Math.log (1.0 / phi1) - 3;
    b = (1.0 - phi1)*(1.0 - phi1) * 2 * Math.log (1.0 / phi1);
    if (b < 1.0e-6) phi1 = phi1 * 0.9;
    else {
     cp1 = a / b; d = cp1 / cp;
     phi1 = phi1 * Math.pow (d, 4);
    }
    itry = itry + 1;
   }
   return phi1;
 }

}

/***********************************************************************/
class Spread extends Canvas {
/***********************************************************************/
  Fred fred; Gadgets choices;
  double [] aw, awp, alp;
  double sigma, vol, wpl, dsi, psi1, psi2;

/***********************************************************************/
  public Spread (Fred fred, Gadgets choices) {
/***********************************************************************
 * from class Spread
 * Purpose: evaluates the Norwood equations and writes answers
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
    this.fred = fred;
    this.choices = choices;
  }
/***********************************************************************/
  public void paint (Graphics g) {
/***********************************************************************
 * from class Spread
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
    g.setColor (Color.black);
    if (!fred.calculated) return;
    double temp, temp1, temp2, temp3, temp4, temp5;
    aw = new double [4];
    awp = new double [4];
    alp = new double [4];
    for (int i = 0; i < 4; i++) {
     aw [i] = 0.0; awp [i] = 0.0; alp [i] = 0.0;
    }

    sigma = Math.PI * Math.pow (choices.beam,2) / (fred.alpha * 8.0) ;
    vol = choices.cp * sigma * choices.length ;
    double b2 = choices.beam / 2 ;
    double omega = fred.l1 / b2;
    double angle = 2 * omega / (omega*omega + 1) ;
    psi1 = Math.atan (2*omega / (omega*omega - 1)) * 57.296 ;
    temp1 =  Math.sqrt ((fred.alpha*fred.alpha+1) / (2*fred.alpha*fred.alpha)) ; 
    temp2 =  Math.asin (angle);
    temp3 = Math.PI / (8 * b2 * b2) ;
    temp4 = Math.pow ((fred.l1*fred.l1 + b2*b2), 2) ;
    temp5 = fred.l1*fred.l1 - b2*b2 ;
    aw [1] = temp3 * temp1 *(temp4 * temp2 - 2*b2*fred.l1 * temp5) ;
    awp [1] = 1 / (4 * b2*b2) * (temp4 * temp2 - 2*b2*fred.l1*temp5) ;
    alp [1] = 1 / (8 * b2*b2 * fred.alpha) * (temp4 * temp2 - 2*b2*fred.l1*temp5) ;
    temp4 = Math.log (1.0 / fred.phi1) ;
    temp5 = (temp4 + fred.phi1 - 1) / (1 - fred.phi1) / (2 * temp4) ;
    psi2 = Math.atan (b2 * temp4 / (fred.l2 * (1 - fred.phi1))) * 57.296 ;
    aw [2] = 2 * Math.PI * b2 * fred.l2 * temp1 * temp5 ;
    awp [2] = 4 * b2 * fred.l2 * temp5;
    alp [2] = (2 * b2 * fred.l2) / fred.alpha * temp5;
    aw [3] = aw [1] + aw [2];
    awp [3] = awp [1] + awp [2];
    alp [3] = alp [1] + alp [2];
    wpl = choices.weight / awp [3];
    if (choices.impmet) dsi = 5.33 * awp[3];
    else dsi = 1.025 * awp[3];

    int iypos = 0;

    StringBuffer number = new StringBuffer (0);
    g.drawString ("L", 10, iypos + 30);
    number.append (choices.length);
    g.drawString (number.toString(), 70, iypos + 30);
    g.drawString ("Waterline Length", 220, iypos + 30);
    g.drawString ("B", 10, iypos + 50);
    number.setLength (0);
    number.append (choices.beam);
    g.drawString (number.toString(), 70, iypos + 50);
    g.drawString ("Waterline Beam", 220, iypos + 50);
    g.drawString ("W", 10, iypos + 70);
    number.setLength (0);
    number.append (choices.weight);
    g.drawString (number.toString(), 70, iypos + 70);
    g.drawString ("Weight", 220, iypos + 70);
    g.drawString ("Cp", 10, iypos + 90);
    number.setLength (0);
    number.append (choices.cp);
    g.drawString (number.toString(), 70, iypos + 90);
    g.drawString ("Prismatic coefficient", 220, iypos + 90);
    g.drawString ("L/B", 10, iypos + 110);
    number.setLength (0);
    number.append (fred.lb);
    number.setLength (6);
    g.drawString (number.toString().trim(), 70, iypos + 110);
    g.drawString ("Waterline Length/Beam ratio", 220, iypos + 110);
    g.drawString ("D", 10, iypos + 130);
    number.setLength (0);
    number.append (fred.disp);
    number.setLength (6);
    g.drawString (number.toString().trim(), 70, iypos + 130);
    g.drawString ("Displacement/Length ratio", 220, iypos + 130);
    g.drawString ("H", 10, iypos + 150);
    number.setLength (0);
    number.append (fred.draft);
    number.setLength (6);
    g.drawString (number.toString().trim(), 70, iypos + 150);
    g.drawString ("Draft", 220, iypos + 150);
    g.drawString ("alpha", 10, iypos + 170);
    number.setLength (0);
    number.append (fred.alpha);
    number.setLength (6);
    g.drawString (number.toString().trim(), 70, iypos + 170);
    g.drawString ("elliptic eccentricity", 220, iypos + 170);
    g.drawString ("sigma", 10, iypos + 190);
    number.setLength (0);
    number.append (sigma);
    number.setLength (6);
    g.drawString (number.toString().trim(), 70, iypos + 190);
    g.drawString ("area of maximum section", 220, iypos + 190);
    g.drawString ("vol", 10, iypos + 210);
    number.setLength (0);
    number.append (vol);
    number.setLength (6);
    g.drawString (number.toString().trim(), 70, iypos + 210);
    g.drawString ("Volume of hull", 220, iypos + 210);
    g.drawString ("wpl", 10, iypos + 230);
    number.setLength (0);
    number.append (wpl);
    number.setLength (6);
    g.drawString (number.toString().trim(), 70, iypos + 230);
    g.drawString ("Water plane loading", 220, iypos + 230);
    g.drawString ("ppi", 10, iypos + 250);
    number.setLength (0);
    number.append (dsi);
    number.setLength (6);
    g.drawString (number.toString().trim(), 70, iypos + 250);
    g.drawString ("Loading to immerse hull", 220, iypos + 250);
    if (choices.impmet) {
     g.drawString ("(ft)", 150, iypos + 30);
     g.drawString ("(ft)", 150, iypos + 50);
     g.drawString ("(lb)", 150, iypos + 70);
     g.drawString ("(ft)", 150, iypos + 150);
     g.drawString ("(sq ft)", 150, iypos + 190);
     g.drawString ("(cu ft)", 150, iypos + 210);
     g.drawString ("(lb/sq ft)", 150, iypos + 230);
     g.drawString ("(lb/in)", 150, iypos + 250);
    } else {
     g.drawString ("(m)", 150, iypos + 30);
     g.drawString ("(m)", 150, iypos + 50);
     g.drawString ("(kg)", 150, iypos + 70);
     g.drawString ("(m)", 150, iypos + 150);
     g.drawString ("(sq m)", 150, iypos + 190);
     g.drawString ("(cu m)", 150, iypos + 210);
     g.drawString ("(kg/sq m)", 150, iypos + 230);
     g.drawString ("(kg/mm)", 150, iypos + 250);
    }
    g.drawString ("front section", 70, iypos + 280);
    g.drawString ("(entry)", 70, iypos + 290);
    g.drawString ("rear section", 160, iypos + 280);
    g.drawString ("(exit)", 160, iypos + 290);
    g.drawString ("Total", 250, iypos + 280);
    g.drawString ("L", 10, iypos + 310);
    number.setLength (0);
    number.append (fred.l1);
    number.setLength (6);
    g.drawString (number.toString().trim(), 70, iypos + 310);
    number.setLength (0);
    number.append (fred.l2);
    number.setLength (6);
    g.drawString (number.toString().trim(), 160, iypos + 310);
    number.setLength (0);
    number.append (choices.length);
    number.setLength (6);
    g.drawString (number.toString().trim(), 250, iypos + 310);
    g.drawString ("Length of section", 360, iypos + 310);
    g.drawString ("Cp", 10,  iypos + 330);
    number.setLength (0);
    number.append (choices.cp1);
    number.setLength (6);
    g.drawString (number.toString().trim(), 70, iypos + 330);
    number.setLength (0);
    number.append (fred.cp2);
    number.setLength (6);
    g.drawString (number.toString().trim(), 160, iypos + 330);
    g.drawString ("Prismatic coefficient", 360, iypos + 330);
    g.drawString ("psi", 10, iypos + 350);
    number.setLength (0);
    number.append (psi1);
    number.setLength (6);
    g.drawString (number.toString().trim(), 70, iypos + 350);
    number.setLength (0);
    number.append (psi2);
    number.setLength (6);
    g.drawString (number.toString().trim(), 160, iypos + 350);
    g.drawString ("half-angle", 360, iypos + 350);
    g.drawString ("Aw", 10, iypos + 370);
    number.setLength (0);
    number.append (aw [1]);
    number.setLength (6);
    g.drawString (number.toString().trim(), 70, iypos + 370);
    number.setLength (0);
    number.append (aw [2]);
    number.setLength (6);
    g.drawString (number.toString().trim(), 160, iypos + 370);
    number.setLength (0);
    number.append (aw [3]);
    number.setLength (6);
    g.drawString (number.toString().trim(), 250, iypos + 370);
    g.drawString ("Wetted area", 360, iypos + 370);
    g.drawString ("Awp", 10, iypos + 390);
    number.setLength (0);
    number.append (awp [1]);
    number.setLength (6);
    g.drawString (number.toString().trim(), 70, iypos + 390);
    number.setLength (0);
    number.append (awp [2]);
    number.setLength (6);
    g.drawString (number.toString().trim(), 160, iypos + 390);
    number.setLength (0);
    number.append (awp [3]);
    number.setLength (6);
    g.drawString (number.toString().trim(), 250, iypos + 390);
    g.drawString ("Waterplane area", 360, iypos + 390);
    g.drawString ("Alp", 10, iypos + 410);
    number.setLength (0);
    number.append (alp [1]);
    number.setLength (6);
    g.drawString (number.toString().trim(), 70, iypos + 410);
    number.setLength (0);
    number.append (alp [2]);
    number.setLength (6);
    g.drawString (number.toString().trim(), 160, iypos + 410);
    number.setLength (0);
    number.append (alp [3]);
    number.setLength (6);
    g.drawString (number.toString().trim(), 250, iypos + 410);
    g.drawString ("Lateral plane area", 360, iypos + 410);
    if (choices.impmet) {
     g.drawString ("(ft)", 310, iypos + 310);
     g.drawString ("(sq ft)", 310, iypos + 370);
     g.drawString ("(sq ft)", 310, iypos + 390);
     g.drawString ("(sq ft)", 310, iypos + 410);
    } else {
     g.drawString ("(m)", 310, iypos + 310);
     g.drawString ("(sq m)", 310, iypos + 370);
     g.drawString ("(sq m)", 310, iypos + 390);
     g.drawString ("(sq m)", 310, iypos + 410);
    }
     g.drawString ("(deg)", 310, iypos + 350);
  }
}  

/***********************************************************************/
class Gadgets extends Panel implements ActionListener, ItemListener {
/***********************************************************************/
  Fred fred;
  boolean impmet;
  double length, beam, weight, cp, cp1, max_speed;
  Panel card1, card2, card3;
  TextField l, b, w, c, p, s;
  String IMPPANEL, METPANEL;

/***********************************************************************/
  public Gadgets (Fred fred) {
/***********************************************************************
 * from class Gadgets
 * Purpose: sets the controls
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   this.fred = fred;
   impmet = true;
   Label lab1a, lab2a, lab3a, lab1b, lab2b, lab3b, lab4, lab5, lab6;
   GridBagLayout gridbag = new GridBagLayout();
   setLayout (gridbag);
   GridBagConstraints gbc = new GridBagConstraints();
   gbc.anchor = GridBagConstraints.NORTH;
   gbc.fill = GridBagConstraints.BOTH;
   gbc.weightx = 1.0; gbc.weighty = 1.0;
   gbc.gridwidth = 1; gbc.gridheight = 1;
   length = 40; l = new TextField ("40", 7);
   gridbag.setConstraints (l, gbc);
   l.setBackground (Color.white); add (l);
   beam = 4; b = new TextField ("4", 7);
   gridbag.setConstraints (b, gbc);
   b.setBackground (Color.white); add (b);
   weight = 8000; w = new TextField ("8000", 7);
   gridbag.setConstraints (w, gbc);
   w.setBackground (Color.white); add (w);
   cp = 0.65; c = new TextField ("0.65", 7);
   gbc.weightx = 1.0; gridbag.setConstraints (c, gbc);
   c.setBackground (Color.white); add (c);
   cp1 = 0.54; p = new TextField ("0.54", 7);
   gbc.gridwidth = GridBagConstraints.RELATIVE;
   gridbag.setConstraints (p, gbc);
   p.setBackground (Color.white); add (p);
   max_speed = 40; s = new TextField ("40", 7);
   gbc.gridwidth = GridBagConstraints.REMAINDER;
   gridbag.setConstraints (s, gbc);
   s.setBackground (Color.white); add (s);
   IMPPANEL = "Imperial units";
   METPANEL = "Metric units";
   gbc.gridwidth = 1;
   gbc.gridheight = GridBagConstraints.RELATIVE;
   card1 = new Panel();
   card1.setLayout (new CardLayout());
   lab1a = new Label ("Length (ft)"); card1.add (IMPPANEL, lab1a);
   lab1b = new Label ("Length (m) "); card1.add (METPANEL, lab1b);
   gbc.weightx = 1.0; gbc.weighty = 1.0;
   gridbag.setConstraints (card1, gbc);
   card1.setBackground (Color.white);
   add (card1);
   gbc.gridheight = 1;
   card2 = new Panel();
   card2.setLayout (new CardLayout());
   lab2a = new Label (" Beam (ft) "); card2.add (IMPPANEL, lab2a);
   lab2b = new Label (" Beam (m)  "); card2.add (METPANEL, lab2b);
   gbc.weightx = 1.0;
   gridbag.setConstraints (card2, gbc);
   card2.setBackground (Color.white);
   add (card2);
   card3 = new Panel();
   card3.setLayout (new CardLayout());
   lab3a = new Label ("Weight (lb)"); card3.add (IMPPANEL, lab3a);
   lab3b = new Label ("Weight (kg)"); card3.add (METPANEL, lab3b);
   gbc.weightx = 1.0;
   gridbag.setConstraints (card3, gbc);
   card3.setBackground (Color.white);
   add (card3);
   lab4 = new Label ("    Cp     ");
   gbc.weightx = 1.0;
   gridbag.setConstraints (lab4, gbc);
   lab4.setBackground (Color.white);
   add (lab4);
   lab5 = new Label ("    Cp1    ");
   gbc.weightx = 1.0;
   gbc.gridwidth = GridBagConstraints.RELATIVE;
   gridbag.setConstraints (lab5, gbc);
   lab5.setBackground (Color.white);
   add (lab5);
   lab6 = new Label ("max speed  ");
   gbc.weightx = 1.0;
   gbc.gridwidth = GridBagConstraints.REMAINDER;
   gridbag.setConstraints (lab6, gbc);
   lab6.setBackground (Color.white);
   add (lab6);
   gbc.gridwidth = 1;
   gbc.gridheight = GridBagConstraints.REMAINDER;
   Choice ch = new Choice();
   ch.addItem (IMPPANEL);
   ch.addItem (METPANEL);
   gbc.weightx = 1.0; gbc.weighty = 1.0;
   gridbag.setConstraints (ch, gbc);
   add (ch); ch.addItemListener (this);
   gbc.weightx = 1.0;
   gbc.gridwidth = GridBagConstraints.REMAINDER;
   gbc.gridheight = 1;
   Button butt = new Button ("Calculate");
   gridbag.setConstraints (butt, gbc);
   butt.setBackground (Color.cyan);
   add (butt); butt.addActionListener (this);
  }

/***********************************************************************/
  public void itemStateChanged (ItemEvent evt) {
/***********************************************************************
 * from class Gadgets
 * Purpose: reads the controls
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   if (evt.getSource() instanceof Choice) {
    this.length = Double.valueOf (l.getText().trim()).doubleValue();
    this.beam = Double.valueOf (b.getText().trim()).doubleValue();
    this.weight = Double.valueOf (w.getText().trim()).doubleValue();
    Choice ch = (Choice) evt.getSource();
    String label = ch.getSelectedItem();
    ((CardLayout)card1.getLayout()).show(card1, label);
    ((CardLayout)card2.getLayout()).show(card2, label);
    ((CardLayout)card3.getLayout()).show(card3, label);
    boolean oldImpmet = this.impmet;
    if (label == IMPPANEL) {
     this.impmet = true;
     if (!oldImpmet) {
      this.length = this.length / 0.3048; 
      this.beam = this.beam / 0.3048; 
      this.weight = this.weight * 2.2026; 
     }
    } else {
      this.impmet = false;
      if (oldImpmet) {
       this.length = this.length * 0.3048; 
       this.beam = this.beam * 0.3048; 
       this.weight = this.weight * 0.454; 
      }
    }
    StringBuffer number = new StringBuffer (0);
    number.append (length); number.setLength (6);
    l.setText (number.toString ());
    number.setLength (0);
    number.append (beam); number.setLength (6);
    b.setText (number.toString ());
    number.setLength (0);
    number.append (weight); number.setLength (8);
    w.setText (number.toString ());
   }
  }

/***********************************************************************/
  public void actionPerformed (ActionEvent evt) {
/***********************************************************************
 * from class Gadgets
 * Purpose: reads the controls
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   if (evt.getSource() instanceof Button) {
    this.length = Double.valueOf (l.getText().trim()).doubleValue();
    this.beam = Double.valueOf (b.getText().trim()).doubleValue();
    this.cp = Double.valueOf (c.getText().trim()).doubleValue();
    this.weight = Double.valueOf (w.getText().trim()).doubleValue();
    this.cp1 = Double.valueOf (p.getText().trim()).doubleValue();
    this.max_speed = Double.valueOf (s.getText().trim()).doubleValue();
    fred.calculate ();
   }
  }
}   

/***********************************************************************/
class Paper extends Canvas {
/***********************************************************************/
  Fred fred; Gadgets choices;
  Graphics2D g;
  double kw;
  int nsets;
  int[] lintyp = new int[4];
  int[] mark = new int[4];
  int[] icol = new int[4];
  String[] keylab = new String [4];
  double[] speed = new double [12];
  double[] wres = new double [12];
  double[] fres = new double [12];
  double[] tres = new double [12];
  String xlabel, ylabel;
  int ixlen, iylen, ixOrigin, iyOrigin, ichWidth, ichHeight;
  double xmin, xmax, ymin, ymax;
  int nxdp, nxf, nxind, nxinc, nxtick;
  int nydp, nyf, nyind, nyinc, nytick;
  double xScale, yScale;
  FontMetrics fontMetrics;
  int keyBoxWidth, keyBoxHeight;

/***********************************************************************/
  public Paper (Fred fred, Gadgets choices) {
/***********************************************************************
 * from class Paper
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
    this.fred = fred;
    this.choices = choices;
  }

/***********************************************************************/
  public void paint (Graphics gr) {
/***********************************************************************
 * from class Paper
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   Graphics2D g = (Graphics2D)gr;
   this.g = g;
   g.setColor (Color.black);
   if (!fred.calculated) return;
   xlabel = "Speed (kt)";
   if (choices.impmet) ylabel = "Resistance (lb)";
   else ylabel = "Resistance (kg)";
   kw = 7.9 - 0.144 * fred.disp + 0.00111 * fred.disp * fred.disp;
   keylab [1] = "Friction Res.";
   keylab [2] = "Wave Res.";
   keylab [3] = "Total Res.";
   for (int i = 1; i < 4; i++) {
     lintyp [i] = i; mark [i] = i; icol [i] = i;
   }
   nsets = 11;
   double froude, froude2, cp4, temp1, reynolds;
   double sspeed = 0.1 * choices.max_speed;
   for (int i = 1; i < 12; i++) {
    speed [i] = (i - 1) * sspeed;
    if (choices.impmet) {
     froude = 0.298 * speed [i] / Math.sqrt (choices.length); 
     reynolds = 0.5 * 1000000 * froude * choices.length * Math.sqrt (choices.length);
    } else {
     froude = 0.1644 * speed [i] / Math.sqrt (choices.length);
     reynolds = 2.97 * 1000000 * froude * choices.length * Math.sqrt (choices.length);
    }
    froude2 = froude * froude;
    cp4 = Math.pow (choices.cp, 4);
    temp1 = Math.cos (choices.cp / froude2);
    wres [i] = 8.93 / 100000 * kw * froude2 * fred.disp / cp4 * (1 - temp1) * choices.weight;
    if (froude < 0.4 * Math.sqrt (choices.cp)) wres [i] = 0.0;
    fres [i] = 0.00465 * froude2 * choices.length * choices.weight / choices.cp / choices.beam;
//    fres [i] = 38.4 * froude2 * choices.length * fred.wetted / Math.pow ((Math.log(reynolds)*0.4343-2), 2);
    tres [i] = wres [i] + fres [i];
   }
   keyBoxWidth = 0; keyBoxHeight = 0;
   xmin = 0; xmax = 0; ymin = 0; ymax = 0;
   nxf = 0; nyf = 0; nxdp = 0; nydp = 0;
   nxind = 0; nyind = 0; nxinc = 10; nyinc = 10;
   nxtick = 0; nytick = 0;

   if (!fred.calculated) return;
   g.setBackground (Color.white);
   drawFrame ();
   plotData (nsets, speed, wres, 1, 1, 1);
   plotData (nsets, speed, fres, 2, 2, 2);
   plotData (nsets, speed, tres, 3, 3, 3);
   plotKey ();
   setVisible(true);
  }
/***********************************************************************/
  void autoScale (int axisOrient, double z[][], int nz, int nc) {
/***********************************************************************
 * from class Paper
 * Purpose: calculates a suitable scale for the axis,
 *          giving round numbers for values and increments
 * Input arguments: z - array of points for scaling axis
 *                  nz - number of points of array z to be used
 * Output arguments: zmin, zmax - minimum and maximum values on axis
 *                   nind - order of mag factor (no of dec places raised to)
 *                   ninc - number of increments along axis
 *                   ntick - number of ticks per increment
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   double zmin = 0, zmax = 0, zlen = 0, zm = 0, zinc = 0, ztemp = 0;
   int ndp = 0, nfield = 0, nind = 0, ninc = 0, ntick = 0, izt = 0;
   Dimension dims = this.getToolkit().getScreenSize();
   int screenH = dims.height; int screenW = dims.width;
/* find max and min values */
   zmin = z [0][0]; zmax = z [0][0];
   for (int k = 0; k < nz; k++) {
    for (int l = 0; l < nc; l++) {
     if (z [l][k] < zmin) zmin = z [l][k];
     if (z [l][k] > zmax) zmax = z [l][k];
    }
   }
   zlen = zmax - zmin;
   if (zlen < 1.0E-6) {zmin = zmin - 0.05; zmax = zmax + 0.05; zlen = zmax - zmin;}
   if (zmin > 0.0 && zmin < 0.5 * zlen) zmin = 0.0;
   if (zmax < 0.0 && (-zmax) < 0.5 * zlen) zmax = 0.0;
   zlen = zmax - zmin;
   ninc = 10;
/* find increment zinc */
   zm = Math.max (Math.abs (zmin), Math.abs (zmax));
   if (zlen * 100 < zm) ninc = 1;
   zinc = zlen / (double)ninc;
   if (zmin < 0.0 && (-zmin) < 0.1 * zinc) zmin = 0.0;
   if (zmax > 0.0 && zmax < 0.1 * zinc) zmax = 0.0;
   zlen = zmax - zmin;
   zinc = zlen / (double)ninc;
/* find order of magnitude */
   ztemp = Math.log (zinc) / Math.log (10.0);
   ndp = -(int)Math.round (ztemp);
   if (ztemp < 0) ndp = ndp + 1;
   zinc = zinc * Math.pow (10., ndp);
   izt = (int)Math.round (zinc);
   if (izt == 0) izt = 1;
   if (izt == 3) izt = 2;
   if (izt == 6 || izt == 7) izt = 5;
   if (izt > 7) {izt = 1; ndp = ndp - 1;}
   zinc = (double)izt * Math.pow (10., -ndp);
   nind = -3 * (ndp / 3);
/* reset ends to round numbers */
   if (zmin < 0.0) zmin = zinc * (int)Math.round (zmin / zinc - 0.99);
   if (zmin > 0.0) zmin = zinc * (int)Math.round (zmin / zinc);
   if (zmax < 0.0) zmax = zinc * (int)Math.round (zmax / zinc);
   if (zmax > 0.0) zmax = zinc * (int)Math.round (zmax / zinc + 0.99);
   ninc = (int)Math.round ((zmax-zmin) / zinc);
   if (ninc == 0) ninc = 1;
/* calculate number of sub increments */
   ntick = 4; if (izt == 5) ntick = 5;
/* calculate field width */
   zm = Math.max (Math.abs (zmin), Math.abs (zmax));
   ztemp = Math.log (zm) / Math.log (10.0);
   nfield = (int) Math.floor (ztemp);
   if (zmin < 0) nfield = nfield + 1;
   if (ndp > 0) nfield = nfield + 1 + ndp;
   if (axisOrient == 1) {
    xmin = zmin; xmax = zmax; nxdp = ndp;
    nxf = nfield; nxind = nind; nxinc = ninc; nxtick = ntick;
   } else {
    ymin = zmin; ymax = zmax; nydp = ndp;
    nyf = nfield; nyind = nind; nyinc = ninc; nytick = ntick;
   }
 }

/***********************************************************************/
  void plotPosition () {
/**********************************************************************
 * from class Paper
 * Purpose: calculates the position on the plot device at
 *          which the graph will appear
 *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   int ixsize = getSize().width;
   int iysize = getSize().height;
   iysize = getSize().height - keyBoxHeight;
   double xlen = xmax - xmin;
   double ylen = ymax - ymin;
   int ixgap = 8 * ichWidth;
   if (xmin < 0.0 && xmax > 0.0) ixgap = 2 * ichWidth;
   int iygap = 5 * ichHeight;
   if (ymin < 0.0 && ymax > 0.0) iygap = 2 * ichHeight;
   ixlen = ixsize - ixgap - 10;
   iylen = iysize - iygap - 2 * ichHeight - 10;
/* reset axis lengths to give a round number of pixels per tick */
   int ixtemp = ixlen / (nxinc * nxtick);
   int iytemp = iylen / (nyinc * nxtick);
   ixlen = ixtemp * (nxinc * nxtick);
   iylen = iytemp * (nyinc * nytick);
   if (xmin >= 0.0) ixOrigin = (int)(0.90 * ixgap);
   else ixOrigin = (int)(0.10 * ixgap);
   ixOrigin = ixOrigin + 10;
   if (ymin >= 0.0) iyOrigin = (int)(0.90 * iygap);
   else iyOrigin = (int)(0.10 * iygap);
   iyOrigin = iyOrigin + 10;
   xScale = (double)ixlen / (xmax - xmin);
   yScale = (double)iylen / (ymax - ymin);
 }  

/***********************************************************************/
  void plotData (int npoints, double [] xx, double [] yy,
      int ltype, int col, int mark) {
/**********************************************************************
 * from class Paper
 * Purpose: plots data points on previously drawn axes
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   String symbol = " ";
   setColour (col);
   int lalign = 5, ix = 0, iy = 0;
   Font oldFont = g.getFont();
   symbol = setMark (mark);
/* change to Map symbols font */
   g.setFont (new Font ("Map Symbols", Font.PLAIN, 10));
   for (int i = 1; i < npoints + 1; i++) {
    ix = ixOrigin + (int)Math.round (xx [i] * xScale);
    iy = getSize().height - iyOrigin - (int)Math.round (yy [i] * yScale);
    drawText (symbol, ix, iy, 5);
   }
   g.setFont (oldFont);
   setLineStyle (ltype);
   ix = ixOrigin + (int)Math.round (xx [1] * xScale);
   iy = getSize().height - iyOrigin - (int)Math.round (yy [1] * yScale);
   int ix0 = ix, iy0 = iy, ix1 = 0, iy1 = 0;
   for (int i = 2; i < npoints + 1; i++) {
    ix = ixOrigin + (int)Math.round (xx [i] * xScale);
    iy = getSize().height - iyOrigin - (int)Math.round (yy [i] * yScale);
    ix1 = ix; iy1 = iy;
    g.drawLine (ix0, iy0, ix1, iy1);
    ix0 = ix; iy0 = iy;
   }
   setLineStyle (1);
 } 

/***********************************************************************/
  void drawFrame () {
/**********************************************************************
 * from class Paper
 * Purpose: open plot window, scales and draws axes,
 *          sets origin and scale for graphs
 *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
/* start a new graph and draw new axes */
   g.setFont (new Font ("Serif", Font.PLAIN, 12));
   fontMetrics = g.getFontMetrics();
   ichWidth = fontMetrics.stringWidth("0");
   ichHeight = fontMetrics.getHeight();
   double [][] xp = new double [1][nsets+1];
   double [][] yp = new double [3][nsets+1];
   for (int i = 0; i < nsets + 1; i++) xp [0][i] = speed [i];
   for (int i = 0; i < nsets + 1; i++) {
    yp [0][i] = fres [i]; yp [1][i] = wres [i]; yp [2][i] = tres [i];
   }
   autoScale (1, xp, nsets, 1);
   autoScale (2, yp, nsets, 3);
   getKeySize ();
   plotPosition ();
   int ixinc = ixlen / nxinc;
   int iyinc = iylen / nyinc;
   drawAxis (ixinc, iyinc);
   int ixmin = ixOrigin + (int)Math.round (xmin * xScale);
   int ixmax = ixOrigin + (int)Math.round (xmax * xScale);
   int iymax = getSize().height - iyOrigin - (int)Math.round (ymin * yScale);
   int iymin = getSize().height - iyOrigin - (int)Math.round (ymax * yScale);
   g.drawLine (ixmin, iymin, ixmax, iymin);
   g.drawLine (ixmax, iymin, ixmax, iymax);
   g.drawLine (ixmax, iymax, ixmin, iymax);
   g.drawLine (ixmin, iymax, ixmin, iymin);
   g.clipRect (ixmin, iymin, ixmax-ixmin, iymax-iymin);
   g.setColor (Color.black);
 }  

/***********************************************************************/
  void plotKey () {
/**********************************************************************
 * from class Paper
 * Purpose: draws a key to the data sets in the graph
 * Input arguments: nkey - number of labels in key
 *       keylab - array of key labels
 *       lintyp - line type
 *       mark - marker symbol
 *       ixpos, iypos - user coordinates of upper left hand corner of plot
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   g.setClip (null); g.setColor (Color.black);
   setLineStyle (1);
   int maxlen = 0, lalign = 0;
   int [] istrwidth = new int [4];
   for (int i = 1; i < 4; i++) {
    istrwidth [i] = fontMetrics.stringWidth (keylab [i]);
    if (istrwidth [i] > maxlen) maxlen = istrwidth [i];
   }

   maxlen = 0;
   for (int i = 1; i < 4; i++) {
    maxlen = maxlen + istrwidth [i];
   }
   int ixleft = (getSize().width - keyBoxWidth) / 2;
   int iytop = 10;
   g.drawRect (ixleft, iytop+1, keyBoxWidth, keyBoxHeight);
   String symbol = " ";
   for (int i = 1; i < 4; i++) {
    setColour (icol [i]);
    int iyy = iytop + ichHeight;
    setLineStyle (lintyp [i]);
    g.drawLine (ixleft, iyy, ixleft + 5 * ichWidth, iyy);
    symbol = setMark (mark [i]);
    int ix = ixleft + (int)(2.5 * ichWidth); lalign = 5;
/*  change to Map symbols font */
    Font oldFont = g.getFont ();
    g.setFont (new Font ("Map Symbols", Font.PLAIN, 10));
    drawText (symbol, ix, iyy, 5);
    g.setFont (oldFont);
    int ixc = ixleft + 3 * ichWidth; lalign = 8;
    drawText (keylab [i], ixc, iyy, lalign);
    ixleft = ixleft + istrwidth [i] + 6 * ichWidth;
   }  
   setLineStyle (1);
 }  

/***********************************************************************/
  void drawText (String label, int ixc, int iyc, int lalign) {
/***********************************************************************
 * from class Paper
 * Purpose: plots a character string
 * Input arguments: ixc, iyc - centreing position of text
 *       label - text to be plotted
 *       lalign - string alignment
 ***********************************************************************
 */
   if (label == null) return;
   int ixofset = 0, iyofset = 0;

/* calculate length of text string */
   fontMetrics = g.getFontMetrics();
   int istrwidth = fontMetrics.stringWidth(label);
   int icharwidth = fontMetrics.stringWidth("o");
   int ichh = fontMetrics.getHeight();
  
/* calculate offsets for alignment */
   ixc = ixc - icharwidth; iyc = iyc + ichh / 2;
   switch (lalign) {
    case 1: case 2: case 3: ixofset = -istrwidth/2; break;
    case 4: case 5: case 6: ixofset = 0; break;
    case 7: case 8: case 9: ixofset = istrwidth/2; break;
   }
   switch (lalign) {
    case 1: case 4: case 7: iyofset = ichh / 2; break;
    case 2: case 5: case 8: iyofset = 0; break;
    case 3: case 6: case 9: iyofset = -ichh / 2; break;
   }
  
/* plot string with lower left corner at point */
   g.drawString (label, ixc + ixofset, iyc + iyofset);
 } 

/************************************************************************/
  void drawAxis (int ixinc, int iyinc) { 
/************************************************************************;
 * from class Paper
 * Purpose: draws axes
 * Input arguments: xmin,xmax, ymin, ymax - user coordinates
 *       ixinc, iyinc -
 *       nxinc, nyinc -
 *       iticlen -
 *       nxtick, nytick -
 *       nxind, nyind - 
 * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++;
 */
   g.setColor (Color.black);
   int ixstart = ixOrigin, iystart = iyOrigin;
   int axisOrient, lalign;
   int ix1, ix2, iy1, iy2;
   double xOrigin = 0.0, yOrigin = 0.0;
   if (xmin > 0) {xOrigin = xmin;}
   else {ixOrigin = ixOrigin - (int) (xmin * xScale);}
   if (xmax < 0) xOrigin = xmax;
   if (ymin > 0) {yOrigin = ymin;}
   else {iyOrigin = iyOrigin - (int) (ymin * yScale);}
   if (ymax < 0) yOrigin = ymax;
   ix1 = ixstart;
   ix2 = ixstart + ixlen;
   iy1 = getSize().height - iystart;
   iy2 = getSize().height - iystart - iylen;

/* x axis */
   axisOrient = 1; lalign = 4;
   g.drawLine (ix1, getSize().height - iyOrigin, ix2,
     getSize().height - iyOrigin);
   drawGrid (axisOrient, iy1, iy2, nxinc, nxtick, getSize().height - iyOrigin, ix1, ixinc); 
   drawNumbs (axisOrient, xmin, xmax, nxf, nxdp, nxinc, nxind, xOrigin, ymax, lalign); 
   drawLabels (axisOrient, nxind, xOrigin, lalign); 

/* y axis */
   axisOrient = 2; lalign = 2;
   g.drawLine (ixOrigin, iy1, ixOrigin, iy2);
   drawGrid (axisOrient, ix1, ix2, nyinc, nytick, ixOrigin, iy1, iyinc);
   drawNumbs (axisOrient, ymin, ymax, nyf, nydp, nyinc, nyind, yOrigin, xmax, lalign);
   drawLabels (axisOrient, nyind, yOrigin, lalign);
 } 

/************************************************************************/
  void drawGrid (int axisOrient, int iz1, int iz2, 
    int nzinc, int nztick, int izOrigin, int istart, int izinc) { 
/************************************************************************;
 * from class Paper
 * Purpose: draws tick marks on axes;
 * Input arguments: iax - x axis = 1; y axis = 2
 *       nzinc - number of increments
 *       nztick - number of ticks between increments
 *       izorig - origin of other axis
 *       istart - start value
 *       izinc - increment
 * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++;
 */
   g.setColor (Color.gray);
   int iztick = izinc / nztick;
   int izz = istart, ngrid = (nztick + 1) * nzinc + 1;
   for (int iz = 0; iz < nzinc + 1; iz++) {
    g.setColor (Color.black);
    if (axisOrient == 1) g.drawLine (izz, iz1, izz, iz2);
    else g.drawLine (iz1, izz, iz2, izz);
    g.setColor (Color.lightGray);
    if (iz < nzinc) {
     for (int i = 1; i < nztick + 1; i++) {
      if (axisOrient == 1) {izz = izz + iztick;
       g.drawLine (izz, iz1, izz, iz2);
      } else {izz = izz - iztick;
       g.drawLine (iz1, izz, iz2, izz); 
      }
     }
    }
   }
   g.setColor (Color.black);
 }

/************************************************************************/
  void drawNumbs (int axisOrient, double zmin, double zmax,
   int nfield, int ndp, int nzinc, int nzind, double zOrigin, double amax, int lalign) {
/************************************************************************;
 * from class Paper
 * Purpose: draws numbers on the axes
 * Input arguments: iax - x axis = 1, y axis = 2
 *       zmax, zmin - maximum and minimum values
 *       nzinc - number of increments
 *       nzind - numbering index (power to which numbers will be raised)
 *       zorig - origin of other axis
 *       amax - maximum value on other axis
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++;
 */
   double zz, xx, yy;
   int ichw, ichh, ilen, jlen;
   int iside, ixx = 0, iyy = 0;
   StringBuffer number;
   String label;
   fontMetrics = g.getFontMetrics();
   ichw = fontMetrics.stringWidth("0");
   ichh = fontMetrics.getHeight();
   Font oldFont = g.getFont();
   int ixsize = getSize().width;
   int iysize = getSize().height;
   if (axisOrient == 1) {
    ilen = (nfield + 1) * ichw;
    jlen = (int)Math.rint ((double)ixsize / (double)nzinc);
   } else {
    ilen = ichh;
    jlen = (int)Math.rint ((double)iysize / (double)nzinc);
   }
   double space = Math.sqrt ((double) ilen / (double) jlen);
   iside = 1; if (amax <= 0) iside = -1; // numbering on other side of axis
   double zmult = Math.pow (10.0, -nzind);
   double zzmax = zmax * zmult, zzmin = zmin * zmult;
   zmult = Math.pow (10.0, nzind);
   double zinc = (zzmax - zzmin) / (double)nzinc;
   number = new StringBuffer (0);
   for (int iz = 0; iz < nzinc + 1; iz++) {
    zz = zzmin + iz * zinc;
    number.setLength (0);
    number.append (zz);
    if (Math.abs (zz) < 0.5 * zinc) {
     number.setLength (0); number.append (0.0);
    }
    ixx = 0; iyy = 0;
    if (axisOrient == 1) {
/*   x axis */
     lalign = 4; if (iside == -1) lalign = 6;
     xx = (zzmin + iz * zinc) * zmult; yy = zOrigin;
     ixx = ixOrigin + (int)Math.round (xx * xScale);
     iyy = getSize().height - iyOrigin - (int)Math.round (yy * yScale) + iside * ichh/2;
    } else {
/*   y axis */
     lalign = 2; if (iside == -1) lalign = 8;
     xx = zOrigin; yy = (zzmin + iz * zinc) * zmult;
     ixx = ixOrigin + (int)Math.round (xx * xScale) - iside * ichw/2;
     iyy = getSize().height - iyOrigin - (int)Math.round (yy * yScale);
    }
/*  truncate trailing portion of string */
    int l1 = number.length();
    label = number.toString(); int l2 = label.indexOf (".");
    int nn = 0; if (ndp > 0) nn = ndp; 
    if (l2 > 0) {
     if (l1 - l2 > nn) number.delete (l2 + nn + 1, l1);
    }
    label = number.toString();
/*  draw string */
    drawText (label, ixx, iyy, lalign);
   }
 }

/************************************************************************/
  void drawLabels (int axisOrient, int nzind, double zOrigin, int lalign) {
/************************************************************************;
 * from class Paper
 * Purpose: writes axis labels
 * Input arguments: iax - x axis = 1; y axis = 2
 *       nzind - numbering index (factor to which numbers are raised)
 *       zlabel - axis label
 *       zorig - origin
 *       xmin, xmax, ymin, ymax -  coordinates of corners
 *       lalign - string alignment  3  6  9
 *                                  2  5  8
 *                                  1  4  7 
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++;
 */
   StringBuffer label = new StringBuffer (0);
   String zLabel;
   if (axisOrient == 1) zLabel = xlabel; else zLabel = ylabel;
   label.append (zLabel);
   if (nzind != 0) label.append ("x 10^" + nzind);
   String alabel = label.toString();
   int istrwidth = fontMetrics.stringWidth(alabel);
   double xx, yy;
   int ixx = 0, iyy = 0;
   if (axisOrient == 1) {
/* x axis */
    lalign = 4; xx = 0.5 * (xmin + xmax); yy = zOrigin;
    ixx = ixOrigin + (int)Math.round (xx * xScale);
    iyy = getSize().height - iyOrigin - (int)Math.round (yy * yScale);
    if (ymax <= 0.0) iyy = iyy - ichHeight;
    else iyy = iyy + ichHeight;
   } else {
/* y axis */
    lalign = 6; xx = zOrigin; yy = ymax;
    ixx = ixOrigin + (int)Math.round (xx * xScale) - ichWidth;
    iyy = getSize().height - iyOrigin - (int)Math.round (yy * yScale) - ichHeight;
   }
   drawText (label.toString(), ixx, iyy, lalign);
 }

/***********************************************************************/
  void getKeySize () {
/**********************************************************************
 * from class Paper
 * Purpose: calculates size of key legend box
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   String symbol = " ";
   int maxlen = 0;
   int [] istrwidth = new int [4];
   for (int i = 1; i < 4; i++) {
    istrwidth [i] = fontMetrics.stringWidth (keylab [i]);
    if (istrwidth [i] > maxlen) maxlen = istrwidth [i];
   }
   maxlen = 0;
   for (int i = 1; i < 4; i++) maxlen = maxlen + istrwidth [i];
   keyBoxWidth = maxlen + 24 * ichWidth;
   keyBoxHeight = 2 * ichHeight;
 }  

/***********************************************************************/
  void setLineStyle (int lintyp) {
/**********************************************************************
 * from class Paper
 * Purpose: selects the linetype for lines
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   BasicStroke bs;
   float [] dashPattern;
   int lineWidth = 1;
   int cap = BasicStroke.CAP_BUTT;
   int join = BasicStroke.JOIN_MITER;

   switch (lintyp) {
    case 1: {bs = new BasicStroke(lineWidth, cap, join, 10); 
       g.setStroke(bs); break;}
    case 2: {dashPattern = new float [2];
       dashPattern [0] = 7; dashPattern [1] = 3;
       bs = new BasicStroke(lineWidth, cap, join, 10, dashPattern, 0);
       g.setStroke(bs); break;}
    case 3: {dashPattern = new float [2];
       dashPattern [0] = 3; dashPattern [1] = 7;
       bs = new BasicStroke(lineWidth, cap, join, 10, dashPattern, 0);
       g.setStroke(bs); break;}
    } 
 } 

/***********************************************************************/
  void setColour (int icol) {
/**********************************************************************
 * from class Paper
 * Purpose: selects the colour for lines and symbols
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   switch (icol) {
    case 1: g.setColor (Color.red); break;
    case 2: g.setColor (Color.blue); break;
    case 3: g.setColor (Color.magenta); break;
   } 
 } 

/***********************************************************************/
  String setMark (int mark) {
/**********************************************************************
 * from class Paper
 * Purpose: selects the colour for lines and symbols
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   String symbol = " ";
   switch (mark) {
    case 1: symbol = "$"; break; // filled star
    case 2: symbol = "%"; break; // filled triangle up
    case 3: symbol = "#"; break; // filled circle
   }
   return symbol;
 }
}

/***********************************************************************/
class Diagram extends Canvas {
/***********************************************************************/
  Fred fred; Gadgets choices;

/***********************************************************************/
  public Diagram (Fred fred, Gadgets choices) {
/***********************************************************************
 * Purpose: draws underwater section of hull
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   this.fred = fred;
   this.choices = choices;
   if (!fred.calculated) return;
  }


/***********************************************************************/
  public void paint (Graphics g) {
/***********************************************************************/
   g.setColor (Color.black);
   if (!fred.calculated) return;
   int ix0, iy0, ix1, iy1, ix2, iy2;
   int ixsect, iysect, ixprof, iyprof, ixplan, iyplan;
   double localx, localx2, localy, localz, shape1;
   double xlen = choices.length;
   double ylen = 0.5 * choices.beam + 5 * fred.draft;
   int ixgap = 10, iygap = 20;
   int ixsize = getSize().width;
   int ixlen = ixsize - 3 * ixgap;
   double scale = (double)ixlen / xlen;
   ixprof = 2 * ixgap; iyprof = 2 * iygap;
   ixplan = ixprof;
   iyplan = (int) (3.5*iygap + scale * fred.draft);
   g.drawString("Underwater profile - submerged part only", 10, 15);
   ix0 = 2 * ixgap; iy0 = 2 * iygap;
   ix1 = (int) (scale * fred.l1);
   iy1 = iy0 + (int) (scale * fred.draft);
   ix2 = (int) (scale * choices.length);
// profile top line
   g.drawLine (ix0, iy0, ix0 + ix2, iy0);
// profile centreline
   g.drawLine (ix0 + ix1, iy0, ix0 + ix1, iy1);
   iy0 = (int) (3 * iygap + scale * fred.draft);
   g.drawString ("Plan view at waterline", 10, iy0);
   iy0 = (int) (3.5*iygap + scale * fred.draft);
   iy1 = iy0 + (int) (0.5 * scale * choices.beam);
// plan top line
   g.drawLine (ix0, iy0, ix0 + ix2, iy0);
// plan centreline
   g.drawLine (ix0 + ix1, iy0, ix0 + ix1, iy1);
   iy0 = (int) (3.5*iygap + scale * (choices.beam + fred.draft));
   g.drawString ("Sections - below waterline only", 10, iy0);
   iy0 = 4*iygap + (int) (scale * (choices.beam + fred.draft));
   ix1 = (int) (scale * choices.beam);
   iy1 = (int) (scale * fred.draft);
// section topline
   g.drawLine (ixgap + ix1, iy0, ixgap + 3 * ix1, iy0);
// section centreline
   g.drawLine (ixgap + 2 * ix1, iy0, ixgap + 2 * ix1, iy0 + 2*iy1);
/* at each station along the length */
/* entry (front) section */
   shape1 = (fred.l1*fred.l1 - 0.25*choices.beam*choices.beam) / choices.beam;
   localx = 0.0;
   for (int ix = 0; ix < 11; ix++) {
    localx = 0.1 * ix * fred.l1;
/*  draw sections */
    localy = Math.sqrt (2*localx*fred.l1 - localx*localx + shape1*shape1) - shape1;
    localz = localy / (fred.alpha);
    ixsect = ixgap + (int) (4 * scale * (0.5 * choices.beam));
    iysect = 4*iygap + (int) (scale * (choices.beam + fred.draft));
    double temp1 = Math.pow (0.5 * localy, 2);
    double ycoord = 0.0;
    double temp3 = 0.0;
//  front sections
    for (int iy = 1; iy < 12; iy++) {
     temp3 = 0.025 * (11 - iy);
     if (temp3 > 0 ) ycoord = Math.sqrt (temp3) * localy;
     else ycoord = -Math.sqrt (-temp3) * localy;
     ix1 = (int) (4 * scale * (ycoord + 0.5 * choices.beam)) ;
     iy1 = (int) (4 * scale * Math.sqrt (temp1 - ycoord * ycoord) / fred.alpha);
     g.drawLine (ixsect, iysect, ix1 + ixgap, iy1 + 4*iygap + (int) (scale * (choices.beam + fred.draft)));
     ixsect = ix1 + ixgap;
     iysect = iy1 + 4*iygap + (int) (scale * (choices.beam + fred.draft));
    }
/*  draw profile */
    ix0 = 2 * ixgap; iy0 = 2 * iygap;
    ix1 = (int) (scale * fred.l1);
    ix2 = (int) (scale * localx);
    iy1 = (int) (scale * localz);
//  front profile curve
    g.drawLine (ixprof, iyprof, ix0 + ix2, iy0 + iy1);
    ixprof = ix0 + ix2; iyprof = iy0 + iy1;
/*  draw plan view */
    iy0 = (int) (3.5*iygap + scale * fred.draft);
    iy1 = (int) (scale * localy);
//  front plan curve
    g.drawLine (ixplan, iyplan, ix0 + ix2, iy0 + iy1);
    ixplan = ix0 + ix2; iyplan = iy0 + iy1;
   }
/* exit (rear) section */
   for (int ix = 0; ix < 11; ix++) {
    localx = fred.l1 + 0.1 * ix * fred.l2;
    localx2 = choices.length - localx;
/*  draw sections */
    localy = 0.5*choices.beam / (1 - fred.phi1) * (1 - Math.exp (-localx2 * Math.log(1.0/fred.phi1)/fred.l2));
    localz = localy / fred.alpha;
    ix1 = (int) (4 * scale * 0.5 * choices.beam) ;
    iy1 = (int) (4 * scale * fred.draft);
    iy1 = (int) (4 * scale * 0.5 * localz);
    ixsect = ix1 + ixgap;
    iysect = iy1 + 4*iygap + (int) (scale * (choices.beam + fred.draft));
    double temp1 = Math.pow (0.5 * localy, 2);
    double ycoord = 0.0, temp3 = 0.0;
//  rear sections
    for (int iy = 11; iy < 22; iy++) {
     temp3 = 0.025 * (11 - iy);
     if (temp3 > 0 ) ycoord = Math.sqrt (temp3) * localy;
     else ycoord = -Math.sqrt (-temp3) * localy;
     ix1 = (int) (4 * scale * (ycoord + 0.5 * choices.beam)) ;
     iy1 = (int) (4 * scale * Math.sqrt (temp1 - ycoord * ycoord) / fred.alpha);
     g.drawLine (ixsect, iysect, ix1 + ixgap, iy1 + 4*iygap + (int) (scale * (choices.beam + fred.draft)));
     ixsect = ix1 + ixgap;
     iysect = iy1 + 4*iygap + (int) (scale * (choices.beam + fred.draft));
    }
/*  draw profile */
    ix0 = 2 * ixgap; iy0 = 2 * iygap;
    ix1 = (int) (scale * fred.l1);
    ix2 = (int) (scale * localx);
    iy1 = (int) (scale * localz);
//  rear profile curve
    g.drawLine (ixprof, iyprof, ix0 + ix2, iy0 + iy1);
    ixprof = ix0 + ix2; iyprof = iy0 + iy1;
/*  draw plan view */
    iy0 = (int) (3.5*iygap + scale * fred.draft);
    iy1 = (int) (scale * localy);
//  rear plan curve
    g.drawLine (ixplan, iyplan, ix0 + ix2, iy0 + iy1);
    ixplan = ix0 + ix2; iyplan = iy0 + iy1;
   }
  }  
}

/***********************************************************************/
class Wireframe extends Canvas implements MouseListener,
      MouseMotionListener, Runnable {
/***********************************************************************/
  Fred fred; Gadgets choices;
  double xmin, xmax, ymin, ymax, zmin, zmax;
  int [][] tranCoord;
  int prevX, prevY;
  boolean transformed, painted = true;
  double xtheta, ytheta;
  Matrix mat, amat = new Matrix(), tmat = new Matrix();
  String message = null;

/***********************************************************************/
  public Wireframe (Fred fred, Gadgets choices) {
/***********************************************************************
 * Purpose: draws 3D wireframe of underwater section of hull
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   this.fred = fred;
   this.choices = choices;
   addMouseListener (this);
   addMouseMotionListener (this);
  }  

/***********************************************************************/
  public void paint(Graphics g) {
/***********************************************************************
 * From class Wireframe
 * Paint this model to a graphics context.  It uses the matrix associated
 * with this model to map from model space to screen space.
 * The next version of the browser should have double buffering,
 * which will make this *much* nicer
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
    g.setColor (Color.black);
    if (!fred.calculated) return;
    g.drawString ("Submerged part only", 10, 10);
    g.drawString ("Drag with left-hand mouse button pressed down to rotate the model", 10, getSize().height-30);
    if (fred.nvert > 0) {
     findBB(); // Find the bounding box of this model
     double xw = xmax - xmin;
     double xfac = 0.8 * getSize().width / xw;
/*   Reinitialize to the unit matrix */
     mat = new Matrix (); mat.unit ();     
/*   Translate the origin to centre of boat */
     mat.translate(-(xmin + xmax) / 2, -(ymin + ymax) / 2, -(zmin + zmax) / 2);
/*   Multiply this matrix by a second: M = M*R */
     mat.mult(amat);
/*   Scale along each axis independently */
     mat.scale (xfac, -xfac, 16 * xfac / getSize().width);
/*   Translate the origin */
     mat.translate (getSize().width / 2, getSize().height / 2, 8);
     transformed = false;
     if (fred.xcoord == null || fred.nvert <= 0) return;
/*   Transform all the points in this model */
     transform (g);
     if (fred.nvert <= 0) return;
     int nn = 2 * fred.nx + 1;
     if (2 * fred.ny > nn) nn = 2 * fred.ny;
     int xpoly [] = new int [nn];
     int ypoly [] = new int [nn];
     for (int i = 1; i < 2 * fred.nx + 1; i++) {
      int nl = 2 * fred.ny - 1;
      for (int j = 0; j < nl; j++) {
       int ip = fred.bline [(i-1)*(2*fred.nx-1) + j + 1];
       xpoly [j] = tranCoord [0][ip];
       ypoly [j] = tranCoord [1][ip];
      }
      for (int j = 0; j < nl - 1; j++) {
       g.drawLine(xpoly[j], ypoly[j], xpoly[j+1], ypoly[j+1]);
      }
/*    g.drawPolyline(xpoly, ypoly, nl);*/
     }
     for (int j = 1; j < 2 * fred.ny; j++) {
      int nb = 2 * fred.nx;
      for (int i = 0; i < nb; i++) {
/*     int ip = fred.lline [(j-1)*(2*fred.ny-1) + i + 1];*/
       int ip = fred.lline [(j-1)*(2*fred.ny) + i + 1];
       xpoly [i] = tranCoord [0][ip];
       ypoly [i] = tranCoord [1][ip];
      }
      for (int i = 0; i < nb - 1; i++) {
       g.drawLine(xpoly[i], ypoly[i], xpoly[i+1], ypoly[i+1]);
      }
/*    g.drawPolyline(xpoly, ypoly, nb);*/
     }
     setPainted();
    } else if (message != null) {
       g.drawString("Error in model:", 3, 20);
       g.drawString(message, 10, 40);
    }
  }
/***********************************************************************/
  void transform (Graphics g) {
/***********************************************************************
 * from class Wireframe
 * Transform all the points in this model
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   if (transformed || fred.nvert <= 0) return;
   if (tranCoord == null || tranCoord.length < fred.nvert) {
       tranCoord = new int [3][fred.nvert];
   }
/* Transform nvert points from coord into tran.  coord contains the input */
   tranCoord = mat.transform (fred.xcoord, fred.ycoord, fred.zcoord, fred.nvert);
   transformed = true;
 }
/***********************************************************************/
  void findBB() {
/***********************************************************************
 * From class Wireframe
 * Find the bounding box of this model
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   if (fred.nvert <= 0) return;
   double xmin = fred.xcoord[0], xmax = xmin;
   double ymin = fred.ycoord[0], ymax = ymin;
   double zmin = fred.zcoord[0], zmax = zmin;
   for (int i = 0; i < fred.nvert; i++) {
    if (fred.xcoord[i] < xmin) xmin = fred.xcoord[i];
    if (fred.xcoord[i] > xmax) xmax = fred.xcoord[i];
    if (fred.ycoord[i] < ymin) ymin = fred.ycoord[i];
    if (fred.ycoord[i] > ymax) ymax = fred.ycoord[i];
    if (fred.zcoord[i] < zmin) zmin = fred.zcoord[i];
    if (fred.zcoord[i] > zmax) zmax = fred.zcoord[i];
   }
   this.xmin = xmin; this.xmax = xmax;
   this.ymin = ymin; this.ymax = ymax;
   this.zmin = zmin; this.zmax = zmax;
 }
/***********************************************************************/
  public void init() {
/***********************************************************************
 * from class Wireframe
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   mat = new Matrix ();
/* rotate 20 degrees about the x axis */
   mat.xrot(20);
/* rotate 30 degrees about the y axis */
   mat.yrot(30);
/* rotate 20 degrees about the y axis */
   amat.yrot(20);
/* rotate 20 degrees about the x axis */
   amat.xrot(20);
   setSize(getSize().width <= 20 ? 400 : getSize().width,
           getSize().height <= 20 ? 400 : getSize().height);
 }

  public void mouseMoved (MouseEvent evt) {}
  public void mouseClicked (MouseEvent evt) {}
  public void mouseEntered (MouseEvent evt) {}
  public void mouseExited (MouseEvent evt) {}
  public void mouseReleased (MouseEvent evt) {}

/************************************************************************/
  public void mousePressed (MouseEvent evt) {
/***********************************************************************
 * from class Wireframe
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
    prevX = evt.getX(); prevY = evt.getY();
  } 

/***********************************************************************/
    public void mouseDragged (MouseEvent evt) {
/***********************************************************************
 * from class Wireframe
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   Graphics g;
/* Reinitialize to the unit matrix */
   int x = evt.getX(), y = evt.getY();
   tmat.unit();
   double xtheta = (prevY - y) * 360.0 / getSize().width;
   double ytheta = (x - prevX) * 360.0 / getSize().height;
/* rotate xtheta degrees about the x axis */
   tmat.xrot (xtheta);
/* rotate ytheta degrees about the y axis */
   tmat.yrot (ytheta);
/* Multiply this matrix by a second: M = M*R */
   amat.mult (tmat);
   if (painted) {painted = false; repaint();}
   prevX = x; prevY = y;
 }

/***********************************************************************/
  private synchronized void setPainted() {
/***********************************************************************/
   painted = true;
   notifyAll();
  }
/***********************************************************************/
  public void run() {
/***********************************************************************
 * from class Wireframe
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   try {
    Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
    Wireframe mesh = new Wireframe (fred, choices);
   } catch(Exception e) {
     message = e.toString();
   }
   repaint();
 }
}

/***********************************************************************/
class Matrix {
/***********************************************************************
 * A fairly conventional 3D matrix object that can transform sets of
 * 3D points and perform a variety of manipulations on the transform
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
   double trans [][] = new double [3][3];
   double orig [] = new double [3];
   static final double pi = 3.14159265;

/***********************************************************************/
   Matrix () {
/***********************************************************************
 * Create a new unit matrix
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
    unit ();
/*  for (int i = 0; i < 3; i++) trans [i][i] = 1.0f;*/
  }

/***********************************************************************/
   void scale (double xf, double yf, double zf) {
/***********************************************************************
 * from class Matrix
 * Scale along each axis independently
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
    for (int i = 0; i < 3; i++) {
     trans [0][i] *= xf; trans [1][i] *= yf; trans [2][i] *= zf;
    }
    orig [0] *= xf; orig [1] *= yf; orig [2] *= zf;
   }
/***********************************************************************/
   void translate (double x, double y, double z) {
/***********************************************************************
 * from class Matrix
 * Translate the origin
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
    orig [0] += x; orig [1] += y; orig [2] += z;
   }
/***********************************************************************/
   void yrot (double theta) {
/***********************************************************************
 * from class Matrix
 * rotate theta degrees about the y axis
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
    theta *= (pi / 180);
    double ct = Math.cos(theta); double st = Math.sin(theta);
    double Nxx = trans[0][0] * ct + trans[2][0] * st;
    double Nxy = trans[0][1] * ct + trans[2][1] * st;
    double Nxz = trans[0][2] * ct + trans[2][2] * st;
    double Nzx = trans[2][0] * ct - trans[0][0] * st;
    double Nzy = trans[2][1] * ct - trans[0][1] * st;
    double Nzz = trans[2][2] * ct - trans[0][2] * st;
    trans[0][0] = Nxx; trans[0][1] = Nxy; trans[0][2] = Nxz;
    trans[2][0] = Nzx; trans[2][1] = Nzy; trans[2][2] = Nzz;
    double Nxo = orig[0] * ct + orig[2] * st;
    double Nzo = orig[2] * ct - orig[0] * st;
    orig[0] = Nxo; orig[2] = Nzo;
   }

/***********************************************************************/
   void xrot (double theta) {
/***********************************************************************
 * from class Matrix
 * rotate theta degrees about the x axis
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
    theta *= (pi / 180);
    double ct = Math.cos(theta); double st = Math.sin(theta);
    double Nyx = trans[1][0] * ct + trans[2][0] * st;
    double Nyy = trans[1][1] * ct + trans[2][1] * st;
    double Nyz = trans[1][2] * ct + trans[2][2] * st;
    double Nzx = trans[2][0] * ct - trans[1][0] * st;
    double Nzy = trans[2][1] * ct - trans[1][1] * st;
    double Nzz = trans[2][2] * ct - trans[1][2] * st;
    trans[1][0] = Nyx; trans[1][1] = Nyy; trans[1][2] = Nyz;
    trans[2][0] = Nzx; trans[2][1] = Nzy; trans[2][2] = Nzz;
    double Nyo = orig[1] * ct + orig[2] * st;
    double Nzo = orig[2] * ct - orig[1] * st;
    orig[1] = Nyo; orig[2] = Nzo;
   }

/***********************************************************************/
   void mult (Matrix rhs) {
/***********************************************************************
 * from class Matrix
 * Multiply this matrix by a second: M = M*R
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
    double temp [][] = new double [3][3];
    double torig [] = new double [3];
    for (int j = 0; j < 3; j++) {
     for (int i = 0; i < 3; i++) {
      temp[i][j] = trans[0][j] * rhs.trans[i][0]
                 + trans[1][j] * rhs.trans[i][1]
                 + trans[2][j] * rhs.trans[i][2];
     }
     torig [j] = orig[0] * rhs.trans[j][0] + orig[1] * rhs.trans[j][1]
               + orig[2] * rhs.trans[j][2] + rhs.orig[j];
    }
    for (int j = 0; j < 3; j++) {
     for (int i = 0; i < 3; i++) trans[i][j] = temp [i][j];
     orig [j] = torig [j];
    }
   }
/***********************************************************************/
   void unit () {
/***********************************************************************
 * from class Matrix
 * Reinitialize to the unit matrix
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
    for (int i = 0; i < 3; i++) {orig [i] = 0;
     for (int j = 0; j < 3; j++) trans [i][j] = 0;
     trans [i][i] = 1;
    }
   }
/***********************************************************************/
   int [][] transform (double xcoord[], double ycoord[],
    double zcoord[], int nvert) {
/***********************************************************************
 * from class Matrix
 * Transform nvert points from coord into tran.  coord contains the input
 * coordinates in double prec. tran ends up holding the transformed
 * points as integers
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
    int [][] tranCoord = new int [3][nvert];
    double lxx = trans[0][0], lxy = trans[0][1], lxz = trans[0][2];
    double lyx = trans[1][0], lyy = trans[1][1], lyz = trans[1][2];
    double lzx = trans[2][0], lzy = trans[2][1], lzz = trans[2][2];
    double lxo = orig[0], lyo = orig[1], lzo = orig[2];
    for (int i = 1; i < nvert; i++) {
     double x = xcoord[i], y = ycoord[i], z = zcoord[i];
     tranCoord [0][i] = (int) (x * lxx + y * lxy + z * lxz + lxo);
     tranCoord [1][i] = (int) (x * lyx + y * lyy + z * lyz + lyo);
     tranCoord [2][i] = (int) (x * lzx + y * lzy + z * lzz + lzo);
    }
    return tranCoord;
   }
}
