Java tutorial
// //CLASS //ExBluePrint - illustrate use of background images // //LESSON //Add a Background node to place a background image of a blueprint //behind foreground geometry of a mechanical part. // //SEE ALSO //ExBackgroundImage // //AUTHOR //David R. Nadeau / San Diego Supercomputer Center // import java.applet.Applet; import java.awt.AWTEvent; import java.awt.BorderLayout; import java.awt.CheckboxMenuItem; import java.awt.Component; import java.awt.Cursor; import java.awt.Frame; import java.awt.Menu; import java.awt.MenuBar; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseEvent; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.io.File; import java.util.Enumeration; import java.util.EventListener; import javax.media.j3d.Appearance; import javax.media.j3d.Background; import javax.media.j3d.Behavior; import javax.media.j3d.BoundingSphere; import javax.media.j3d.BranchGroup; import javax.media.j3d.Canvas3D; import javax.media.j3d.ColoringAttributes; import javax.media.j3d.DirectionalLight; import javax.media.j3d.GeometryArray; import javax.media.j3d.Group; import javax.media.j3d.ImageComponent2D; import javax.media.j3d.Light; import javax.media.j3d.LineAttributes; import javax.media.j3d.Material; import javax.media.j3d.PolygonAttributes; import javax.media.j3d.QuadArray; import javax.media.j3d.Shape3D; import javax.media.j3d.Switch; import javax.media.j3d.Transform3D; import javax.media.j3d.TransformGroup; import javax.media.j3d.TriangleStripArray; import javax.media.j3d.WakeupCriterion; import javax.media.j3d.WakeupOnAWTEvent; import javax.media.j3d.WakeupOnElapsedFrames; import javax.media.j3d.WakeupOr; import javax.vecmath.Color3f; import javax.vecmath.Matrix4d; import javax.vecmath.Point3d; import javax.vecmath.Point3f; import javax.vecmath.Vector3d; import javax.vecmath.Vector3f; import com.sun.j3d.utils.geometry.Cylinder; import com.sun.j3d.utils.geometry.Primitive; import com.sun.j3d.utils.image.TextureLoader; import com.sun.j3d.utils.universe.PlatformGeometry; import com.sun.j3d.utils.universe.SimpleUniverse; import com.sun.j3d.utils.universe.Viewer; import com.sun.j3d.utils.universe.ViewingPlatform; public class ExBluePrint extends Java3DFrame { //-------------------------------------------------------------- // SCENE CONTENT //-------------------------------------------------------------- // // Nodes (updated via menu) // private Background background = null; private Switch shadingSwitch = null; // // Build scene // public Group buildScene() { // Get the current image ImageComponent2D image = imageComponents[currentImage]; // Build the scene root Group scene = new Group(); // BEGIN EXAMPLE TOPIC // Create application bounds BoundingSphere worldBounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), // Center 1000.0); // Extent // Set the background color and its application bounds background = new Background(); background.setColor(White); background.setImage(image); background.setCapability(Background.ALLOW_IMAGE_WRITE); background.setApplicationBounds(worldBounds); scene.addChild(background); // END EXAMPLE TOPIC // Build foreground geometry scene.addChild(buildGadget()); return scene; } //-------------------------------------------------------------- // FOREGROUND AND ANNOTATION CONTENT //-------------------------------------------------------------- // // Build a mechanical gadget including a few gears and a // shaft going through them. // private Group buildGadget() { if (debug) System.err.println(" gadget..."); // // Create two appearances: // wireframeApp: draw as blue wireframe // shadedApp: draw as metalic shaded polygons // // Wireframe: // no Material - defaults to coloring attributes color // polygons as lines, with backfaces // thick lines Appearance wireframeApp = new Appearance(); ColoringAttributes wireframeCatt = new ColoringAttributes(); wireframeCatt.setColor(0.0f, 0.2559f, 0.4213f); wireframeCatt.setShadeModel(ColoringAttributes.SHADE_FLAT); wireframeApp.setColoringAttributes(wireframeCatt); PolygonAttributes wireframePatt = new PolygonAttributes(); wireframePatt.setPolygonMode(PolygonAttributes.POLYGON_LINE); wireframePatt.setCullFace(PolygonAttributes.CULL_NONE); wireframeApp.setPolygonAttributes(wireframePatt); LineAttributes wireframeLatt = new LineAttributes(); wireframeLatt.setLineWidth(2.0f); wireframeApp.setLineAttributes(wireframeLatt); // Shaded: // silver material Appearance shadedApp = new Appearance(); Material shadedMat = new Material(); shadedMat.setAmbientColor(0.30f, 0.30f, 0.30f); shadedMat.setDiffuseColor(0.30f, 0.30f, 0.50f); shadedMat.setSpecularColor(0.60f, 0.60f, 0.80f); shadedMat.setShininess(0.10f); shadedApp.setMaterial(shadedMat); ColoringAttributes shadedCatt = new ColoringAttributes(); shadedCatt.setShadeModel(ColoringAttributes.SHADE_GOURAUD); shadedApp.setColoringAttributes(shadedCatt); // // Create a switch group to hold two versions of the // shape: one wireframe, and one shaded // Transform3D tr = new Transform3D(); tr.set(new Vector3f(-1.0f, 0.2f, 0.0f)); TransformGroup gadget = new TransformGroup(tr); shadingSwitch = new Switch(); shadingSwitch.setCapability(Switch.ALLOW_SWITCH_WRITE); Group wireframe = new Group(); Group shaded = new Group(); shadingSwitch.addChild(wireframe); shadingSwitch.addChild(shaded); shadingSwitch.setWhichChild(1); // shaded gadget.addChild(shadingSwitch); // // Build a gear (wireframe and shaded) // tr = new Transform3D(); tr.rotY(Math.PI / 2.0); TransformGroup tg = new TransformGroup(tr); SpurGear gear = new SpurGearThinBody(24, // tooth count 1.6f, // pitch circle radius 0.3f, // shaft radius 0.08f, // addendum 0.05f, // dedendum 0.3f, // gear thickness 0.28f, // tooth tip thickness wireframeApp);// appearance tg.addChild(gear); wireframe.addChild(tg); tg = new TransformGroup(tr); gear = new SpurGearThinBody(24, // tooth count 1.6f, // pitch circle radius 0.3f, // shaft radius 0.08f, // addendum 0.05f, // dedendum 0.3f, // gear thickness 0.28f, // tooth tip thickness shadedApp); // appearance tg.addChild(gear); shaded.addChild(tg); // // Build another gear (wireframe and shaded) // tr.rotY(Math.PI / 2.0); Vector3f trans = new Vector3f(-0.5f, 0.0f, 0.0f); tr.setTranslation(trans); tg = new TransformGroup(tr); gear = new SpurGearThinBody(30, // tooth count 2.0f, // pitch circle radius 0.3f, // shaft radius 0.08f, // addendum 0.05f, // dedendum 0.3f, // gear thickness 0.28f, // tooth tip thickness wireframeApp);// appearance tg.addChild(gear); wireframe.addChild(tg); tg = new TransformGroup(tr); gear = new SpurGearThinBody(30, // tooth count 2.0f, // pitch circle radius 0.3f, // shaft radius 0.08f, // addendum 0.05f, // dedendum 0.3f, // gear thickness 0.28f, // tooth tip thickness shadedApp); // appearance tg.addChild(gear); shaded.addChild(tg); // // Build a cylindrical shaft (wireframe and shaded) // tr.rotZ(-Math.PI / 2.0); trans = new Vector3f(1.0f, 0.0f, 0.0f); tr.setTranslation(trans); tg = new TransformGroup(tr); Cylinder cyl = new Cylinder(0.3f, // radius 4.0f, // length Primitive.GENERATE_NORMALS, // format 16, // radial resolution 1, // length-wise resolution wireframeApp);// appearance tg.addChild(cyl); wireframe.addChild(tg); tg = new TransformGroup(tr); cyl = new Cylinder(0.3f, // radius 4.0f, // length Primitive.GENERATE_NORMALS, // format 16, // radial resolution 1, // length-wise resolution shadedApp); // appearance tg.addChild(cyl); shaded.addChild(tg); // // Build shaft teeth (wireframe and shaded) // tr.rotY(Math.PI / 2.0); trans = new Vector3f(2.05f, 0.0f, 0.0f); tr.setTranslation(trans); tg = new TransformGroup(tr); gear = new SpurGear(12, // tooth count 0.5f, // pitch circle radius 0.3f, // shaft radius 0.05f, // addendum 0.05f, // dedendum 1.5f, // gear thickness 0.8f, // tooth tip thickness wireframeApp);// appearance tg.addChild(gear); wireframe.addChild(tg); tg = new TransformGroup(tr); gear = new SpurGear(12, // tooth count 0.5f, // pitch circle radius 0.3f, // shaft radius 0.05f, // addendum 0.05f, // dedendum 1.5f, // gear thickness 0.8f, // tooth tip thickness shadedApp); // appearance tg.addChild(gear); shaded.addChild(tg); return gadget; } //-------------------------------------------------------------- // USER INTERFACE //-------------------------------------------------------------- // // Main // public static void main(String[] args) { ExBluePrint ex = new ExBluePrint(); ex.initialize(args); ex.buildUniverse(); ex.showFrame(); } // Image menu choices private NameValue[] images = { new NameValue("None", null), new NameValue("Blueprint", "blueprint.jpg"), }; private int currentImage = 0; private ImageComponent2D[] imageComponents; private CheckboxMenuItem[] imageMenu; private int currentAppearance = 0; private CheckboxMenuItem[] appearanceMenu; // // Initialize the GUI (application and applet) // public void initialize(String[] args) { // Initialize the window, menubar, etc. super.initialize(args); exampleFrame.setTitle("Java 3D Blueprint Example"); // // Add a menubar menu to change parameters // (images) // -------- // Wireframe // Shaded // // Add a menu to select among background and shading options Menu m = new Menu("Options"); imageMenu = new CheckboxMenuItem[images.length]; for (int i = 0; i < images.length; i++) { imageMenu[i] = new CheckboxMenuItem(images[i].name); imageMenu[i].addItemListener(this); imageMenu[i].setState(false); m.add(imageMenu[i]); } imageMenu[currentImage].setState(true); m.addSeparator(); appearanceMenu = new CheckboxMenuItem[2]; appearanceMenu[0] = new CheckboxMenuItem("Wireframe"); appearanceMenu[0].addItemListener(this); appearanceMenu[0].setState(false); m.add(appearanceMenu[0]); appearanceMenu[1] = new CheckboxMenuItem("Shaded"); appearanceMenu[1].addItemListener(this); appearanceMenu[1].setState(true); m.add(appearanceMenu[1]); exampleMenuBar.add(m); // Preload background images TextureLoader texLoader = null; imageComponents = new ImageComponent2D[images.length]; String value = null; for (int i = 0; i < images.length; i++) { value = (String) images[i].value; if (value == null) { imageComponents[i] = null; continue; } texLoader = new TextureLoader(value, this); imageComponents[i] = texLoader.getImage(); } } // // Handle checkboxes // public void itemStateChanged(ItemEvent event) { Object src = event.getSource(); // Check if it is an image choice for (int i = 0; i < imageMenu.length; i++) { if (src == imageMenu[i]) { // Update the checkboxes imageMenu[currentImage].setState(false); currentImage = i; imageMenu[currentImage].setState(true); // Set the background image ImageComponent2D image = imageComponents[currentImage]; background.setImage(image); return; } } // Check if it is an appearance choice if (src == appearanceMenu[0]) { appearanceMenu[1].setState(false); shadingSwitch.setWhichChild(0); return; } if (src == appearanceMenu[1]) { appearanceMenu[0].setState(false); shadingSwitch.setWhichChild(1); return; } // Handle all other checkboxes super.itemStateChanged(event); } } /* * @(#)SpurGearThinBody.java 1.3 98/02/20 14:29:59 * * Copyright (c) 1996-1998 Sun Microsystems, Inc. All Rights Reserved. * * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use, * modify and redistribute this software in source and binary code form, * provided that i) this copyright notice and license appear on all copies of * the software; and ii) Licensee does not utilize the software in a manner * which is disparaging to Sun. * * 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. * * This software is not designed or intended for use in on-line control of * aircraft, air traffic, aircraft navigation or aircraft communications; or in * the design, construction, operation or maintenance of any nuclear facility. * Licensee represents and warrants that it will not use or redistribute the * Software for such purposes. */ 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); } } /* * @(#)SpurGear.java 1.12 98/02/20 14:29:58 * * Copyright (c) 1996-1998 Sun Microsystems, Inc. All Rights Reserved. * * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use, * modify and redistribute this software in source and binary code form, * provided that i) this copyright notice and license appear on all copies of * the software; and ii) Licensee does not utilize the software in a manner * which is disparaging to Sun. * * 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. * * This software is not designed or intended for use in on-line control of * aircraft, air traffic, aircraft navigation or aircraft communications; or in * the design, construction, operation or maintenance of any nuclear facility. * Licensee represents and warrants that it will not use or redistribute the * Software for such purposes. */ 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); } } /* * @(#)Gear.java 1.5 98/02/20 14:29:55 * * Copyright (c) 1996-1998 Sun Microsystems, Inc. All Rights Reserved. * * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use, * modify and redistribute this software in source and binary code form, * provided that i) this copyright notice and license appear on all copies of * the software; and ii) Licensee does not utilize the software in a manner * which is disparaging to Sun. * * 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. * * This software is not designed or intended for use in on-line control of * aircraft, air traffic, aircraft navigation or aircraft communications; or in * the design, construction, operation or maintenance of any nuclear facility. * Licensee represents and warrants that it will not use or redistribute the * Software for such purposes. */ 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; } } /** * The Example class is a base class extended by example applications. The class * provides basic features to create a top-level frame, add a menubar and * Canvas3D, build the universe, set up "examine" and "walk" style navigation * behaviors, and provide hooks so that subclasses can add 3D content to the * example's universe. * <P> * Using this Example class simplifies the construction of example applications, * enabling the author to focus upon 3D content and not the busywork of creating * windows, menus, and universes. * * @version 1.0, 98/04/16 * @author David R. Nadeau, San Diego Supercomputer Center */ class Java3DFrame extends Applet implements WindowListener, ActionListener, ItemListener, CheckboxMenuListener { // Navigation types public final static int Walk = 0; public final static int Examine = 1; // Should the scene be compiled? private boolean shouldCompile = true; // GUI objects for our subclasses protected Java3DFrame example = null; protected Frame exampleFrame = null; protected MenuBar exampleMenuBar = null; protected Canvas3D exampleCanvas = null; protected TransformGroup exampleViewTransform = null; protected TransformGroup exampleSceneTransform = null; protected boolean debug = false; // Private GUI objects and state private boolean headlightOnOff = true; private int navigationType = Examine; private CheckboxMenuItem headlightMenuItem = null; private CheckboxMenuItem walkMenuItem = null; private CheckboxMenuItem examineMenuItem = null; private DirectionalLight headlight = null; private ExamineViewerBehavior examineBehavior = null; private WalkViewerBehavior walkBehavior = null; //-------------------------------------------------------------- // ADMINISTRATION //-------------------------------------------------------------- /** * The main program entry point when invoked as an application. Each example * application that extends this class must define their own main. * * @param args * a String array of command-line arguments */ public static void main(String[] args) { Java3DFrame ex = new Java3DFrame(); ex.initialize(args); ex.buildUniverse(); ex.showFrame(); } /** * Constructs a new Example object. * * @return a new Example that draws no 3D content */ public Java3DFrame() { // Do nothing } /** * Initializes the application when invoked as an applet. */ public void init() { // Collect properties into String array String[] args = new String[2]; // NOTE: to be done still... this.initialize(args); this.buildUniverse(); this.showFrame(); // NOTE: add something to the browser page? } /** * Initializes the Example by parsing command-line arguments, building an * AWT Frame, constructing a menubar, and creating the 3D canvas. * * @param args * a String array of command-line arguments */ protected void initialize(String[] args) { example = this; // Parse incoming arguments parseArgs(args); // Build the frame if (debug) System.err.println("Building GUI..."); exampleFrame = new Frame(); exampleFrame.setSize(640, 480); exampleFrame.setTitle("Java 3D Example"); exampleFrame.setLayout(new BorderLayout()); // Set up a close behavior exampleFrame.addWindowListener(this); // Create a canvas exampleCanvas = new Canvas3D(null); exampleCanvas.setSize(630, 460); exampleFrame.add("Center", exampleCanvas); // Build the menubar exampleMenuBar = this.buildMenuBar(); exampleFrame.setMenuBar(exampleMenuBar); // Pack exampleFrame.pack(); exampleFrame.validate(); // exampleFrame.setVisible( true ); } /** * Parses incoming command-line arguments. Applications that subclass this * class may override this method to support their own command-line * arguments. * * @param args * a String array of command-line arguments */ protected void parseArgs(String[] args) { for (int i = 0; i < args.length; i++) { if (args[i].equals("-d")) debug = true; } } //-------------------------------------------------------------- // SCENE CONTENT //-------------------------------------------------------------- /** * Builds the 3D universe by constructing a virtual universe (via * SimpleUniverse), a view platform (via SimpleUniverse), and a view (via * SimpleUniverse). A headlight is added and a set of behaviors initialized * to handle navigation types. */ protected void buildUniverse() { // // Create a SimpleUniverse object, which builds: // // - a Locale using the given hi-res coordinate origin // // - a ViewingPlatform which in turn builds: // - a MultiTransformGroup with which to move the // the ViewPlatform about // // - a ViewPlatform to hold the view // // - a BranchGroup to hold avatar geometry (if any) // // - a BranchGroup to hold view platform // geometry (if any) // // - a Viewer which in turn builds: // - a PhysicalBody which characterizes the user's // viewing preferences and abilities // // - a PhysicalEnvironment which characterizes the // user's rendering hardware and software // // - a JavaSoundMixer which initializes sound // support within the 3D environment // // - a View which renders the scene into a Canvas3D // // All of these actions could be done explicitly, but // using the SimpleUniverse utilities simplifies the code. // if (debug) System.err.println("Building scene graph..."); SimpleUniverse universe = new SimpleUniverse(null, // Hi-res coordinate // for the origin - // use default 1, // Number of transforms in MultiTransformGroup exampleCanvas, // Canvas3D into which to draw null); // URL for user configuration file - use defaults // // Get the viewer and create an audio device so that // sound will be enabled in this content. // Viewer viewer = universe.getViewer(); viewer.createAudioDevice(); // // Get the viewing platform created by SimpleUniverse. // From that platform, get the inner-most TransformGroup // in the MultiTransformGroup. That inner-most group // contains the ViewPlatform. It is this inner-most // TransformGroup we need in order to: // // - add a "headlight" that always aims forward from // the viewer // // - change the viewing direction in a "walk" style // // The inner-most TransformGroup's transform will be // changed by the walk behavior (when enabled). // ViewingPlatform viewingPlatform = universe.getViewingPlatform(); exampleViewTransform = viewingPlatform.getViewPlatformTransform(); // // Create a "headlight" as a forward-facing directional light. // Set the light's bounds to huge. Since we want the light // on the viewer's "head", we need the light within the // TransformGroup containing the ViewPlatform. The // ViewingPlatform class creates a handy hook to do this // called "platform geometry". The PlatformGeometry class is // subclassed off of BranchGroup, and is intended to contain // a description of the 3D platform itself... PLUS a headlight! // So, to add the headlight, create a new PlatformGeometry group, // add the light to it, then add that platform geometry to the // ViewingPlatform. // BoundingSphere allBounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 100000.0); PlatformGeometry pg = new PlatformGeometry(); headlight = new DirectionalLight(); headlight.setColor(White); headlight.setDirection(new Vector3f(0.0f, 0.0f, -1.0f)); headlight.setInfluencingBounds(allBounds); headlight.setCapability(Light.ALLOW_STATE_WRITE); pg.addChild(headlight); viewingPlatform.setPlatformGeometry(pg); // // Create the 3D content BranchGroup, containing: // // - a TransformGroup who's transform the examine behavior // will change (when enabled). // // - 3D geometry to view // // Build the scene root BranchGroup sceneRoot = new BranchGroup(); // Build a transform that we can modify exampleSceneTransform = new TransformGroup(); exampleSceneTransform.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); exampleSceneTransform.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); exampleSceneTransform.setCapability(Group.ALLOW_CHILDREN_EXTEND); // // Build the scene, add it to the transform, and add // the transform to the scene root // if (debug) System.err.println(" scene..."); Group scene = this.buildScene(); exampleSceneTransform.addChild(scene); sceneRoot.addChild(exampleSceneTransform); // // Create a pair of behaviors to implement two navigation // types: // // - "examine": a style where mouse drags rotate about // the scene's origin as if it is an object under // examination. This is similar to the "Examine" // navigation type used by VRML browsers. // // - "walk": a style where mouse drags rotate about // the viewer's center as if the viewer is turning // about to look at a scene they are in. This is // similar to the "Walk" navigation type used by // VRML browsers. // // Aim the examine behavior at the scene's TransformGroup // and add the behavior to the scene root. // // Aim the walk behavior at the viewing platform's // TransformGroup and add the behavior to the scene root. // // Enable one (and only one!) of the two behaviors // depending upon the current navigation type. // examineBehavior = new ExamineViewerBehavior(exampleSceneTransform, // Transform // gorup // to // modify exampleFrame); // Parent frame for cusor changes examineBehavior.setSchedulingBounds(allBounds); sceneRoot.addChild(examineBehavior); walkBehavior = new WalkViewerBehavior(exampleViewTransform, // Transform // group to // modify exampleFrame); // Parent frame for cusor changes walkBehavior.setSchedulingBounds(allBounds); sceneRoot.addChild(walkBehavior); if (navigationType == Walk) { examineBehavior.setEnable(false); walkBehavior.setEnable(true); } else { examineBehavior.setEnable(true); walkBehavior.setEnable(false); } // // Compile the scene branch group and add it to the // SimpleUniverse. // if (shouldCompile) sceneRoot.compile(); universe.addBranchGraph(sceneRoot); reset(); } /** * Builds the scene. Example application subclasses should replace this * method with their own method to build 3D content. * * @return a Group containing 3D content to display */ public Group buildScene() { // Build the scene group containing nothing Group scene = new Group(); return scene; } //-------------------------------------------------------------- // SET/GET METHODS //-------------------------------------------------------------- /** * Sets the headlight on/off state. The headlight faces forward in the * direction the viewer is facing. Example applications that add their own * lights will typically turn the headlight off. A standard menu item * enables the headlight to be turned on and off via user control. * * @param onOff * a boolean turning the light on (true) or off (false) */ public void setHeadlightEnable(boolean onOff) { headlightOnOff = onOff; if (headlight != null) headlight.setEnable(headlightOnOff); if (headlightMenuItem != null) headlightMenuItem.setState(headlightOnOff); } /** * Gets the headlight on/off state. * * @return a boolean indicating if the headlight is on or off */ public boolean getHeadlightEnable() { return headlightOnOff; } /** * Sets the navigation type to be either Examine or Walk. The Examine * navigation type sets up behaviors that use mouse drags to rotate and * translate scene content as if it is an object held at arm's length and * under examination. The Walk navigation type uses mouse drags to rotate * and translate the viewer as if they are walking through the content. The * Examine type is the default. * * @param nav * either Walk or Examine */ public void setNavigationType(int nav) { if (nav == Walk) { navigationType = Walk; if (walkMenuItem != null) walkMenuItem.setState(true); if (examineMenuItem != null) examineMenuItem.setState(false); if (walkBehavior != null) walkBehavior.setEnable(true); if (examineBehavior != null) examineBehavior.setEnable(false); } else { navigationType = Examine; if (walkMenuItem != null) walkMenuItem.setState(false); if (examineMenuItem != null) examineMenuItem.setState(true); if (walkBehavior != null) walkBehavior.setEnable(false); if (examineBehavior != null) examineBehavior.setEnable(true); } } /** * Gets the current navigation type, returning either Walk or Examine. * * @return either Walk or Examine */ public int getNavigationType() { return navigationType; } /** * Sets whether the scene graph should be compiled or not. Normally this is * always a good idea. For some example applications that use this Example * framework, it is useful to disable compilation - particularly when nodes * and node components will need to be made un-live in order to make * changes. Once compiled, such components can be made un-live, but they are * still unchangable unless appropriate capabilities have been set. * * @param onOff * a boolean turning compilation on (true) or off (false) */ public void setCompilable(boolean onOff) { shouldCompile = onOff; } /** * Gets whether the scene graph will be compiled or not. * * @return a boolean indicating if scene graph compilation is on or off */ public boolean getCompilable() { return shouldCompile; } //These methods will be replaced // Set the view position and direction public void setViewpoint(Point3f position, Vector3f direction) { Transform3D t = new Transform3D(); t.set(new Vector3f(position)); exampleViewTransform.setTransform(t); // how to set direction? } // Reset transforms public void reset() { Transform3D trans = new Transform3D(); exampleSceneTransform.setTransform(trans); trans.set(new Vector3f(0.0f, 0.0f, 10.0f)); exampleViewTransform.setTransform(trans); setNavigationType(navigationType); } // // Gets the URL (with file: prepended) for the current directory. // This is a terrible hack needed in the Alpha release of Java3D // in order to build a full path URL for loading sounds with // MediaContainer. When MediaContainer is fully implemented, // it should handle relative path names, but not yet. // public String getCurrentDirectory() { // Create a bogus file so that we can query it's path File dummy = new File("dummy.tmp"); String dummyPath = dummy.getAbsolutePath(); // strip "/dummy.tmp" from end of dummyPath and put into 'path' if (dummyPath.endsWith(File.separator + "dummy.tmp")) { int index = dummyPath.lastIndexOf(File.separator + "dummy.tmp"); if (index >= 0) { int pathLength = index + 5; // pre-pend 'file:' char[] charPath = new char[pathLength]; dummyPath.getChars(0, index, charPath, 5); String path = new String(charPath, 0, pathLength); path = "file:" + path.substring(5, pathLength); return path + File.separator; } } return dummyPath + File.separator; } //-------------------------------------------------------------- // USER INTERFACE //-------------------------------------------------------------- /** * Builds the example AWT Frame menubar. Standard menus and their options * are added. Applications that subclass this class should build their * menubar additions within their initialize method. * * @return a MenuBar for the AWT Frame */ private MenuBar buildMenuBar() { // Build the menubar MenuBar menuBar = new MenuBar(); // File menu Menu m = new Menu("File"); m.addActionListener(this); m.add("Exit"); menuBar.add(m); // View menu m = new Menu("View"); m.addActionListener(this); m.add("Reset view"); m.addSeparator(); walkMenuItem = new CheckboxMenuItem("Walk"); walkMenuItem.addItemListener(this); m.add(walkMenuItem); examineMenuItem = new CheckboxMenuItem("Examine"); examineMenuItem.addItemListener(this); m.add(examineMenuItem); if (navigationType == Walk) { walkMenuItem.setState(true); examineMenuItem.setState(false); } else { walkMenuItem.setState(false); examineMenuItem.setState(true); } m.addSeparator(); headlightMenuItem = new CheckboxMenuItem("Headlight on/off"); headlightMenuItem.addItemListener(this); headlightMenuItem.setState(headlightOnOff); m.add(headlightMenuItem); menuBar.add(m); return menuBar; } /** * Shows the application's frame, making it and its menubar, 3D canvas, and * 3D content visible. */ public void showFrame() { exampleFrame.show(); } /** * Quits the application. */ public void quit() { System.exit(0); } /** * Handles menu selections. * * @param event * an ActionEvent indicating what menu action requires handling */ public void actionPerformed(ActionEvent event) { String arg = event.getActionCommand(); if (arg.equals("Reset view")) reset(); else if (arg.equals("Exit")) quit(); } /** * Handles checkbox items on a CheckboxMenu. The Example class has none of * its own, but subclasses may have some. * * @param menu * which CheckboxMenu needs action * @param check * which CheckboxMenu item has changed */ public void checkboxChanged(CheckboxMenu menu, int check) { // None for us } /** * Handles on/off checkbox items on a standard menu. * * @param event * an ItemEvent indicating what requires handling */ public void itemStateChanged(ItemEvent event) { Object src = event.getSource(); boolean state; if (src == headlightMenuItem) { state = headlightMenuItem.getState(); headlight.setEnable(state); } else if (src == walkMenuItem) setNavigationType(Walk); else if (src == examineMenuItem) setNavigationType(Examine); } /** * Handles a window closing event notifying the application that the user * has chosen to close the application without selecting the "Exit" menu * item. * * @param event * a WindowEvent indicating the window is closing */ public void windowClosing(WindowEvent event) { quit(); } public void windowClosed(WindowEvent event) { } public void windowOpened(WindowEvent event) { } public void windowIconified(WindowEvent event) { } public void windowDeiconified(WindowEvent event) { } public void windowActivated(WindowEvent event) { } public void windowDeactivated(WindowEvent event) { } // Well known colors, positions, and directions public final static Color3f White = new Color3f(1.0f, 1.0f, 1.0f); public final static Color3f Gray = new Color3f(0.7f, 0.7f, 0.7f); public final static Color3f DarkGray = new Color3f(0.2f, 0.2f, 0.2f); public final static Color3f Black = new Color3f(0.0f, 0.0f, 0.0f); public final static Color3f Red = new Color3f(1.0f, 0.0f, 0.0f); public final static Color3f DarkRed = new Color3f(0.3f, 0.0f, 0.0f); public final static Color3f Yellow = new Color3f(1.0f, 1.0f, 0.0f); public final static Color3f DarkYellow = new Color3f(0.3f, 0.3f, 0.0f); public final static Color3f Green = new Color3f(0.0f, 1.0f, 0.0f); public final static Color3f DarkGreen = new Color3f(0.0f, 0.3f, 0.0f); public final static Color3f Cyan = new Color3f(0.0f, 1.0f, 1.0f); public final static Color3f Blue = new Color3f(0.0f, 0.0f, 1.0f); public final static Color3f DarkBlue = new Color3f(0.0f, 0.0f, 0.3f); public final static Color3f Magenta = new Color3f(1.0f, 0.0f, 1.0f); public final static Vector3f PosX = new Vector3f(1.0f, 0.0f, 0.0f); public final static Vector3f NegX = new Vector3f(-1.0f, 0.0f, 0.0f); public final static Vector3f PosY = new Vector3f(0.0f, 1.0f, 0.0f); public final static Vector3f NegY = new Vector3f(0.0f, -1.0f, 0.0f); public final static Vector3f PosZ = new Vector3f(0.0f, 0.0f, 1.0f); public final static Vector3f NegZ = new Vector3f(0.0f, 0.0f, -1.0f); public final static Point3f Origin = new Point3f(0.0f, 0.0f, 0.0f); public final static Point3f PlusX = new Point3f(0.75f, 0.0f, 0.0f); public final static Point3f MinusX = new Point3f(-0.75f, 0.0f, 0.0f); public final static Point3f PlusY = new Point3f(0.0f, 0.75f, 0.0f); public final static Point3f MinusY = new Point3f(0.0f, -0.75f, 0.0f); public final static Point3f PlusZ = new Point3f(0.0f, 0.0f, 0.75f); public final static Point3f MinusZ = new Point3f(0.0f, 0.0f, -0.75f); } // //INTERFACE //CheckboxMenuListener - listen for checkbox change events // //DESCRIPTION //The checkboxChanged method is called by users of this class //to notify the listener when a checkbox choice has changed on //a CheckboxMenu class menu. // interface CheckboxMenuListener extends EventListener { public void checkboxChanged(CheckboxMenu menu, int check); } /** * ExamineViewerBehavior * * @version 1.0, 98/04/16 */ /** * Wakeup on mouse button presses, releases, and mouse movements and generate * transforms in an "examination style" that enables the user to rotate, * translation, and zoom an object as if it is held at arm's length. Such an * examination style is similar to the "Examine" navigation type used by VRML * browsers. * * The behavior maps mouse drags to different transforms depending upon the * mosue button held down: * * Button 1 (left) Horizontal movement --> Y-axis rotation Vertical movement --> * X-axis rotation * * Button 2 (middle) Horizontal movement --> nothing Vertical movement --> * Z-axis translation * * Button 3 (right) Horizontal movement --> X-axis translation Vertical movement * --> Y-axis translation * * To support systems with 2 or 1 mouse buttons, the following alternate * mappings are supported while dragging with any mouse button held down and * zero or more keyboard modifiers held down: * * No modifiers = Button 1 ALT = Button 2 Meta = Button 3 Control = Button 3 * * The behavior automatically modifies a TransformGroup provided to the * constructor. The TransformGroup's transform can be set at any time by the * application or other behaviors to cause the examine rotation and translation * to be reset. */ // This class is inspired by the MouseBehavior, MouseRotate, // MouseTranslate, and MouseZoom utility behaviors provided with // Java 3D. This class differs from those utilities in that it: // // (a) encapsulates all three behaviors into one in order to // enforce a specific "Examine" symantic // // (b) supports set/get of the rotation and translation factors // that control the speed of movement. // // (c) supports the "Control" modifier as an alternative to the // "Meta" modifier not present on PC, Mac, and most non-Sun // keyboards. This makes button3 behavior usable on PCs, // Macs, and other systems with fewer than 3 mouse buttons. class ExamineViewerBehavior extends ViewerBehavior { // Previous cursor location protected int previousX = 0; protected int previousY = 0; // Saved standard cursor protected Cursor savedCursor = null; /** * Construct an examine behavior that listens to mouse movement and button * presses to generate rotation and translation transforms written into a * transform group given later with the setTransformGroup( ) method. */ public ExamineViewerBehavior() { super(); } /** * Construct an examine behavior that listens to mouse movement and button * presses to generate rotation and translation transforms written into a * transform group given later with the setTransformGroup( ) method. * * @param parent * The AWT Component that contains the area generating mouse * events. */ public ExamineViewerBehavior(Component parent) { super(parent); } /** * Construct an examine behavior that listens to mouse movement and button * presses to generate rotation and translation transforms written into the * given transform group. * * @param transformGroup * The transform group to be modified by the behavior. */ public ExamineViewerBehavior(TransformGroup transformGroup) { super(); subjectTransformGroup = transformGroup; } /** * Construct an examine behavior that listens to mouse movement and button * presses to generate rotation and translation transforms written into the * given transform group. * * @param transformGroup * The transform group to be modified by the behavior. * @param parent * The AWT Component that contains the area generating mouse * events. */ public ExamineViewerBehavior(TransformGroup transformGroup, Component parent) { super(parent); subjectTransformGroup = transformGroup; } /** * Respond to a button1 event (press, release, or drag). * * @param mouseEvent * A MouseEvent to respond to. */ public void onButton1(MouseEvent mev) { if (subjectTransformGroup == null) return; int x = mev.getX(); int y = mev.getY(); if (mev.getID() == MouseEvent.MOUSE_PRESSED) { // Mouse button pressed: record position previousX = x; previousY = y; // Change to a "move" cursor if (parentComponent != null) { savedCursor = parentComponent.getCursor(); parentComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } return; } if (mev.getID() == MouseEvent.MOUSE_RELEASED) { // Mouse button released: do nothing // Switch the cursor back if (parentComponent != null) parentComponent.setCursor(savedCursor); return; } // // Mouse moved while button down: create a rotation // // Compute the delta in X and Y from the previous // position. Use the delta to compute rotation // angles with the mapping: // // positive X mouse delta --> positive Y-axis rotation // positive Y mouse delta --> positive X-axis rotation // // where positive X mouse movement is to the right, and // positive Y mouse movement is **down** the screen. // int deltaX = x - previousX; int deltaY = y - previousY; if (deltaX > UNUSUAL_XDELTA || deltaX < -UNUSUAL_XDELTA || deltaY > UNUSUAL_YDELTA || deltaY < -UNUSUAL_YDELTA) { // Deltas are too huge to be believable. Probably a glitch. // Don't record the new XY location, or do anything. return; } double xRotationAngle = deltaY * XRotationFactor; double yRotationAngle = deltaX * YRotationFactor; // // Build transforms // transform1.rotX(xRotationAngle); transform2.rotY(yRotationAngle); // Get and save the current transform matrix subjectTransformGroup.getTransform(currentTransform); currentTransform.get(matrix); translate.set(matrix.m03, matrix.m13, matrix.m23); // Translate to the origin, rotate, then translate back currentTransform.setTranslation(origin); currentTransform.mul(transform1, currentTransform); currentTransform.mul(transform2, currentTransform); currentTransform.setTranslation(translate); // Update the transform group subjectTransformGroup.setTransform(currentTransform); previousX = x; previousY = y; } /** * Respond to a button2 event (press, release, or drag). * * @param mouseEvent * A MouseEvent to respond to. */ public void onButton2(MouseEvent mev) { if (subjectTransformGroup == null) return; int x = mev.getX(); int y = mev.getY(); if (mev.getID() == MouseEvent.MOUSE_PRESSED) { // Mouse button pressed: record position previousX = x; previousY = y; // Change to a "move" cursor if (parentComponent != null) { savedCursor = parentComponent.getCursor(); parentComponent.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); } return; } if (mev.getID() == MouseEvent.MOUSE_RELEASED) { // Mouse button released: do nothing // Switch the cursor back if (parentComponent != null) parentComponent.setCursor(savedCursor); return; } // // Mouse moved while button down: create a translation // // Compute the delta in Y from the previous // position. Use the delta to compute translation // distances with the mapping: // // positive Y mouse delta --> positive Y-axis translation // // where positive X mouse movement is to the right, and // positive Y mouse movement is **down** the screen. // int deltaY = y - previousY; if (deltaY > UNUSUAL_YDELTA || deltaY < -UNUSUAL_YDELTA) { // Deltas are too huge to be believable. Probably a glitch. // Don't record the new XY location, or do anything. return; } double zTranslationDistance = deltaY * ZTranslationFactor; // // Build transforms // translate.set(0.0, 0.0, zTranslationDistance); transform1.set(translate); // Get and save the current transform subjectTransformGroup.getTransform(currentTransform); // Translate as needed currentTransform.mul(transform1, currentTransform); // Update the transform group subjectTransformGroup.setTransform(currentTransform); previousX = x; previousY = y; } /** * Respond to a button3 event (press, release, or drag). * * @param mouseEvent * A MouseEvent to respond to. */ public void onButton3(MouseEvent mev) { if (subjectTransformGroup == null) return; int x = mev.getX(); int y = mev.getY(); if (mev.getID() == MouseEvent.MOUSE_PRESSED) { // Mouse button pressed: record position previousX = x; previousY = y; // Change to a "move" cursor if (parentComponent != null) { savedCursor = parentComponent.getCursor(); parentComponent.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); } return; } if (mev.getID() == MouseEvent.MOUSE_RELEASED) { // Mouse button released: do nothing // Switch the cursor back if (parentComponent != null) parentComponent.setCursor(savedCursor); return; } // // Mouse moved while button down: create a translation // // Compute the delta in X and Y from the previous // position. Use the delta to compute translation // distances with the mapping: // // positive X mouse delta --> positive X-axis translation // positive Y mouse delta --> negative Y-axis translation // // where positive X mouse movement is to the right, and // positive Y mouse movement is **down** the screen. // int deltaX = x - previousX; int deltaY = y - previousY; if (deltaX > UNUSUAL_XDELTA || deltaX < -UNUSUAL_XDELTA || deltaY > UNUSUAL_YDELTA || deltaY < -UNUSUAL_YDELTA) { // Deltas are too huge to be believable. Probably a glitch. // Don't record the new XY location, or do anything. return; } double xTranslationDistance = deltaX * XTranslationFactor; double yTranslationDistance = -deltaY * YTranslationFactor; // // Build transforms // translate.set(xTranslationDistance, yTranslationDistance, 0.0); transform1.set(translate); // Get and save the current transform subjectTransformGroup.getTransform(currentTransform); // Translate as needed currentTransform.mul(transform1, currentTransform); // Update the transform group subjectTransformGroup.setTransform(currentTransform); previousX = x; previousY = y; } /** * Respond to an elapsed frames event (assuming subclass has set up a wakeup * criterion for it). * * @param time * A WakeupOnElapsedFrames criterion to respond to. */ public void onElapsedFrames(WakeupOnElapsedFrames timeEvent) { // Can't happen } } /* * * Copyright (c) 1998 David R. Nadeau * */ /** * WalkViewerBehavior is a utility class that creates a "walking style" * navigation symantic. * * The behavior wakes up on mouse button presses, releases, and mouse movements * and generates transforms in a "walk style" that enables the user to walk * through a scene, translating and turning about as if they are within the * scene. Such a walk style is similar to the "Walk" navigation type used by * VRML browsers. * <P> * The behavior maps mouse drags to different transforms depending upon the * mouse button held down: * <DL> * <DT>Button 1 (left) * <DD>Horizontal movement --> Y-axis rotation * <DD>Vertical movement --> Z-axis translation * * <DT>Button 2 (middle) * <DD>Horizontal movement --> Y-axis rotation * <DD>Vertical movement --> X-axis rotation * * <DT>Button 3 (right) * <DD>Horizontal movement --> X-axis translation * <DD>Vertical movement --> Y-axis translation * </DL> * * To support systems with 2 or 1 mouse buttons, the following alternate * mappings are supported while dragging with any mouse button held down and * zero or more keyboard modifiers held down: * <UL> * <LI>No modifiers = Button 1 * <LI>ALT = Button 2 * <LI>Meta = Button 3 * <LI>Control = Button 3 * </UL> * The behavior automatically modifies a TransformGroup provided to the * constructor. The TransformGroup's transform can be set at any time by the * application or other behaviors to cause the walk rotation and translation to * be reset. * <P> * While a mouse button is down, the behavior automatically changes the cursor * in a given parent AWT Component. If no parent Component is given, no cursor * changes are attempted. * * @version 1.0, 98/04/16 * @author David R. Nadeau, San Diego Supercomputer Center */ class WalkViewerBehavior extends ViewerBehavior { // This class is inspired by the MouseBehavior, MouseRotate, // MouseTranslate, and MouseZoom utility behaviors provided with // Java 3D. This class differs from those utilities in that it: // // (a) encapsulates all three behaviors into one in order to // enforce a specific "Walk" symantic // // (b) supports set/get of the rotation and translation factors // that control the speed of movement. // // (c) supports the "Control" modifier as an alternative to the // "Meta" modifier not present on PC, Mac, and most non-Sun // keyboards. This makes button3 behavior usable on PCs, // Macs, and other systems with fewer than 3 mouse buttons. // Previous and initial cursor locations protected int previousX = 0; protected int previousY = 0; protected int initialX = 0; protected int initialY = 0; // Deadzone size (delta from initial XY for which no // translate or rotate action is taken protected static final int DELTAX_DEADZONE = 10; protected static final int DELTAY_DEADZONE = 10; // Keep a set of wakeup criterion for animation-generated // event types. protected WakeupCriterion[] mouseAndAnimationEvents = null; protected WakeupOr mouseAndAnimationCriterion = null; protected WakeupOr savedMouseCriterion = null; // Saved standard cursor protected Cursor savedCursor = null; /** * Default Rotation and translation scaling factors for animated movements * (Button 1 press). */ public static final double DEFAULT_YROTATION_ANIMATION_FACTOR = 0.0002; public static final double DEFAULT_ZTRANSLATION_ANIMATION_FACTOR = 0.01; protected double YRotationAnimationFactor = DEFAULT_YROTATION_ANIMATION_FACTOR; protected double ZTranslationAnimationFactor = DEFAULT_ZTRANSLATION_ANIMATION_FACTOR; /** * Constructs a new walk behavior that converts mouse actions into rotations * and translations. Rotations and translations are written into a * TransformGroup that must be set using the setTransformGroup method. The * cursor will be changed during mouse actions if the parent frame is set * using the setParentComponent method. * * @return a new WalkViewerBehavior that needs its TransformGroup and parent * Component set */ public WalkViewerBehavior() { super(); } /** * Constructs a new walk behavior that converts mouse actions into rotations * and translations. Rotations and translations are written into a * TransformGroup that must be set using the setTransformGroup method. The * cursor will be changed within the given AWT parent Component during mouse * drags. * * @param parent * a parent AWT Component within which the cursor will change * during mouse drags * * @return a new WalkViewerBehavior that needs its TransformGroup and parent * Component set */ public WalkViewerBehavior(Component parent) { super(parent); } /** * Constructs a new walk behavior that converts mouse actions into rotations * and translations. Rotations and translations are written into the given * TransformGroup. The cursor will be changed during mouse actions if the * parent frame is set using the setParentComponent method. * * @param transformGroup * a TransformGroup whos transform is read and written by the * behavior * * @return a new WalkViewerBehavior that needs its TransformGroup and parent * Component set */ public WalkViewerBehavior(TransformGroup transformGroup) { super(); subjectTransformGroup = transformGroup; } /** * Constructs a new walk behavior that converts mouse actions into rotations * and translations. Rotations and translations are written into the given * TransformGroup. The cursor will be changed within the given AWT parent * Component during mouse drags. * * @param transformGroup * a TransformGroup whos transform is read and written by the * behavior * * @param parent * a parent AWT Component within which the cursor will change * during mouse drags * * @return a new WalkViewerBehavior that needs its TransformGroup and parent * Component set */ public WalkViewerBehavior(TransformGroup transformGroup, Component parent) { super(parent); subjectTransformGroup = transformGroup; } /** * Initializes the behavior. */ public void initialize() { super.initialize(); savedMouseCriterion = mouseCriterion; // from parent class mouseAndAnimationEvents = new WakeupCriterion[4]; mouseAndAnimationEvents[0] = new WakeupOnAWTEvent(MouseEvent.MOUSE_DRAGGED); mouseAndAnimationEvents[1] = new WakeupOnAWTEvent(MouseEvent.MOUSE_PRESSED); mouseAndAnimationEvents[2] = new WakeupOnAWTEvent(MouseEvent.MOUSE_RELEASED); mouseAndAnimationEvents[3] = new WakeupOnElapsedFrames(0); mouseAndAnimationCriterion = new WakeupOr(mouseAndAnimationEvents); // Don't use the above criterion until a button 1 down event } /** * Sets the Y rotation animation scaling factor for Y-axis rotations. This * scaling factor is used to control the speed of Y rotation when button 1 * is pressed and dragged. * * @param factor * the double Y rotation scaling factor */ public void setYRotationAnimationFactor(double factor) { YRotationAnimationFactor = factor; } /** * Gets the current Y animation rotation scaling factor for Y-axis * rotations. * * @return the double Y rotation scaling factor */ public double getYRotationAnimationFactor() { return YRotationAnimationFactor; } /** * Sets the Z animation translation scaling factor for Z-axis translations. * This scaling factor is used to control the speed of Z translation when * button 1 is pressed and dragged. * * @param factor * the double Z translation scaling factor */ public void setZTranslationAnimationFactor(double factor) { ZTranslationAnimationFactor = factor; } /** * Gets the current Z animation translation scaling factor for Z-axis * translations. * * @return the double Z translation scaling factor */ public double getZTranslationAnimationFactor() { return ZTranslationAnimationFactor; } /** * Responds to an elapsed frames event. Such an event is generated on every * frame while button 1 is held down. On each call, this method computes new * Y-axis rotation and Z-axis translation values and writes them to the * behavior's TransformGroup. The translation and rotation amounts are * computed based upon the distance between the current cursor location and * the cursor location when button 1 was pressed. As this distance * increases, the translation or rotation amount increases. * * @param time * the WakeupOnElapsedFrames criterion to respond to */ public void onElapsedFrames(WakeupOnElapsedFrames timeEvent) { // // Time elapsed while button down: create a rotation and // a translation. // // Compute the delta in X and Y from the initial position to // the previous position. Multiply the delta times a scaling // factor to compute an offset to add to the current translation // and rotation. Use the mapping: // // positive X mouse delta --> negative Y-axis rotation // positive Y mouse delta --> positive Z-axis translation // // where positive X mouse movement is to the right, and // positive Y mouse movement is **down** the screen. // if (buttonPressed != BUTTON1) return; int deltaX = previousX - initialX; int deltaY = previousY - initialY; double yRotationAngle = -deltaX * YRotationAnimationFactor; double zTranslationDistance = deltaY * ZTranslationAnimationFactor; // // Build transforms // transform1.rotY(yRotationAngle); translate.set(0.0, 0.0, zTranslationDistance); // Get and save the current transform matrix subjectTransformGroup.getTransform(currentTransform); currentTransform.get(matrix); // Translate to the origin, rotate, then translate back currentTransform.setTranslation(origin); currentTransform.mul(transform1, currentTransform); // Translate back from the origin by the original translation // distance, plus the new walk translation... but force walk // to travel on a plane by ignoring the Y component of a // transformed translation vector. currentTransform.transform(translate); translate.x += matrix.m03; // add in existing X translation translate.y = matrix.m13; // use Y translation translate.z += matrix.m23; // add in existing Z translation currentTransform.setTranslation(translate); // Update the transform group subjectTransformGroup.setTransform(currentTransform); } /** * Responds to a button1 event (press, release, or drag). On a press, the * method adds a wakeup criterion to the behavior's set, callling for the * behavior to be awoken on each frame. On a button prelease, this criterion * is removed from the set. * * @param mouseEvent * the MouseEvent to respond to */ public void onButton1(MouseEvent mev) { if (subjectTransformGroup == null) return; int x = mev.getX(); int y = mev.getY(); if (mev.getID() == MouseEvent.MOUSE_PRESSED) { // Mouse button pressed: record position and change // the wakeup criterion to include elapsed time wakeups // so we can animate. previousX = x; previousY = y; initialX = x; initialY = y; // Swap criterion... parent class will not reschedule us mouseCriterion = mouseAndAnimationCriterion; // Change to a "move" cursor if (parentComponent != null) { savedCursor = parentComponent.getCursor(); parentComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } return; } if (mev.getID() == MouseEvent.MOUSE_RELEASED) { // Mouse button released: restore original wakeup // criterion which only includes mouse activity, not // elapsed time mouseCriterion = savedMouseCriterion; // Switch the cursor back if (parentComponent != null) parentComponent.setCursor(savedCursor); return; } previousX = x; previousY = y; } /** * Responds to a button2 event (press, release, or drag). On a press, the * method records the initial cursor location. On a drag, the difference * between the current and previous cursor location provides a delta that * controls the amount by which to rotate in X and Y. * * @param mouseEvent * the MouseEvent to respond to */ public void onButton2(MouseEvent mev) { if (subjectTransformGroup == null) return; int x = mev.getX(); int y = mev.getY(); if (mev.getID() == MouseEvent.MOUSE_PRESSED) { // Mouse button pressed: record position previousX = x; previousY = y; initialX = x; initialY = y; // Change to a "rotate" cursor if (parentComponent != null) { savedCursor = parentComponent.getCursor(); parentComponent.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); } return; } if (mev.getID() == MouseEvent.MOUSE_RELEASED) { // Mouse button released: do nothing // Switch the cursor back if (parentComponent != null) parentComponent.setCursor(savedCursor); return; } // // Mouse moved while button down: create a rotation // // Compute the delta in X and Y from the previous // position. Use the delta to compute rotation // angles with the mapping: // // positive X mouse delta --> negative Y-axis rotation // positive Y mouse delta --> negative X-axis rotation // // where positive X mouse movement is to the right, and // positive Y mouse movement is **down** the screen. // int deltaX = x - previousX; int deltaY = 0; if (Math.abs(y - initialY) > DELTAY_DEADZONE) { // Cursor has moved far enough vertically to consider // it intentional, so get it's delta. deltaY = y - previousY; } if (deltaX > UNUSUAL_XDELTA || deltaX < -UNUSUAL_XDELTA || deltaY > UNUSUAL_YDELTA || deltaY < -UNUSUAL_YDELTA) { // Deltas are too huge to be believable. Probably a glitch. // Don't record the new XY location, or do anything. return; } double xRotationAngle = -deltaY * XRotationFactor; double yRotationAngle = -deltaX * YRotationFactor; // // Build transforms // transform1.rotX(xRotationAngle); transform2.rotY(yRotationAngle); // Get and save the current transform matrix subjectTransformGroup.getTransform(currentTransform); currentTransform.get(matrix); translate.set(matrix.m03, matrix.m13, matrix.m23); // Translate to the origin, rotate, then translate back currentTransform.setTranslation(origin); currentTransform.mul(transform2, currentTransform); currentTransform.mul(transform1); currentTransform.setTranslation(translate); // Update the transform group subjectTransformGroup.setTransform(currentTransform); previousX = x; previousY = y; } /** * Responds to a button3 event (press, release, or drag). On a drag, the * difference between the current and previous cursor location provides a * delta that controls the amount by which to translate in X and Y. * * @param mouseEvent * the MouseEvent to respond to */ public void onButton3(MouseEvent mev) { if (subjectTransformGroup == null) return; int x = mev.getX(); int y = mev.getY(); if (mev.getID() == MouseEvent.MOUSE_PRESSED) { // Mouse button pressed: record position previousX = x; previousY = y; // Change to a "move" cursor if (parentComponent != null) { savedCursor = parentComponent.getCursor(); parentComponent.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); } return; } if (mev.getID() == MouseEvent.MOUSE_RELEASED) { // Mouse button released: do nothing // Switch the cursor back if (parentComponent != null) parentComponent.setCursor(savedCursor); return; } // // Mouse moved while button down: create a translation // // Compute the delta in X and Y from the previous // position. Use the delta to compute translation // distances with the mapping: // // positive X mouse delta --> positive X-axis translation // positive Y mouse delta --> negative Y-axis translation // // where positive X mouse movement is to the right, and // positive Y mouse movement is **down** the screen. // int deltaX = x - previousX; int deltaY = y - previousY; if (deltaX > UNUSUAL_XDELTA || deltaX < -UNUSUAL_XDELTA || deltaY > UNUSUAL_YDELTA || deltaY < -UNUSUAL_YDELTA) { // Deltas are too huge to be believable. Probably a glitch. // Don't record the new XY location, or do anything. return; } double xTranslationDistance = deltaX * XTranslationFactor; double yTranslationDistance = -deltaY * YTranslationFactor; // // Build transforms // translate.set(xTranslationDistance, yTranslationDistance, 0.0); transform1.set(translate); // Get and save the current transform subjectTransformGroup.getTransform(currentTransform); // Translate as needed currentTransform.mul(transform1); // Update the transform group subjectTransformGroup.setTransform(currentTransform); previousX = x; previousY = y; } } // //CLASS //CheckboxMenu - build a menu of grouped checkboxes // //DESCRIPTION //The class creates a menu with one or more CheckboxMenuItem's //and monitors that menu. When a menu checkbox is picked, the //previous one is turned off (in radio-button style). Then, //a given listener's checkboxChanged method is called, passing it //the menu and the item checked. // class CheckboxMenu extends Menu implements ItemListener { // State protected CheckboxMenuItem[] checks = null; protected int current = 0; protected CheckboxMenuListener listener = null; // Construct public CheckboxMenu(String name, NameValue[] items, CheckboxMenuListener listen) { this(name, items, 0, listen); } public CheckboxMenu(String name, NameValue[] items, int cur, CheckboxMenuListener listen) { super(name); current = cur; listener = listen; if (items == null) return; checks = new CheckboxMenuItem[items.length]; for (int i = 0; i < items.length; i++) { checks[i] = new CheckboxMenuItem(items[i].name, false); checks[i].addItemListener(this); add(checks[i]); } checks[cur].setState(true); } // Handle checkbox changed events public void itemStateChanged(ItemEvent event) { Object src = event.getSource(); for (int i = 0; i < checks.length; i++) { if (src == checks[i]) { // Update the checkboxes checks[current].setState(false); current = i; checks[current].setState(true); if (listener != null) listener.checkboxChanged(this, i); return; } } } // Methods to get and set state public int getCurrent() { return current; } public void setCurrent(int cur) { if (cur < 0 || cur >= checks.length) return; // ignore out of range choices if (checks == null) return; checks[current].setState(false); current = cur; checks[current].setState(true); } public CheckboxMenuItem getSelectedCheckbox() { if (checks == null) return null; return checks[current]; } public void setSelectedCheckbox(CheckboxMenuItem item) { if (checks == null) return; for (int i = 0; i < checks.length; i++) { if (item == checks[i]) { checks[i].setState(false); current = i; checks[i].setState(true); } } } } /** * ViewerBehavior * * @version 1.0, 98/04/16 */ /** * Wakeup on mouse button presses, releases, and mouse movements and generate * transforms for a transform group. Classes that extend this class impose * specific symantics, such as "Examine" or "Walk" viewing, similar to the * navigation types used by VRML browsers. * * To support systems with 2 or 1 mouse buttons, the following alternate * mappings are supported while dragging with any mouse button held down and * zero or more keyboard modifiers held down: * * No modifiers = Button 1 ALT = Button 2 Meta = Button 3 Control = Button 3 * * The behavior automatically modifies a TransformGroup provided to the * constructor. The TransformGroup's transform can be set at any time by the * application or other behaviors to cause the viewer's rotation and translation * to be reset. */ // This class is inspired by the MouseBehavior, MouseRotate, // MouseTranslate, and MouseZoom utility behaviors provided with // Java 3D. This class differs from those utilities in that it: // // (a) encapsulates all three behaviors into one in order to // enforce a specific viewing symantic // // (b) supports set/get of the rotation and translation factors // that control the speed of movement. // // (c) supports the "Control" modifier as an alternative to the // "Meta" modifier not present on PC, Mac, and most non-Sun // keyboards. This makes button3 behavior usable on PCs, // Macs, and other systems with fewer than 3 mouse buttons. abstract class ViewerBehavior extends Behavior { // Keep track of the transform group who's transform we modify // during mouse motion. protected TransformGroup subjectTransformGroup = null; // Keep a set of wakeup criterion for different mouse-generated // event types. protected WakeupCriterion[] mouseEvents = null; protected WakeupOr mouseCriterion = null; // Track which button was last pressed protected static final int BUTTONNONE = -1; protected static final int BUTTON1 = 0; protected static final int BUTTON2 = 1; protected static final int BUTTON3 = 2; protected int buttonPressed = BUTTONNONE; // Keep a few Transform3Ds for use during event processing. This // avoids having to allocate new ones on each event. protected Transform3D currentTransform = new Transform3D(); protected Transform3D transform1 = new Transform3D(); protected Transform3D transform2 = new Transform3D(); protected Matrix4d matrix = new Matrix4d(); protected Vector3d origin = new Vector3d(0.0, 0.0, 0.0); protected Vector3d translate = new Vector3d(0.0, 0.0, 0.0); // Unusual X and Y delta limits. protected static final int UNUSUAL_XDELTA = 400; protected static final int UNUSUAL_YDELTA = 400; protected Component parentComponent = null; /** * Construct a viewer behavior that listens to mouse movement and button * presses to generate rotation and translation transforms written into a * transform group given later with the setTransformGroup( ) method. */ public ViewerBehavior() { super(); } /** * Construct a viewer behavior that listens to mouse movement and button * presses to generate rotation and translation transforms written into a * transform group given later with the setTransformGroup( ) method. * * @param parent * The AWT Component that contains the area generating mouse * events. */ public ViewerBehavior(Component parent) { super(); parentComponent = parent; } /** * Construct a viewer behavior that listens to mouse movement and button * presses to generate rotation and translation transforms written into the * given transform group. * * @param transformGroup * The transform group to be modified by the behavior. */ public ViewerBehavior(TransformGroup transformGroup) { super(); subjectTransformGroup = transformGroup; } /** * Construct a viewer behavior that listens to mouse movement and button * presses to generate rotation and translation transforms written into the * given transform group. * * @param transformGroup * The transform group to be modified by the behavior. * @param parent * The AWT Component that contains the area generating mouse * events. */ public ViewerBehavior(TransformGroup transformGroup, Component parent) { super(); subjectTransformGroup = transformGroup; parentComponent = parent; } /** * Set the transform group modified by the viewer behavior. Setting the * transform group to null disables the behavior until the transform group * is again set to an existing group. * * @param transformGroup * The new transform group to be modified by the behavior. */ public void setTransformGroup(TransformGroup transformGroup) { subjectTransformGroup = transformGroup; } /** * Get the transform group modified by the viewer behavior. */ public TransformGroup getTransformGroup() { return subjectTransformGroup; } /** * Sets the parent component who's cursor will be changed during mouse * drags. If no component is given is given to the constructor, or set via * this method, no cursor changes will be done. * * @param parent * the AWT Component, such as a Frame, within which cursor * changes should take place during mouse drags */ public void setParentComponent(Component parent) { parentComponent = parent; } /* * Gets the parent frame within which the cursor changes during mouse drags. * * @return the AWT Component, such as a Frame, within which cursor changes * should take place during mouse drags. Returns null if no parent is set. */ public Component getParentComponent() { return parentComponent; } /** * Initialize the behavior. */ public void initialize() { // Wakeup when the mouse is dragged or when a mouse button // is pressed or released. mouseEvents = new WakeupCriterion[3]; mouseEvents[0] = new WakeupOnAWTEvent(MouseEvent.MOUSE_DRAGGED); mouseEvents[1] = new WakeupOnAWTEvent(MouseEvent.MOUSE_PRESSED); mouseEvents[2] = new WakeupOnAWTEvent(MouseEvent.MOUSE_RELEASED); mouseCriterion = new WakeupOr(mouseEvents); wakeupOn(mouseCriterion); } /** * Process a new wakeup. Interpret mouse button presses, releases, and mouse * drags. * * @param criteria * The wakeup criteria causing the behavior wakeup. */ public void processStimulus(Enumeration criteria) { WakeupCriterion wakeup = null; AWTEvent[] event = null; int whichButton = BUTTONNONE; // Process all pending wakeups while (criteria.hasMoreElements()) { wakeup = (WakeupCriterion) criteria.nextElement(); if (wakeup instanceof WakeupOnAWTEvent) { event = ((WakeupOnAWTEvent) wakeup).getAWTEvent(); // Process all pending events for (int i = 0; i < event.length; i++) { if (event[i].getID() != MouseEvent.MOUSE_PRESSED && event[i].getID() != MouseEvent.MOUSE_RELEASED && event[i].getID() != MouseEvent.MOUSE_DRAGGED) // Ignore uninteresting mouse events continue; // // Regretably, Java event handling (or perhaps // underlying OS event handling) doesn't always // catch button bounces (redundant presses and // releases), or order events so that the last // drag event is delivered before a release. // This means we can get stray events that we // filter out here. // if (event[i].getID() == MouseEvent.MOUSE_PRESSED && buttonPressed != BUTTONNONE) // Ignore additional button presses until a release continue; if (event[i].getID() == MouseEvent.MOUSE_RELEASED && buttonPressed == BUTTONNONE) // Ignore additional button releases until a press continue; if (event[i].getID() == MouseEvent.MOUSE_DRAGGED && buttonPressed == BUTTONNONE) // Ignore drags until a press continue; MouseEvent mev = (MouseEvent) event[i]; int modifiers = mev.getModifiers(); // // Unfortunately, the underlying event handling // doesn't do a "grab" operation when a mouse button // is pressed. This means that once a button is // pressed, if another mouse button or a keyboard // modifier key is pressed, the delivered mouse event // will show that a different button is being held // down. For instance: // // Action Event // Button 1 press Button 1 press // Drag with button 1 down Button 1 drag // ALT press - // Drag with ALT & button 1 down Button 2 drag // Button 1 release Button 2 release // // The upshot is that we can get a button press // without a matching release, and the button // associated with a drag can change mid-drag. // // To fix this, we watch for an initial button // press, and thenceforth consider that button // to be the one held down, even if additional // buttons get pressed, and despite what is // reported in the event. Only when a button is // released, do we end such a grab. // if (buttonPressed == BUTTONNONE) { // No button is pressed yet, figure out which // button is down now and how to direct events if (((modifiers & InputEvent.BUTTON3_MASK) != 0) || (((modifiers & InputEvent.BUTTON1_MASK) != 0) && ((modifiers & InputEvent.CTRL_MASK) == InputEvent.CTRL_MASK))) { // Button 3 activity (META or CTRL down) whichButton = BUTTON3; } else if ((modifiers & InputEvent.BUTTON2_MASK) != 0) { // Button 2 activity (ALT down) whichButton = BUTTON2; } else { // Button 1 activity (no modifiers down) whichButton = BUTTON1; } // If the event is to press a button, then // record that that button is now down if (event[i].getID() == MouseEvent.MOUSE_PRESSED) buttonPressed = whichButton; } else { // Otherwise a button was pressed earlier and // hasn't been released yet. Assign all further // events to it, even if ALT, META, CTRL, or // another button has been pressed as well. whichButton = buttonPressed; } // Distribute the event switch (whichButton) { case BUTTON1: onButton1(mev); break; case BUTTON2: onButton2(mev); break; case BUTTON3: onButton3(mev); break; default: break; } // If the event is to release a button, then // record that that button is now up if (event[i].getID() == MouseEvent.MOUSE_RELEASED) buttonPressed = BUTTONNONE; } continue; } if (wakeup instanceof WakeupOnElapsedFrames) { onElapsedFrames((WakeupOnElapsedFrames) wakeup); continue; } } // Reschedule us for another wakeup wakeupOn(mouseCriterion); } /** * Default X and Y rotation factors, and XYZ translation factors. */ public static final double DEFAULT_XROTATION_FACTOR = 0.02; public static final double DEFAULT_YROTATION_FACTOR = 0.005; public static final double DEFAULT_XTRANSLATION_FACTOR = 0.02; public static final double DEFAULT_YTRANSLATION_FACTOR = 0.02; public static final double DEFAULT_ZTRANSLATION_FACTOR = 0.04; protected double XRotationFactor = DEFAULT_XROTATION_FACTOR; protected double YRotationFactor = DEFAULT_YROTATION_FACTOR; protected double XTranslationFactor = DEFAULT_XTRANSLATION_FACTOR; protected double YTranslationFactor = DEFAULT_YTRANSLATION_FACTOR; protected double ZTranslationFactor = DEFAULT_ZTRANSLATION_FACTOR; /** * Set the X rotation scaling factor for X-axis rotations. * * @param factor * The new scaling factor. */ public void setXRotationFactor(double factor) { XRotationFactor = factor; } /** * Get the current X rotation scaling factor for X-axis rotations. */ public double getXRotationFactor() { return XRotationFactor; } /** * Set the Y rotation scaling factor for Y-axis rotations. * * @param factor * The new scaling factor. */ public void setYRotationFactor(double factor) { YRotationFactor = factor; } /** * Get the current Y rotation scaling factor for Y-axis rotations. */ public double getYRotationFactor() { return YRotationFactor; } /** * Set the X translation scaling factor for X-axis translations. * * @param factor * The new scaling factor. */ public void setXTranslationFactor(double factor) { XTranslationFactor = factor; } /** * Get the current X translation scaling factor for X-axis translations. */ public double getXTranslationFactor() { return XTranslationFactor; } /** * Set the Y translation scaling factor for Y-axis translations. * * @param factor * The new scaling factor. */ public void setYTranslationFactor(double factor) { YTranslationFactor = factor; } /** * Get the current Y translation scaling factor for Y-axis translations. */ public double getYTranslationFactor() { return YTranslationFactor; } /** * Set the Z translation scaling factor for Z-axis translations. * * @param factor * The new scaling factor. */ public void setZTranslationFactor(double factor) { ZTranslationFactor = factor; } /** * Get the current Z translation scaling factor for Z-axis translations. */ public double getZTranslationFactor() { return ZTranslationFactor; } /** * Respond to a button1 event (press, release, or drag). * * @param mouseEvent * A MouseEvent to respond to. */ public abstract void onButton1(MouseEvent mouseEvent); /** * Respond to a button2 event (press, release, or drag). * * @param mouseEvent * A MouseEvent to respond to. */ public abstract void onButton2(MouseEvent mouseEvent); /** * Responed to a button3 event (press, release, or drag). * * @param mouseEvent * A MouseEvent to respond to. */ public abstract void onButton3(MouseEvent mouseEvent); /** * Respond to an elapsed frames event (assuming subclass has set up a wakeup * criterion for it). * * @param time * A WakeupOnElapsedFrames criterion to respond to. */ public abstract void onElapsedFrames(WakeupOnElapsedFrames timeEvent); } // //CLASS //NameValue - create a handy name-value pair // //DESCRIPTION //It is frequently handy to have one or more name-value pairs //with which to store named colors, named positions, named textures, //and so forth. Several of the examples use this class. // //AUTHOR //David R. Nadeau / San Diego Supercomputer Center // class NameValue { public String name; public Object value; public NameValue(String n, Object v) { name = n; value = v; } }