GearTest.java Source code

Java tutorial

Introduction

Here is the source code for GearTest.java

Source

/*
 * @(#)GearTest.java 1.17 02/10/21 13:40:16
 * 
 * Copyright (c) 1996-2002 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: -
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer. - Redistribution in binary
 * form must reproduce the above copyright notice, this list of conditions and
 * the following disclaimer in the documentation and/or other materials provided
 * with the distribution.
 * 
 * Neither the name of Sun Microsystems, Inc. or the names of contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGES.
 * 
 * You acknowledge that Software is not designed,licensed or intended for use in
 * the design, construction, operation or maintenance of any nuclear facility.
 */

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);
    }
}