import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.GraphicsConfiguration;

import javax.media.j3d.Alpha;
import javax.media.j3d.AmbientLight;
import javax.media.j3d.Appearance;
import javax.media.j3d.Background;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.Material;
import javax.media.j3d.QuadArray;
import javax.media.j3d.RotationInterpolator;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.TriangleFanArray;
import javax.media.j3d.TriangleStripArray;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.behaviors.vp.OrbitBehavior;
import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.universe.ViewingPlatform;

public class GearTest extends Applet {

  static final int defaultToothCount = 24;

  private int toothCount;

  private SimpleUniverse u = null;

  public BranchGroup createSceneGraph(int toothCount) {
    // Create the root of the branch graph
    BranchGroup objRoot = new BranchGroup();

    // Create a Transformgroup to scale all objects so they
    // appear in the scene.
    TransformGroup objScale = new TransformGroup();
    Transform3D t3d = new Transform3D();
    t3d.setScale(0.4);
    objScale.setTransform(t3d);
    objRoot.addChild(objScale);

    // Create a bounds for the background and lights
    BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
        100.0);

    // Set up the background
    Color3f bgColor = new Color3f(0.05f, 0.05f, 0.2f);
    Background bgNode = new Background(bgColor);
    bgNode.setApplicationBounds(bounds);
    objScale.addChild(bgNode);

    // Set up the global lights
    Color3f light1Color = new Color3f(1.0f, 1.0f, 0.9f);
    Vector3f light1Direction = new Vector3f(4.0f, -7.0f, -12.0f);
    Color3f light2Color = new Color3f(0.3f, 0.3f, 0.4f);
    Vector3f light2Direction = new Vector3f(-6.0f, -2.0f, -1.0f);
    Color3f ambientColor = new Color3f(0.1f, 0.1f, 0.1f);

    AmbientLight ambientLightNode = new AmbientLight(ambientColor);
    ambientLightNode.setInfluencingBounds(bounds);
    objScale.addChild(ambientLightNode);

    DirectionalLight light1 = new DirectionalLight(light1Color,
        light1Direction);
    light1.setInfluencingBounds(bounds);
    objScale.addChild(light1);

    DirectionalLight light2 = new DirectionalLight(light2Color,
        light2Direction);
    light2.setInfluencingBounds(bounds);
    objScale.addChild(light2);

    // Create the transform group node and initialize it to the
    // identity. Enable the TRANSFORM_WRITE capability so that
    // our behavior code can modify it at runtime. Add it to the
    // root of the subgraph.
    TransformGroup objTrans = new TransformGroup();
    objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    objScale.addChild(objTrans);

    // Create an Appearance.
    Appearance look = new Appearance();
    Color3f objColor = new Color3f(0.5f, 0.5f, 0.6f);
    Color3f black = new Color3f(0.0f, 0.0f, 0.0f);
    Color3f white = new Color3f(1.0f, 1.0f, 1.0f);
    look
        .setMaterial(new Material(objColor, black, objColor, white,
            100.0f));

    // Create a gear, add it to the scene graph.
    //  SpurGear gear = new SpurGear(toothCount, 1.0f, 0.2f,
    SpurGear gear = new SpurGearThinBody(toothCount, 1.0f, 0.2f, 0.05f,
        0.05f, 0.3f, 0.28f, look);
    objTrans.addChild(gear);

    // Create a new Behavior object that will rotate the object and
    // add it into the scene graph.
    Transform3D yAxis = new Transform3D();
    Alpha rotationAlpha = new Alpha(-1, Alpha.INCREASING_ENABLE, 0, 0,
        8000, 0, 0, 0, 0, 0);

    RotationInterpolator rotator = new RotationInterpolator(rotationAlpha,
        objTrans, yAxis, 0.0f, (float) Math.PI * 2.0f);
    rotator.setSchedulingBounds(bounds);
    objTrans.addChild(rotator);

    // Have Java 3D perform optimizations on this scene graph.
    objRoot.compile();

    return objRoot;
  }

  public GearTest() {
    this(defaultToothCount);
  }

  public GearTest(int toothCount) {
    this.toothCount = toothCount;
  }

  public void init() {
    setLayout(new BorderLayout());
    GraphicsConfiguration config = SimpleUniverse
        .getPreferredConfiguration();

    Canvas3D c = new Canvas3D(config);
    add("Center", c);

    // Create a simple scene and attach it to the virtual universe
    BranchGroup scene = createSceneGraph(toothCount);
    u = new SimpleUniverse(c);

    // This will move the ViewPlatform back a bit so the
    // objects in the scene can be viewed.
    u.getViewingPlatform().setNominalViewingTransform();

    u.addBranchGraph(scene);
  }

  public void destroy() {
    u.cleanup();
  }

  //
  // The following allows GearTest to be run as an application
  // as well as an applet
  //
  public static void main(String[] args) {
    int value;

    if (args.length > 1) {
      System.out.println("Usage: java GearTest [#teeth]");
      System.exit(0);
    } else if (args.length == 0) {
      new MainFrame(new GearTest(), 700, 700);
    } else {
      try {
        value = Integer.parseInt(args[0]);
      } catch (NumberFormatException e) {
        System.out.println("Illegal integer specified");
        System.out.println("Usage: java GearTest [#teeth]");
        value = 0;
        System.exit(0);
      }
      if (value <= 0) {
        System.out.println("Integer must be positive (> 0)");
        System.out.println("Usage: java GearBox [#teeth]");
        System.exit(0);
      }
      new MainFrame(new GearTest(value), 700, 700);
    }
  }
}

class Gear extends javax.media.j3d.TransformGroup {

  // Specifiers determining whether to generate outward facing normals or
  // inward facing normals.
  static final int OutwardNormals = 1;

  static final int InwardNormals = -1;

  // The number of teeth in the gear
  int toothCount;

  // Gear start differential angle. All gears are constructed with the
  // center of a tooth at Z-axis angle = 0.
  double gearStartAngle;

  // The Z-rotation angle to place the tooth center at theta = 0
  float toothTopCenterAngle;

  // The Z-rotation angle to place the valley center at theta = 0
  float valleyCenterAngle;

  // The angle about Z subtended by one tooth and its associated valley
  float circularPitchAngle;

  // Increment angles
  float toothValleyAngleIncrement;

  // Front and rear facing normals for the gear's body
  final Vector3f frontNormal = new Vector3f(0.0f, 0.0f, -1.0f);

  final Vector3f rearNormal = new Vector3f(0.0f, 0.0f, 1.0f);

  Gear(int toothCount) {
    this.toothCount = toothCount;
  }

  void addBodyDisks(float shaftRadius, float bodyOuterRadius,
      float thickness, Appearance look) {
    int gearBodySegmentVertexCount; // #(segments) per tooth-unit
    int gearBodyTotalVertexCount; // #(vertices) in a gear face
    int gearBodyStripCount[] = new int[1]; // per strip (1) vertex count

    // A ray from the gear center, used in normal calculations
    float xDirection, yDirection;

    // The x and y coordinates at each point of a facet and at each
    // point on the gear: at the shaft, the root of the teeth, and
    // the outer point of the teeth
    float xRoot0, yRoot0, xShaft0, yShaft0;
    float xRoot3, yRoot3, xShaft3, yShaft3;
    float xRoot4, yRoot4, xShaft4, yShaft4;

    // Temporary variables for storing coordinates and vectors
    Point3f coordinate = new Point3f(0.0f, 0.0f, 0.0f);

    // Gear start differential angle. All gears are constructed with the
    // center of a tooth at Z-axis angle = 0.
    double gearStartAngle = -1.0 * toothTopCenterAngle;

    // Temporaries that store start angle for each portion of tooth facet
    double toothStartAngle, toothTopStartAngle, toothDeclineStartAngle, 
   toothValleyStartAngle, nextToothStartAngle;

    Shape3D newShape;
    int index;

    // The z coordinates for the body disks
    final float frontZ = -0.5f * thickness;
    final float rearZ = 0.5f * thickness;

    /*
     * Construct the gear's front body (front facing torus disk) __2__ - | -
     * 4 - /| /- / / | /| \ 0\ / | / / > \ / | / | > \ / | / / | \ / ____|/ | >
     * \-- --__/ | 1 3 5
     *  
     */
    gearBodySegmentVertexCount = 4;
    gearBodyTotalVertexCount = 2 + gearBodySegmentVertexCount * toothCount;
    gearBodyStripCount[0] = gearBodyTotalVertexCount;

    TriangleStripArray frontGearBody = new TriangleStripArray(
        gearBodyTotalVertexCount, GeometryArray.COORDINATES
            | GeometryArray.NORMALS, gearBodyStripCount);

    xDirection = (float) Math.cos(gearStartAngle);
    yDirection = (float) Math.sin(gearStartAngle);
    xShaft0 = shaftRadius * xDirection;
    yShaft0 = shaftRadius * yDirection;
    xRoot0 = bodyOuterRadius * xDirection;
    yRoot0 = bodyOuterRadius * yDirection;

    coordinate.set(xRoot0, yRoot0, frontZ);
    frontGearBody.setCoordinate(0, coordinate);
    frontGearBody.setNormal(0, frontNormal);

    coordinate.set(xShaft0, yShaft0, frontZ);
    frontGearBody.setCoordinate(1, coordinate);
    frontGearBody.setNormal(1, frontNormal);

    for (int count = 0; count < toothCount; count++) {
      index = 2 + count * 4;
      toothStartAngle = gearStartAngle + circularPitchAngle
          * (double) count;
      toothValleyStartAngle = toothStartAngle + toothValleyAngleIncrement;
      nextToothStartAngle = toothStartAngle + circularPitchAngle;

      xDirection = (float) Math.cos(toothValleyStartAngle);
      yDirection = (float) Math.sin(toothValleyStartAngle);
      xShaft3 = shaftRadius * xDirection;
      yShaft3 = shaftRadius * yDirection;
      xRoot3 = bodyOuterRadius * xDirection;
      yRoot3 = bodyOuterRadius * yDirection;

      xDirection = (float) Math.cos(nextToothStartAngle);
      yDirection = (float) Math.sin(nextToothStartAngle);
      xShaft4 = shaftRadius * xDirection;
      yShaft4 = shaftRadius * yDirection;
      xRoot4 = bodyOuterRadius * xDirection;
      yRoot4 = bodyOuterRadius * yDirection;

      coordinate.set(xRoot3, yRoot3, frontZ);
      frontGearBody.setCoordinate(index, coordinate);
      frontGearBody.setNormal(index, frontNormal);

      coordinate.set(xShaft3, yShaft3, frontZ);
      frontGearBody.setCoordinate(index + 1, coordinate);
      frontGearBody.setNormal(index + 1, frontNormal);

      coordinate.set(xRoot4, yRoot4, frontZ);
      frontGearBody.setCoordinate(index + 2, coordinate);
      frontGearBody.setNormal(index + 2, frontNormal);

      coordinate.set(xShaft4, yShaft4, frontZ);
      frontGearBody.setCoordinate(index + 3, coordinate);
      frontGearBody.setNormal(index + 3, frontNormal);
    }
    newShape = new Shape3D(frontGearBody, look);
    this.addChild(newShape);

    // Construct the gear's rear body (rear facing torus disc)
    TriangleStripArray rearGearBody = new TriangleStripArray(
        gearBodyTotalVertexCount, GeometryArray.COORDINATES
            | GeometryArray.NORMALS, gearBodyStripCount);
    xDirection = (float) Math.cos(gearStartAngle);
    yDirection = (float) Math.sin(gearStartAngle);
    xShaft0 = shaftRadius * xDirection;
    yShaft0 = shaftRadius * yDirection;
    xRoot0 = bodyOuterRadius * xDirection;
    yRoot0 = bodyOuterRadius * yDirection;

    coordinate.set(xShaft0, yShaft0, rearZ);
    rearGearBody.setCoordinate(0, coordinate);
    rearGearBody.setNormal(0, rearNormal);

    coordinate.set(xRoot0, yRoot0, rearZ);
    rearGearBody.setCoordinate(1, coordinate);
    rearGearBody.setNormal(1, rearNormal);

    for (int count = 0; count < toothCount; count++) {
      index = 2 + count * 4;
      toothStartAngle = gearStartAngle + circularPitchAngle
          * (double) count;
      toothValleyStartAngle = toothStartAngle + toothValleyAngleIncrement;
      nextToothStartAngle = toothStartAngle + circularPitchAngle;

      xDirection = (float) Math.cos(toothValleyStartAngle);
      yDirection = (float) Math.sin(toothValleyStartAngle);
      xShaft3 = shaftRadius * xDirection;
      yShaft3 = shaftRadius * yDirection;
      xRoot3 = bodyOuterRadius * xDirection;
      yRoot3 = bodyOuterRadius * yDirection;

      xDirection = (float) Math.cos(nextToothStartAngle);
      yDirection = (float) Math.sin(nextToothStartAngle);
      xShaft4 = shaftRadius * xDirection;
      yShaft4 = shaftRadius * yDirection;
      xRoot4 = bodyOuterRadius * xDirection;
      yRoot4 = bodyOuterRadius * yDirection;

      coordinate.set(xShaft3, yShaft3, rearZ);
      rearGearBody.setCoordinate(index, coordinate);
      rearGearBody.setNormal(index, rearNormal);

      coordinate.set(xRoot3, yRoot3, rearZ);
      rearGearBody.setCoordinate(index + 1, coordinate);
      rearGearBody.setNormal(index + 1, rearNormal);

      coordinate.set(xShaft4, yShaft4, rearZ);
      rearGearBody.setCoordinate(index + 2, coordinate);
      rearGearBody.setNormal(index + 2, rearNormal);

      coordinate.set(xRoot4, yRoot4, rearZ);
      rearGearBody.setCoordinate(index + 3, coordinate);
      rearGearBody.setNormal(index + 3, rearNormal);

    }
    newShape = new Shape3D(rearGearBody, look);
    this.addChild(newShape);
  }

  void addCylinderSkins(float shaftRadius, float length, int normalDirection,
      Appearance look) {
    int insideShaftVertexCount; // #(vertices) for shaft
    int insideShaftStripCount[] = new int[1]; // #(vertices) in strip/strip
    double toothStartAngle, nextToothStartAngle, toothValleyStartAngle;

    // A ray from the gear center, used in normal calculations
    float xDirection, yDirection;

    // The z coordinates for the body disks
    final float frontZ = -0.5f * length;
    final float rearZ = 0.5f * length;

    // Temporary variables for storing coordinates, points, and vectors
    float xShaft3, yShaft3, xShaft4, yShaft4;
    Point3f coordinate = new Point3f(0.0f, 0.0f, 0.0f);
    Vector3f surfaceNormal = new Vector3f();

    Shape3D newShape;
    int index;
    int firstIndex;
    int secondIndex;

    /*
     * Construct gear's inside shaft cylinder First the tooth's up, flat
     * outer, and down distances Second the tooth's flat inner distance
     * 
     * Outward facing vertex order: 0_______2____4 | /| /| | / | / | | / | / |
     * |/______|/___| 1 3 5
     * 
     * Inward facing vertex order: 1_______3____5 |\ |\ | | \ | \ | | \ | \ |
     * |______\|___\| 0 2 4
     */
    insideShaftVertexCount = 4 * toothCount + 2;
    insideShaftStripCount[0] = insideShaftVertexCount;

    TriangleStripArray insideShaft = new TriangleStripArray(
        insideShaftVertexCount, GeometryArray.COORDINATES
            | GeometryArray.NORMALS, insideShaftStripCount);
    xShaft3 = shaftRadius * (float) Math.cos(gearStartAngle);
    yShaft3 = shaftRadius * (float) Math.sin(gearStartAngle);

    if (normalDirection == OutwardNormals) {
      surfaceNormal.set(1.0f, 0.0f, 0.0f);
      firstIndex = 1;
      secondIndex = 0;
    } else {
      surfaceNormal.set(-1.0f, 0.0f, 0.0f);
      firstIndex = 0;
      secondIndex = 1;
    }

    // Coordinate labeled 0 in the strip
    coordinate.set(shaftRadius, 0.0f, frontZ);
    insideShaft.setCoordinate(firstIndex, coordinate);
    insideShaft.setNormal(firstIndex, surfaceNormal);

    // Coordinate labeled 1 in the strip
    coordinate.set(shaftRadius, 0.0f, rearZ);
    insideShaft.setCoordinate(secondIndex, coordinate);
    insideShaft.setNormal(secondIndex, surfaceNormal);

    for (int count = 0; count < toothCount; count++) {
      index = 2 + count * 4;

      toothStartAngle = circularPitchAngle * (double) count;
      toothValleyStartAngle = toothStartAngle + toothValleyAngleIncrement;
      nextToothStartAngle = toothStartAngle + circularPitchAngle;

      xDirection = (float) Math.cos(toothValleyStartAngle);
      yDirection = (float) Math.sin(toothValleyStartAngle);
      xShaft3 = shaftRadius * xDirection;
      yShaft3 = shaftRadius * yDirection;
      if (normalDirection == OutwardNormals)
        surfaceNormal.set(xDirection, yDirection, 0.0f);
      else
        surfaceNormal.set(-xDirection, -yDirection, 0.0f);

      // Coordinate labeled 2 in the strip
      coordinate.set(xShaft3, yShaft3, frontZ);
      insideShaft.setCoordinate(index + firstIndex, coordinate);
      insideShaft.setNormal(index + firstIndex, surfaceNormal);

      // Coordinate labeled 3 in the strip
      coordinate.set(xShaft3, yShaft3, rearZ);
      insideShaft.setCoordinate(index + secondIndex, coordinate);
      insideShaft.setNormal(index + secondIndex, surfaceNormal);

      xDirection = (float) Math.cos(nextToothStartAngle);
      yDirection = (float) Math.sin(nextToothStartAngle);
      xShaft4 = shaftRadius * xDirection;
      yShaft4 = shaftRadius * yDirection;
      if (normalDirection == OutwardNormals)
        surfaceNormal.set(xDirection, yDirection, 0.0f);
      else
        surfaceNormal.set(-xDirection, -yDirection, 0.0f);

      // Coordinate labeled 4 in the strip
      coordinate.set(xShaft4, yShaft4, frontZ);
      insideShaft.setCoordinate(index + 2 + firstIndex, coordinate);
      insideShaft.setNormal(index + 2 + firstIndex, surfaceNormal);

      // Coordinate labeled 5 in the strip
      coordinate.set(xShaft4, yShaft4, rearZ);
      insideShaft.setCoordinate(index + 2 + secondIndex, coordinate);
      insideShaft.setNormal(index + 2 + secondIndex, surfaceNormal);

    }
    newShape = new Shape3D(insideShaft, look);
    this.addChild(newShape);
  }

  public float getToothTopCenterAngle() {
    return toothTopCenterAngle;
  }

  public float getValleyCenterAngle() {
    return valleyCenterAngle;
  }

  public float getCircularPitchAngle() {
    return circularPitchAngle;
  }
}

class GearBox extends Applet {

  static final int defaultToothCount = 48;

  private int toothCount;

  private SimpleUniverse u = null;

  public BranchGroup createGearBox(int toothCount) {
    Transform3D tempTransform = new Transform3D();

    // Create the root of the branch graph
    BranchGroup branchRoot = createBranchEnvironment();

    // Create a Transformgroup to scale all objects so they
    // appear in the scene.
    TransformGroup objScale = new TransformGroup();
    Transform3D t3d = new Transform3D();
    t3d.setScale(0.4);
    objScale.setTransform(t3d);
    branchRoot.addChild(objScale);

    // Create an Appearance.
    Appearance look = new Appearance();
    Color3f objColor = new Color3f(0.5f, 0.5f, 0.6f);
    Color3f black = new Color3f(0.0f, 0.0f, 0.0f);
    Color3f white = new Color3f(1.0f, 1.0f, 1.0f);
    look
        .setMaterial(new Material(objColor, black, objColor, white,
            100.0f));

    // Create the transform group node and initialize it to the
    // identity. Enable the TRANSFORM_WRITE capability so that
    // our behavior code can modify it at runtime. Add it to the
    // root of the subgraph.
    TransformGroup gearboxTrans = new TransformGroup();
    gearboxTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    gearboxTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
    objScale.addChild(gearboxTrans);

    // Create a bounds for the mouse behavior methods
    BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
        100.0);

    // Define the shaft base information
    int shaftCount = 4;
    int secondsPerRevolution = 8000;

    // Create the Shaft(s)
    Shaft shafts[] = new Shaft[shaftCount];
    TransformGroup shaftTGs[] = new TransformGroup[shaftCount];
    Alpha shaftAlphas[] = new Alpha[shaftCount];
    RotationInterpolator shaftRotors[] = new RotationInterpolator[shaftCount];
    Transform3D shaftAxis[] = new Transform3D[shaftCount];

    // Note: the following arrays we're incorporated to make changing
    // the gearbox easier.
    float shaftRatios[] = new float[shaftCount];
    shaftRatios[0] = 1.0f;
    shaftRatios[1] = 0.5f;
    shaftRatios[2] = 0.75f;
    shaftRatios[3] = 5.0f;

    float shaftRadius[] = new float[shaftCount];
    shaftRadius[0] = 0.2f;
    shaftRadius[1] = 0.2f;
    shaftRadius[2] = 0.2f;
    shaftRadius[3] = 0.2f;

    float shaftLength[] = new float[shaftCount];
    shaftLength[0] = 1.8f;
    shaftLength[1] = 0.8f;
    shaftLength[2] = 0.8f;
    shaftLength[3] = 0.8f;

    float shaftDirection[] = new float[shaftCount];
    shaftDirection[0] = 1.0f;
    shaftDirection[1] = -1.0f;
    shaftDirection[2] = 1.0f;
    shaftDirection[3] = -1.0f;

    Vector3d shaftPlacement[] = new Vector3d[shaftCount];
    shaftPlacement[0] = new Vector3d(-0.75, -0.9, 0.0);
    shaftPlacement[1] = new Vector3d(0.75, -0.9, 0.0);
    shaftPlacement[2] = new Vector3d(0.75, 0.35, 0.0);
    shaftPlacement[3] = new Vector3d(-0.75, 0.60, -0.7);

    // Create the shafts.
    for (int i = 0; i < shaftCount; i++) {
      shafts[i] = new Shaft(shaftRadius[i], shaftLength[i], 25, look);
    }

    // Create a transform group node for placing each shaft
    for (int i = 0; i < shaftCount; i++) {
      shaftTGs[i] = new TransformGroup();
      gearboxTrans.addChild(shaftTGs[i]);
      shaftTGs[i].getTransform(tempTransform);
      tempTransform.setTranslation(shaftPlacement[i]);
      shaftTGs[i].setTransform(tempTransform);
      shaftTGs[i].addChild(shafts[i]);
    }

    // Add rotation interpolators to rotate the shaft in the appropriate
    // direction and at the appropriate rate
    for (int i = 0; i < shaftCount; i++) {
      shaftAlphas[i] = new Alpha(-1, Alpha.INCREASING_ENABLE, 0, 0,
          (long) (secondsPerRevolution * shaftRatios[i]), 0, 0, 0, 0,
          0);
      shaftAxis[i] = new Transform3D();
      shaftAxis[i].rotX(Math.PI / 2.0);
      shaftRotors[i] = new RotationInterpolator(shaftAlphas[i],
          shafts[i], shaftAxis[i], 0.0f, shaftDirection[i]
              * (float) Math.PI * 2.0f);
      shaftRotors[i].setSchedulingBounds(bounds);
      shaftTGs[i].addChild(shaftRotors[i]);
    }

    // Define the gear base information. Again, these arrays exist to
    // make the process of changing the GearBox via an editor faster
    int gearCount = 5;
    float valleyToCircularPitchRatio = .15f;
    float pitchCircleRadius = 1.0f;
    float addendum = 0.05f;
    float dedendum = 0.05f;
    float gearThickness = 0.3f;
    float toothTipThickness = 0.27f;

    // Create an array of gears and their associated information
    SpurGear gears[] = new SpurGear[gearCount];
    TransformGroup gearTGs[] = new TransformGroup[gearCount];

    int gearShaft[] = new int[gearCount];
    gearShaft[0] = 0;
    gearShaft[1] = 1;
    gearShaft[2] = 2;
    gearShaft[3] = 0;
    gearShaft[4] = 3;

    float ratio[] = new float[gearCount];
    ratio[0] = 1.0f;
    ratio[1] = 0.5f;
    ratio[2] = 0.75f;
    ratio[3] = 0.25f;
    ratio[4] = 1.25f;

    Vector3d placement[] = new Vector3d[gearCount];
    placement[0] = new Vector3d(0.0, 0.0, 0.0);
    placement[1] = new Vector3d(0.0, 0.0, 0.0);
    placement[2] = new Vector3d(0.0, 0.0, 0.0);
    placement[3] = new Vector3d(0.0, 0.0, -0.7);
    placement[4] = new Vector3d(0.0, 0.0, 0.0);

    // Create the gears.
    for (int i = 0; i < gearCount; i++) {
      gears[i] = new SpurGearThinBody(
          ((int) ((float) toothCount * ratio[i])), pitchCircleRadius
              * ratio[i], shaftRadius[0], addendum, dedendum,
          gearThickness, toothTipThickness,
          valleyToCircularPitchRatio, look);
    }

    // Create a transform group node for arranging the gears on a shaft
    // and attach the gear to its associated shaft
    for (int i = 0; i < gearCount; i++) {
      gearTGs[i] = new TransformGroup();
      gearTGs[i].getTransform(tempTransform);
      tempTransform
          .rotZ((shaftDirection[gearShaft[i]] == -1.0) ? gears[i]
              .getCircularPitchAngle()
              / -2.0f : 0.0f);
      tempTransform.setTranslation(placement[i]);
      gearTGs[i].setTransform(tempTransform);
      gearTGs[i].addChild(gears[i]);
      shafts[gearShaft[i]].addChild(gearTGs[i]);
    }

    // Have Java 3D perform optimizations on this scene graph.
    branchRoot.compile();

    return branchRoot;
  }

  BranchGroup createBranchEnvironment() {
    // Create the root of the branch graph
    BranchGroup branchRoot = new BranchGroup();

    // Create a bounds for the background and lights
    BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
        100.0);

    // Set up the background
    Color3f bgColor = new Color3f(0.05f, 0.05f, 0.5f);
    Background bgNode = new Background(bgColor);
    bgNode.setApplicationBounds(bounds);
    branchRoot.addChild(bgNode);

    // Set up the ambient light
    Color3f ambientColor = new Color3f(0.1f, 0.1f, 0.1f);
    AmbientLight ambientLightNode = new AmbientLight(ambientColor);
    ambientLightNode.setInfluencingBounds(bounds);
    branchRoot.addChild(ambientLightNode);

    // Set up the directional lights
    Color3f light1Color = new Color3f(1.0f, 1.0f, 0.9f);
    Vector3f light1Direction = new Vector3f(1.0f, 1.0f, 1.0f);
    Color3f light2Color = new Color3f(1.0f, 1.0f, 0.9f);
    Vector3f light2Direction = new Vector3f(-1.0f, -1.0f, -1.0f);

    DirectionalLight light1 = new DirectionalLight(light1Color,
        light1Direction);
    light1.setInfluencingBounds(bounds);
    branchRoot.addChild(light1);

    DirectionalLight light2 = new DirectionalLight(light2Color,
        light2Direction);
    light2.setInfluencingBounds(bounds);
    branchRoot.addChild(light2);

    return branchRoot;
  }

  public GearBox() {
    this(defaultToothCount);
  }

  public GearBox(int toothCount) {
    this.toothCount = toothCount;
  }

  public void init() {
    setLayout(new BorderLayout());
    GraphicsConfiguration config = SimpleUniverse
        .getPreferredConfiguration();

    Canvas3D c = new Canvas3D(config);
    add("Center", c);

    // Create the gearbox and attach it to the virtual universe
    BranchGroup scene = createGearBox(toothCount);
    u = new SimpleUniverse(c);

    // add mouse behaviors to the ViewingPlatform
    ViewingPlatform viewingPlatform = u.getViewingPlatform();

    // This will move the ViewPlatform back a bit so the
    // objects in the scene can be viewed.
    viewingPlatform.setNominalViewingTransform();

    // add orbit behavior to the ViewingPlatform
    OrbitBehavior orbit = new OrbitBehavior(c, OrbitBehavior.REVERSE_ALL);
    BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
        100.0);
    orbit.setSchedulingBounds(bounds);
    viewingPlatform.setViewPlatformBehavior(orbit);

    u.addBranchGraph(scene);
  }

  public void destroy() {
    u.cleanup();
  }

  //
  // The following allows GearBox to be run as an application
  // as well as an applet
  //
  public static void main(String[] args) {
    int value;

    if (args.length > 1) {
      System.out.println("Usage: java GearBox  #teeth (LCD 4)");
      System.exit(0);
    } else if (args.length == 0) {
      new MainFrame(new GearBox(), 700, 700);
    } else {
      try {
        value = Integer.parseInt(args[0]);  } catch (NumberFormatException e) {
        System.out.println("Illegal integer specified");
        System.out.println("Usage: java GearBox  #teeth (LCD 4)");
        value = 0;
        System.exit(0);
      }
      if (value <= 0 | (value % 4) != 0) {
        System.out.println("Integer not a positive multiple of 4");
        System.out.println("Usage: java GearBox  #teeth (LCD 4)");
        System.exit(0);
      }
      new MainFrame(new GearBox(value), 700, 700);
    }
  }
}

class SpurGear extends Gear {

  float toothTopAngleIncrement;

  float toothDeclineAngleIncrement;

  float rootRadius;

  float outsideRadius;

  //The angle subtended by the ascending or descending portion of a tooth
  float circularToothEdgeAngle;

  // The angle subtended by a flat (either a tooth top or a valley
  // between teeth
  float circularToothFlatAngle;

  /**
   * internal constructor for SpurGear, used by subclasses to establish
   * SpurGear's required state
   * 
   * @return a new spur gear that contains sufficient information to continue
   *         building
   * @param toothCount
   *            number of teeth
   * @param pitchCircleRadius
   *            radius at center of teeth
   * @param addendum
   *            distance from pitch circle to top of teeth
   * @param dedendum
   *            distance from pitch circle to root of teeth
   * @param toothToValleyAngleRatio
   *            the ratio of the angle subtended by the tooth to the angle
   *            subtended by the valley (must be <= .25)
   */
  SpurGear(int toothCount, float pitchCircleRadius, float addendum,
      float dedendum, float toothToValleyAngleRatio) {

    super(toothCount);

    // The angle about Z subtended by one tooth and its associated valley
    circularPitchAngle = (float) (2.0 * Math.PI / (double) toothCount);

    // The angle subtended by a flat (either a tooth top or a valley
    // between teeth
    circularToothFlatAngle = circularPitchAngle * toothToValleyAngleRatio;

    //The angle subtended by the ascending or descending portion of a tooth
    circularToothEdgeAngle = circularPitchAngle / 2.0f
        - circularToothFlatAngle;

    // Increment angles
    toothTopAngleIncrement = circularToothEdgeAngle;
    toothDeclineAngleIncrement = toothTopAngleIncrement
        + circularToothFlatAngle;
    toothValleyAngleIncrement = toothDeclineAngleIncrement
        + circularToothEdgeAngle;

    // Differential angles for offsetting to the center of tooth's top
    // and valley
    toothTopCenterAngle = toothTopAngleIncrement + circularToothFlatAngle
        / 2.0f;
    valleyCenterAngle = toothValleyAngleIncrement + circularToothFlatAngle
        / 2.0f;

    // Gear start differential angle. All gears are constructed with the
    // center of a tooth at Z-axis angle = 0.
    gearStartAngle = -1.0 * toothTopCenterAngle;

    // The radial distance to the root and top of the teeth, respectively
    rootRadius = pitchCircleRadius - dedendum;
    outsideRadius = pitchCircleRadius + addendum;

    // Allow this object to spin. etc.
    this.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
  }

  /**
   * Construct a SpurGear;
   * 
   * @return a new spur gear that conforms to the input paramters
   * @param toothCount
   *            number of teeth
   * @param pitchCircleRadius
   *            radius at center of teeth
   * @param shaftRadius
   *            radius of hole at center
   * @param addendum
   *            distance from pitch circle to top of teeth
   * @param dedendum
   *            distance from pitch circle to root of teeth
   * @param gearThickness
   *            thickness of the gear
   */
  public SpurGear(int toothCount, float pitchCircleRadius, float shaftRadius,
      float addendum, float dedendum, float gearThickness) {
    this(toothCount, pitchCircleRadius, shaftRadius, addendum, dedendum,
        gearThickness, gearThickness, 0.25f, null);
  }

  /**
   * Construct a SpurGear;
   * 
   * @return a new spur gear that conforms to the input paramters
   * @param toothCount
   *            number of teeth
   * @param pitchCircleRadius
   *            radius at center of teeth
   * @param shaftRadius
   *            radius of hole at center
   * @param addendum
   *            distance from pitch circle to top of teeth
   * @param dedendum
   *            distance from pitch circle to root of teeth
   * @param gearThickness
   *            thickness of the gear
   * @param look
   *            the gear's appearance
   */
  public SpurGear(int toothCount, float pitchCircleRadius, float shaftRadius,
      float addendum, float dedendum, float gearThickness, Appearance look) {
    this(toothCount, pitchCircleRadius, shaftRadius, addendum, dedendum,
        gearThickness, gearThickness, 0.25f, look);
  }

  /**
   * Construct a SpurGear;
   * 
   * @return a new spur gear that conforms to the input paramters
   * @param toothCount
   *            number of teeth
   * @param pitchCircleRadius
   *            radius at center of teeth
   * @param shaftRadius
   *            radius of hole at center
   * @param addendum
   *            distance from pitch circle to top of teeth
   * @param dedendum
   *            distance from pitch circle to root of teeth
   * @param gearThickness
   *            thickness of the gear
   * @param toothTipThickness
   *            thickness of the tip of the tooth
   * @param look
   *            the gear's appearance
   */
  public SpurGear(int toothCount, float pitchCircleRadius, float shaftRadius,
      float addendum, float dedendum, float gearThickness,
      float toothTipThickness, Appearance look) {
    this(toothCount, pitchCircleRadius, shaftRadius, addendum, dedendum,
        gearThickness, toothTipThickness, 0.25f, look);
  }

  /**
   * Construct a SpurGear;
   * 
   * @return a new spur gear that conforms to the input paramters
   * @param toothCount
   *            number of teeth
   * @param pitchCircleRadius
   *            radius at center of teeth
   * @param shaftRadius
   *            radius of hole at center
   * @param addendum
   *            distance from pitch circle to top of teeth
   * @param dedendum
   *            distance from pitch circle to root of teeth
   * @param gearThickness
   *            thickness of the gear
   * @param toothTipThickness
   *            thickness of the tip of the tooth
   * @param toothToValleyAngleRatio
   *            the ratio of the angle subtended by the tooth to the angle
   *            subtended by the valley (must be <= .25)
   * @param look
   *            the gear's appearance object
   */
  public SpurGear(int toothCount, float pitchCircleRadius, float shaftRadius,
      float addendum, float dedendum, float gearThickness,
      float toothTipThickness, float toothToValleyAngleRatio,
      Appearance look) {

    this(toothCount, pitchCircleRadius, addendum, dedendum,
        toothToValleyAngleRatio);

    // Generate the gear's body disks
    addBodyDisks(shaftRadius, rootRadius, gearThickness, look);

    // Generate the gear's interior shaft
    addCylinderSkins(shaftRadius, gearThickness, InwardNormals, look);

    // Generate the gear's teeth
    addTeeth(pitchCircleRadius, rootRadius, outsideRadius, gearThickness,
        toothTipThickness, toothToValleyAngleRatio, look);
  }

  /**
   * Construct a SpurGear's teeth by adding the teeth shape nodes
   * 
   * @param pitchCircleRadius
   *            radius at center of teeth
   * @param rootRadius
   *            distance from pitch circle to top of teeth
   * @param outsideRadius
   *            distance from pitch circle to root of teeth
   * @param gearThickness
   *            thickness of the gear
   * @param toothTipThickness
   *            thickness of the tip of the tooth
   * @param toothToValleyAngleRatio
   *            the ratio of the angle subtended by the tooth to the angle
   *            subtended by the valley (must be <= .25)
   * @param look
   *            the gear's appearance object
   */
  void addTeeth(float pitchCircleRadius, float rootRadius,
      float outsideRadius, float gearThickness, float toothTipThickness,
      float toothToValleyAngleRatio, Appearance look) {
    int index;
    Shape3D newShape;

    // Temporaries that store start angle for each portion of tooth facet
    double toothStartAngle, toothTopStartAngle, toothDeclineStartAngle, 
    toothValleyStartAngle, nextToothStartAngle;

    // The x and y coordinates at each point of a facet and at each
    // point on the gear: at the shaft, the root of the teeth, and
    // the outer point of the teeth
    float xRoot0, yRoot0;
    float xOuter1, yOuter1;
    float xOuter2, yOuter2;
    float xRoot3, yRoot3;
    float xRoot4, yRoot4;

    // The z coordinates for the gear
    final float frontZ = -0.5f * gearThickness;
    final float rearZ = 0.5f * gearThickness;

    // The z coordinates for the tooth tip of the gear
    final float toothTipFrontZ = -0.5f * toothTipThickness;
    final float toothTipRearZ = 0.5f * toothTipThickness;

    int toothFacetVertexCount; // #(vertices) per tooth facet
    int toothFacetCount; // #(facets) per tooth
    int toothFaceTotalVertexCount; // #(vertices) in all teeth
    int toothFaceStripCount[] = new int[toothCount];
    // per tooth vertex count
    int topVertexCount; // #(vertices) for teeth tops
    int topStripCount[] = new int[1]; // #(vertices) in strip/strip

    // Front and rear facing normals for the teeth faces
    Vector3f frontToothNormal = new Vector3f(0.0f, 0.0f, -1.0f);
    Vector3f rearToothNormal = new Vector3f(0.0f, 0.0f, 1.0f);

    // Normals for teeth tops up incline, tooth top, and down incline
    Vector3f leftNormal = new Vector3f(-1.0f, 0.0f, 0.0f);
    Vector3f rightNormal = new Vector3f(1.0f, 0.0f, 0.0f);
    Vector3f outNormal = new Vector3f(1.0f, 0.0f, 0.0f);
    Vector3f inNormal = new Vector3f(-1.0f, 0.0f, 0.0f);

    // Temporary variables for storing coordinates and vectors
    Point3f coordinate = new Point3f(0.0f, 0.0f, 0.0f);
    Point3f tempCoordinate1 = new Point3f(0.0f, 0.0f, 0.0f);
    Point3f tempCoordinate2 = new Point3f(0.0f, 0.0f, 0.0f);
    Point3f tempCoordinate3 = new Point3f(0.0f, 0.0f, 0.0f);
    Vector3f tempVector1 = new Vector3f(0.0f, 0.0f, 0.0f);
    Vector3f tempVector2 = new Vector3f(0.0f, 0.0f, 0.0f);    /*
     * Construct the gear's front facing teeth facets 0______2 / /\ / / \ / / \
     * //___________\ 1 3
     */
    toothFacetVertexCount = 4;
    toothFaceTotalVertexCount = toothFacetVertexCount * toothCount;
    for (int i = 0; i < toothCount; i++)
      toothFaceStripCount[i] = toothFacetVertexCount;

    TriangleStripArray frontGearTeeth = new TriangleStripArray(
        toothFaceTotalVertexCount, GeometryArray.COORDINATES
            | GeometryArray.NORMALS, toothFaceStripCount);

    for (int count = 0; count < toothCount; count++) {
      index = count * toothFacetVertexCount;

      toothStartAngle = gearStartAngle + circularPitchAngle
          * (double) count;
      toothTopStartAngle = toothStartAngle + toothTopAngleIncrement;
      toothDeclineStartAngle = toothStartAngle
          + toothDeclineAngleIncrement;
      toothValleyStartAngle = toothStartAngle + toothValleyAngleIncrement;

      xRoot0 = rootRadius * (float) Math.cos(toothStartAngle);
      yRoot0 = rootRadius * (float) Math.sin(toothStartAngle);
      xOuter1 = outsideRadius * (float) Math.cos(toothTopStartAngle);
      yOuter1 = outsideRadius * (float) Math.sin(toothTopStartAngle);
      xOuter2 = outsideRadius * (float) Math.cos(toothDeclineStartAngle);
      yOuter2 = outsideRadius * (float) Math.sin(toothDeclineStartAngle);
      xRoot3 = rootRadius * (float) Math.cos(toothValleyStartAngle);
      yRoot3 = rootRadius * (float) Math.sin(toothValleyStartAngle);

      tempCoordinate1.set(xRoot0, yRoot0, frontZ);
      tempCoordinate2.set(xRoot3, yRoot3, frontZ);
      tempVector1.sub(tempCoordinate2, tempCoordinate1);

      tempCoordinate2.set(xOuter1, yOuter1, toothTipFrontZ);
      tempVector2.sub(tempCoordinate2, tempCoordinate1);

      frontToothNormal.cross(tempVector1, tempVector2);
      frontToothNormal.normalize();

      coordinate.set(xOuter1, yOuter1, toothTipFrontZ);
      frontGearTeeth.setCoordinate(index, coordinate);
      frontGearTeeth.setNormal(index, frontToothNormal);

      coordinate.set(xRoot0, yRoot0, frontZ);
      frontGearTeeth.setCoordinate(index + 1, coordinate);
      frontGearTeeth.setNormal(index + 1, frontToothNormal);

      coordinate.set(xOuter2, yOuter2, toothTipFrontZ);
      frontGearTeeth.setCoordinate(index + 2, coordinate);
      frontGearTeeth.setNormal(index + 2, frontToothNormal);

      coordinate.set(xRoot3, yRoot3, frontZ);
      frontGearTeeth.setCoordinate(index + 3, coordinate);
      frontGearTeeth.setNormal(index + 3, frontToothNormal);
    }
    newShape = new Shape3D(frontGearTeeth, look);
    this.addChild(newShape);

    /*
     * Construct the gear's rear facing teeth facets (Using Quads) 1______2 / \ / \ / \
     * /____________\ 0 3
     */
    toothFacetVertexCount = 4;
    toothFaceTotalVertexCount = toothFacetVertexCount * toothCount;

    QuadArray rearGearTeeth = new QuadArray(toothCount
        * toothFacetVertexCount, GeometryArray.COORDINATES
        | GeometryArray.NORMALS);

    for (int count = 0; count < toothCount; count++) {

      index = count * toothFacetVertexCount;
      toothStartAngle = gearStartAngle + circularPitchAngle
          * (double) count;
      toothTopStartAngle = toothStartAngle + toothTopAngleIncrement;
      toothDeclineStartAngle = toothStartAngle
          + toothDeclineAngleIncrement;
      toothValleyStartAngle = toothStartAngle + toothValleyAngleIncrement;

      xRoot0 = rootRadius * (float) Math.cos(toothStartAngle);
      yRoot0 = rootRadius * (float) Math.sin(toothStartAngle);
      xOuter1 = outsideRadius * (float) Math.cos(toothTopStartAngle);
      yOuter1 = outsideRadius * (float) Math.sin(toothTopStartAngle);
      xOuter2 = outsideRadius * (float) Math.cos(toothDeclineStartAngle);
      yOuter2 = outsideRadius * (float) Math.sin(toothDeclineStartAngle);
      xRoot3 = rootRadius * (float) Math.cos(toothValleyStartAngle);
      yRoot3 = rootRadius * (float) Math.sin(toothValleyStartAngle);

      tempCoordinate1.set(xRoot0, yRoot0, rearZ);
      tempCoordinate2.set(xRoot3, yRoot3, rearZ);
      tempVector1.sub(tempCoordinate2, tempCoordinate1);
      tempCoordinate2.set(xOuter1, yOuter1, toothTipRearZ);
      tempVector2.sub(tempCoordinate2, tempCoordinate1);
      rearToothNormal.cross(tempVector2, tempVector1);
      rearToothNormal.normalize();

      coordinate.set(xRoot0, yRoot0, rearZ);
      rearGearTeeth.setCoordinate(index, coordinate);
      rearGearTeeth.setNormal(index, rearToothNormal);

      coordinate.set(xOuter1, yOuter1, toothTipRearZ);
      rearGearTeeth.setCoordinate(index + 1, coordinate);
      rearGearTeeth.setNormal(index + 1, rearToothNormal);

      coordinate.set(xOuter2, yOuter2, toothTipRearZ);
      rearGearTeeth.setCoordinate(index + 2, coordinate);
      rearGearTeeth.setNormal(index + 2, rearToothNormal);

      coordinate.set(xRoot3, yRoot3, rearZ);
      rearGearTeeth.setCoordinate(index + 3, coordinate);
      rearGearTeeth.setNormal(index + 3, rearToothNormal);

    }
    newShape = new Shape3D(rearGearTeeth, look);
    this.addChild(newShape);

    /*
     * Construct the gear's top teeth faces (As seen from above) Root0
     * Outer1 Outer2 Root3 Root4 (RearZ) 0_______3 2_______5 4_______7
     * 6_______9 |0 3| |4 7| |8 11| |12 15| | | | | | | | | | | | | | | | |
     * |1_____2| |5_____6| |9____10| |13___14| 1 2 3 4 5 6 7 8 Root0 Outer1
     * Outer2 Root3 Root4 (FrontZ)
     * 
     * Quad 0123 uses a left normal Quad 2345 uses an out normal Quad 4567
     * uses a right normal Quad 6789 uses an out normal
     */
    topVertexCount = 8 * toothCount + 2;
    topStripCount[0] = topVertexCount;

    toothFacetVertexCount = 4;
    toothFacetCount = 4;

    QuadArray topGearTeeth = new QuadArray(toothCount
        * toothFacetVertexCount * toothFacetCount,
        GeometryArray.COORDINATES | GeometryArray.NORMALS);

    for (int count = 0; count < toothCount; count++) {
      index = count * toothFacetCount * toothFacetVertexCount;
      toothStartAngle = gearStartAngle + circularPitchAngle
          * (double) count;
      toothTopStartAngle = toothStartAngle + toothTopAngleIncrement;
      toothDeclineStartAngle = toothStartAngle
          + toothDeclineAngleIncrement;
      toothValleyStartAngle = toothStartAngle + toothValleyAngleIncrement;
      nextToothStartAngle = toothStartAngle + circularPitchAngle;

      xRoot0 = rootRadius * (float) Math.cos(toothStartAngle);
      yRoot0 = rootRadius * (float) Math.sin(toothStartAngle);
      xOuter1 = outsideRadius * (float) Math.cos(toothTopStartAngle);
      yOuter1 = outsideRadius * (float) Math.sin(toothTopStartAngle);
      xOuter2 = outsideRadius * (float) Math.cos(toothDeclineStartAngle);
      yOuter2 = outsideRadius * (float) Math.sin(toothDeclineStartAngle);
      xRoot3 = rootRadius * (float) Math.cos(toothValleyStartAngle);
      yRoot3 = rootRadius * (float) Math.sin(toothValleyStartAngle);
      xRoot4 = rootRadius * (float) Math.cos(nextToothStartAngle);
      yRoot4 = rootRadius * (float) Math.sin(nextToothStartAngle);

      // Compute normal for quad 1
      tempCoordinate1.set(xRoot0, yRoot0, frontZ);
      tempCoordinate2.set(xOuter1, yOuter1, toothTipFrontZ);
      tempVector1.sub(tempCoordinate2, tempCoordinate1);
      leftNormal.cross(frontNormal, tempVector1);
      leftNormal.normalize();

      // Coordinate labeled 0 in the quad
      coordinate.set(xRoot0, yRoot0, rearZ);
      topGearTeeth.setCoordinate(index, coordinate);
      topGearTeeth.setNormal(index, leftNormal);

      // Coordinate labeled 1 in the quad
      coordinate.set(tempCoordinate1);
      topGearTeeth.setCoordinate(index + 1, coordinate);
      topGearTeeth.setNormal(index + 1, leftNormal);

      // Coordinate labeled 2 in the quad
      topGearTeeth.setCoordinate(index + 2, tempCoordinate2);
      topGearTeeth.setNormal(index + 2, leftNormal);
      topGearTeeth.setCoordinate(index + 5, tempCoordinate2);

      // Coordinate labeled 3 in the quad
      coordinate.set(xOuter1, yOuter1, toothTipRearZ);
      topGearTeeth.setCoordinate(index + 3, coordinate);
      topGearTeeth.setNormal(index + 3, leftNormal);
      topGearTeeth.setCoordinate(index + 4, coordinate);

      // Compute normal for quad 2
      tempCoordinate1.set(xOuter1, yOuter1, toothTipFrontZ);
      tempCoordinate2.set(xOuter2, yOuter2, toothTipFrontZ);
      tempVector1.sub(tempCoordinate2, tempCoordinate1);
      outNormal.cross(frontNormal, tempVector1);
      outNormal.normalize();

      topGearTeeth.setNormal(index + 4, outNormal);
      topGearTeeth.setNormal(index + 5, outNormal);

      // Coordinate labeled 4 in the quad
      topGearTeeth.setCoordinate(index + 6, tempCoordinate2);
      topGearTeeth.setNormal(index + 6, outNormal);
      topGearTeeth.setCoordinate(index + 9, tempCoordinate2);

      // Coordinate labeled 5 in the quad
      coordinate.set(xOuter2, yOuter2, toothTipRearZ);
      topGearTeeth.setCoordinate(index + 7, coordinate);
      topGearTeeth.setNormal(index + 7, outNormal);
      topGearTeeth.setCoordinate(index + 8, coordinate);

      // Compute normal for quad 3
      tempCoordinate1.set(xOuter2, yOuter2, toothTipFrontZ);
      tempCoordinate2.set(xRoot3, yRoot3, frontZ);
      tempVector1.sub(tempCoordinate2, tempCoordinate1);
      rightNormal.cross(frontNormal, tempVector1);
      rightNormal.normalize();

      topGearTeeth.setNormal(index + 8, rightNormal);
      topGearTeeth.setNormal(index + 9, rightNormal);

      // Coordinate labeled 7 in the quad
      topGearTeeth.setCoordinate(index + 10, tempCoordinate2);
      topGearTeeth.setNormal(index + 10, rightNormal);
      topGearTeeth.setCoordinate(index + 13, tempCoordinate2);

      // Coordinate labeled 6 in the quad
      coordinate.set(xRoot3, yRoot3, rearZ);
      topGearTeeth.setCoordinate(index + 11, coordinate);
      topGearTeeth.setNormal(index + 11, rightNormal);
      topGearTeeth.setCoordinate(index + 12, coordinate);

      // Compute normal for quad 4
      tempCoordinate1.set(xRoot3, yRoot3, frontZ);
      tempCoordinate2.set(xRoot4, yRoot4, frontZ);
      tempVector1.sub(tempCoordinate2, tempCoordinate1);
      outNormal.cross(frontNormal, tempVector1);
      outNormal.normalize();

      topGearTeeth.setNormal(index + 12, outNormal);
      topGearTeeth.setNormal(index + 13, outNormal);

      // Coordinate labeled 9 in the quad
      topGearTeeth.setCoordinate(index + 14, tempCoordinate2);
      topGearTeeth.setNormal(index + 14, outNormal);

      // Coordinate labeled 8 in the quad
      coordinate.set(xRoot4, yRoot4, rearZ);
      topGearTeeth.setCoordinate(index + 15, coordinate);
      topGearTeeth.setNormal(index + 15, outNormal);

      // Prepare for the loop by computing the new normal
      toothTopStartAngle = nextToothStartAngle + toothTopAngleIncrement;
      xOuter1 = outsideRadius * (float) Math.cos(toothTopStartAngle);
      yOuter1 = outsideRadius * (float) Math.sin(toothTopStartAngle);

      tempCoordinate1.set(xRoot4, yRoot4, toothTipFrontZ);
      tempCoordinate2.set(xOuter1, yOuter1, toothTipFrontZ);
      tempVector1.sub(tempCoordinate2, tempCoordinate1);
      leftNormal.cross(frontNormal, tempVector1);
      leftNormal.normalize();
    }
    newShape = new Shape3D(topGearTeeth, look);
    this.addChild(newShape);
  }

}

class SpurGearThinBody extends SpurGear {

  /**
   * Construct a SpurGearThinBody;
   * 
   * @return a new spur gear that conforms to the input paramters
   * @param toothCount
   *            number of teeth
   * @param pitchCircleRadius
   *            radius at center of teeth
   * @param shaftRadius
   *            radius of hole at center
   * @param addendum
   *            distance from pitch circle to top of teeth
   * @param dedendum
   *            distance from pitch circle to root of teeth
   * @param gearThickness
   *            thickness of the gear
   */
  public SpurGearThinBody(int toothCount, float pitchCircleRadius,
      float shaftRadius, float addendum, float dedendum,
      float gearThickness) {
    this(toothCount, pitchCircleRadius, shaftRadius, addendum, dedendum,
        gearThickness, gearThickness, 0.25f, null);
  }

  /**
   * Construct a SpurGearThinBody;
   * 
   * @return a new spur gear that conforms to the input paramters
   * @param toothCount
   *            number of teeth
   * @param pitchCircleRadius
   *            radius at center of teeth
   * @param shaftRadius
   *            radius of hole at center
   * @param addendum
   *            distance from pitch circle to top of teeth
   * @param dedendum
   *            distance from pitch circle to root of teeth
   * @param gearThickness
   *            thickness of the gear
   * @param look
   *            the gear's appearance
   */
  public SpurGearThinBody(int toothCount, float pitchCircleRadius,
      float shaftRadius, float addendum, float dedendum,
      float gearThickness, Appearance look) {
    this(toothCount, pitchCircleRadius, shaftRadius, addendum, dedendum,
        gearThickness, gearThickness, 0.25f, look);
  }

  /**
   * Construct a SpurGearThinBody;
   * 
   * @return a new spur gear that conforms to the input paramters
   * @param toothCount
   *            number of teeth
   * @param pitchCircleRadius
   *            radius at center of teeth
   * @param shaftRadius
   *            radius of hole at center
   * @param addendum
   *            distance from pitch circle to top of teeth
   * @param dedendum
   *            distance from pitch circle to root of teeth
   * @param gearThickness
   *            thickness of the gear
   * @param toothTipThickness
   *            thickness of the tip of the tooth
   * @param look
   *            the gear's appearance
   */
  public SpurGearThinBody(int toothCount, float pitchCircleRadius,
      float shaftRadius, float addendum, float dedendum,
      float gearThickness, float toothTipThickness, Appearance look) {
    this(toothCount, pitchCircleRadius, shaftRadius, addendum, dedendum,
        gearThickness, toothTipThickness, 0.25f, look);
  }

  /**
   * Construct a SpurGearThinBody;
   * 
   * @return a new spur gear that conforms to the input paramters
   * @param toothCount
   *            number of teeth
   * @param pitchCircleRadius
   *            radius at center of teeth
   * @param shaftRadius
   *            radius of hole at center
   * @param addendum
   *            distance from pitch circle to top of teeth
   * @param dedendum
   *            distance from pitch circle to root of teeth
   * @param gearThickness
   *            thickness of the gear
   * @param toothTipThickness
   *            thickness of the tip of the tooth
   * @param toothToValleyRatio
   *            ratio of tooth valley to circular pitch (must be <= .25)
   * @param look
   *            the gear's appearance object
   */
  public SpurGearThinBody(int toothCount, float pitchCircleRadius,
      float shaftRadius, float addendum, float dedendum,
      float gearThickness, float toothTipThickness,
      float toothToValleyAngleRatio, Appearance look) {

    this(toothCount, pitchCircleRadius, shaftRadius, addendum, dedendum,
        gearThickness, toothTipThickness, 0.25f, look,
        0.6f * gearThickness, 0.75f * (pitchCircleRadius - shaftRadius));
  }

  /**
   * Construct a SpurGearThinBody;
   * 
   * @return a new spur gear that conforms to the input paramters
   * @param toothCount
   *            number of teeth
   * @param pitchCircleRadius
   *            radius at center of teeth
   * @param shaftRadius
   *            radius of hole at center
   * @param addendum
   *            distance from pitch circle to top of teeth
   * @param dedendum
   *            distance from pitch circle to root of teeth
   * @param gearThickness
   *            thickness of the gear
   * @param toothTipThickness
   *            thickness of the tip of the tooth
   * @param toothToValleyRatio
   *            ratio of tooth valley to circular pitch (must be <= .25)
   * @param look
   *            the gear's appearance object
   * @param bodyThickness
   *            the thickness of the gear body
   * @param crossSectionWidth
   *            the width of the depressed portion of the gear's body
   */
  public SpurGearThinBody(int toothCount, float pitchCircleRadius,
      float shaftRadius, float addendum, float dedendum,
      float gearThickness, float toothTipThickness,
      float toothToValleyAngleRatio, Appearance look,
      float bodyThickness, float crossSectionWidth) {

    super(toothCount, pitchCircleRadius, addendum, dedendum,
        toothToValleyAngleRatio);

    float diskCrossSectionWidth = (rootRadius - shaftRadius - crossSectionWidth) / 2.0f;
    float outerShaftRadius = shaftRadius + diskCrossSectionWidth;
    float innerToothRadius = rootRadius - diskCrossSectionWidth;

    // Generate the gear's body disks, first by the shaft, then in
    // the body and, lastly, by the teeth
    addBodyDisks(shaftRadius, outerShaftRadius, gearThickness, look);
    addBodyDisks(innerToothRadius, rootRadius, gearThickness, look);
    addBodyDisks(outerShaftRadius, innerToothRadius, bodyThickness, look);

    // Generate the gear's "shaft" equivalents the two at the teeth
    // and the two at the shaft
    addCylinderSkins(innerToothRadius, gearThickness, InwardNormals, look);
    addCylinderSkins(outerShaftRadius, gearThickness, OutwardNormals, look);

    // Generate the gear's interior shaft
    addCylinderSkins(shaftRadius, gearThickness, InwardNormals, look);

    // Generate the gear's teeth
    addTeeth(pitchCircleRadius, rootRadius, outsideRadius, gearThickness,
        toothTipThickness, toothToValleyAngleRatio, look);
  }

}

class Shaft extends javax.media.j3d.TransformGroup {

  /**
   * Construct a Shaft;
   * 
   * @return a new shaft that with the specified radius centered about the
   *         origin an laying in the XY plane and of a specified length
   *         extending in the Z dimension
   * @param radius
   *            radius of shaft
   * @param length
   *            shaft length shaft extends from -length/2 to length/2 in the Z
   *            dimension
   * @param segmentCount
   *            number of segments for the shaft face
   * @param look
   *            the Appearance to associate with this shaft
   */
  public Shaft(float radius, float length, int segmentCount, Appearance look) {
    // The direction of the ray from the shaft's center
    float xDirection, yDirection;
    float xShaft, yShaft;

    // The z coordinates for the shaft's faces (never change)
    float frontZ = -0.5f * length;
    float rearZ = 0.5f * length;

    int shaftFaceVertexCount; // #(vertices) per shaft face
    int shaftFaceTotalVertexCount; // total #(vertices) in all teeth
    int shaftFaceStripCount[] = new int[1]; // per shaft vertex count
    int shaftVertexCount; // #(vertices) for shaft
    int shaftStripCount[] = new int[1]; // #(vertices) in strip/strip

    // Front and rear facing normals for the shaft's faces
    Vector3f frontNormal = new Vector3f(0.0f, 0.0f, -1.0f);
    Vector3f rearNormal = new Vector3f(0.0f, 0.0f, 1.0f);
    // Outward facing normal
    Vector3f outNormal = new Vector3f(1.0f, 0.0f, 0.0f);

    // Temporary variables for storing coordinates and vectors
    Point3f coordinate = new Point3f(0.0f, 0.0f, 0.0f);
    Shape3D newShape;

    // The angle subtended by a single segment
    double segmentAngle = 2.0 * Math.PI / segmentCount;
    double tempAngle;

    // Allow this object to spin. etc.
    this.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

    /*
     * for the forward facing fan: ___3___ - | - / | \ 4/\ | /\2 / \ | / \ / \ | / \ : \ | / :
     * |--------------- *----------------| 5 0 1
     * 
     * for backward facing fan exchange 1 with 5; 2 with 4, etc.
     */

    // Construct the shaft's front and rear face
    shaftFaceVertexCount = segmentCount + 2;
    shaftFaceStripCount[0] = shaftFaceVertexCount;

    TriangleFanArray frontShaftFace = new TriangleFanArray(
        shaftFaceVertexCount, GeometryArray.COORDINATES
            | GeometryArray.NORMALS, shaftFaceStripCount);

    TriangleFanArray rearShaftFace = new TriangleFanArray(
        shaftFaceVertexCount, GeometryArray.COORDINATES
            | GeometryArray.NORMALS, shaftFaceStripCount);

    coordinate.set(0.0f, 0.0f, frontZ);
    frontShaftFace.setCoordinate(0, coordinate);
    frontShaftFace.setNormal(0, frontNormal);

    coordinate.set(0.0f, 0.0f, rearZ);
    rearShaftFace.setCoordinate(0, coordinate);
    rearShaftFace.setNormal(0, rearNormal);

    for (int index = 1; index < segmentCount + 2; index++) {

      tempAngle = segmentAngle * -(double) index;
      coordinate.set(radius * (float) Math.cos(tempAngle), radius
          * (float) Math.sin(tempAngle), frontZ);
      frontShaftFace.setCoordinate(index, coordinate);
      frontShaftFace.setNormal(index, frontNormal);

      tempAngle = -tempAngle;
      coordinate.set(radius * (float) Math.cos(tempAngle), radius
          * (float) Math.sin(tempAngle), rearZ);
      rearShaftFace.setCoordinate(index, coordinate);
      rearShaftFace.setNormal(index, rearNormal);
    }
    newShape = new Shape3D(frontShaftFace, look);
    this.addChild(newShape);
    newShape = new Shape3D(rearShaftFace, look);
    this.addChild(newShape);

    // Construct shaft's outer skin (the cylinder body)
    shaftVertexCount = 2 * segmentCount + 2;
    shaftStripCount[0] = shaftVertexCount;

    TriangleStripArray shaft = new TriangleStripArray(shaftVertexCount,
        GeometryArray.COORDINATES | GeometryArray.NORMALS,
        shaftStripCount);

    outNormal.set(1.0f, 0.0f, 0.0f);

    coordinate.set(radius, 0.0f, rearZ);
    shaft.setCoordinate(0, coordinate);
    shaft.setNormal(0, outNormal);

    coordinate.set(radius, 0.0f, frontZ);
    shaft.setCoordinate(1, coordinate);
    shaft.setNormal(1, outNormal);

    for (int count = 0; count < segmentCount; count++) {
      int index = 2 + count * 2;

      tempAngle = segmentAngle * (double) (count + 1);
      xDirection = (float) Math.cos(tempAngle);
      yDirection = (float) Math.sin(tempAngle);
      xShaft = radius * xDirection;
      yShaft = radius * yDirection;
      outNormal.set(xDirection, yDirection, 0.0f);

      coordinate.set(xShaft, yShaft, rearZ);
      shaft.setCoordinate(index, coordinate);
      shaft.setNormal(index, outNormal);

      coordinate.set(xShaft, yShaft, frontZ);
      shaft.setCoordinate(index + 1, coordinate);
      shaft.setNormal(index + 1, outNormal);
    }
    newShape = new Shape3D(shaft, look);
    this.addChild(newShape);
  }
}