Java/3D/Sound 3D

Материал из Java эксперт
Версия от 09:15, 1 июня 2010; Admin (обсуждение | вклад) (1 версия)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

3D Sound Demo

ExSound - illustrate the use of sounds

   <source lang="java">

// //CLASS //ExSound - illustrate the use of sounds // //LESSON //Add a PointSound and a BackgroundSound to an environment. // //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.ruponent; 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.Alpha; import javax.media.j3d.AmbientLight; import javax.media.j3d.Appearance; import javax.media.j3d.BackgroundSound; import javax.media.j3d.Behavior; import javax.media.j3d.BoundingSphere; import javax.media.j3d.BranchGroup; import javax.media.j3d.Canvas3D; import javax.media.j3d.DirectionalLight; import javax.media.j3d.GeometryArray; import javax.media.j3d.Group; import javax.media.j3d.ImageComponent; import javax.media.j3d.IndexedQuadArray; import javax.media.j3d.IndexedTriangleStripArray; import javax.media.j3d.Light; import javax.media.j3d.Material; import javax.media.j3d.MediaContainer; import javax.media.j3d.PointLight; import javax.media.j3d.PointSound; import javax.media.j3d.RotationInterpolator; import javax.media.j3d.Shape3D; import javax.media.j3d.Sound; import javax.media.j3d.Texture; import javax.media.j3d.TextureAttributes; import javax.media.j3d.Transform3D; import javax.media.j3d.TransformGroup; 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.Point2f; import javax.vecmath.Point3d; import javax.vecmath.Point3f; import javax.vecmath.Vector3d; import javax.vecmath.Vector3f; 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 ExSound extends Java3DFrame {

 //--------------------------------------------------------------
 //  SCENE CONTENT
 //--------------------------------------------------------------
 //
 //  Nodes (updated via menu)
 //
 private BackgroundSound backgroundSound = null;
 private PointSound pointSound = null;
 private float soundHeight = 1.6f;
 private float pointX = 0.0f;
 private AmbientLight ambientLight = null;
 private PointLight pointLight = null;
 //
 //  Build scene
 //
 public Group buildScene() {
   // Get the initial sound volume
   float vol = ((Float) volumes[currentVolume].value).floatValue();
   // Turn off the example headlight
   setHeadlightEnable(false);
   // Default to walk navigation
   setNavigationType(Walk);
   // Build the scene group
   Group scene = new Group();
   //
   // Preload the sounds
   //
   if (debug)
     System.err.println("  sounds...");
   String path = getCurrentDirectory();
   MediaContainer backgroundMedia = new MediaContainer(path + "canon.wav");
   backgroundMedia.setCacheEnable(true);
   MediaContainer pointMedia = new MediaContainer(path + "willow1.wav");
   pointMedia.setCacheEnable(true);
   // BEGIN EXAMPLE TOPIC
   // Create influencing bounds
   BoundingSphere worldBounds = new BoundingSphere(new Point3d(0.0, 0.0,
       0.0), // Center
       1000.0); // Extent
   // Background sound
   backgroundSound = new BackgroundSound();
   backgroundSound.setEnable(backgroundSoundOnOff);
   backgroundSound.setLoop(Sound.INFINITE_LOOPS);
   backgroundSound.setSoundData(backgroundMedia);
   backgroundSound.setInitialGain(vol);
   backgroundSound.setSchedulingBounds(worldBounds);
   backgroundSound.setCapability(Sound.ALLOW_ENABLE_WRITE);
   backgroundSound.setCapability(Sound.ALLOW_INITIAL_GAIN_WRITE);
   scene.addChild(backgroundSound);
   // Create a distance gain array for the point sound
   Point2f[] distanceGain = { new Point2f(9.0f, 1.0f), // Full volume
       new Point2f(10.0f, 0.5f), // Half volume
       new Point2f(20.0f, 0.25f), // Quarter volume
       new Point2f(30.0f, 0.0f), // Zero volume
   };
   // Point sound
   pointSound = new PointSound();
   pointSound.setEnable(pointSoundOnOff);
   pointSound.setPosition(new Point3f(pointX, soundHeight, 0.0f));
   pointSound.setLoop(Sound.INFINITE_LOOPS);
   pointSound.setSoundData(pointMedia);
   pointSound.setInitialGain(vol);
   pointSound.setDistanceGain(distanceGain);
   pointSound.setSchedulingBounds(worldBounds);
   pointSound.setCapability(Sound.ALLOW_ENABLE_WRITE);
   pointSound.setCapability(Sound.ALLOW_INITIAL_GAIN_WRITE);
   scene.addChild(pointSound);
   // END EXAMPLE TOPIC
   // Build a few lights, one per sound. We"ll turn them
   // on when the associated sound is on.
   ambientLight = new AmbientLight();
   ambientLight.setEnable(backgroundSoundOnOff);
   ambientLight.setColor(Gray);
   ambientLight.setInfluencingBounds(worldBounds);
   ambientLight.setCapability(Light.ALLOW_STATE_WRITE);
   scene.addChild(ambientLight);
   pointLight = new PointLight();
   pointLight.setEnable(pointSoundOnOff);
   pointLight.setColor(White);
   pointLight.setPosition(0.0f, soundHeight, 0.0f);
   pointLight.setInfluencingBounds(worldBounds);
   pointLight.setCapability(Light.ALLOW_STATE_WRITE);
   scene.addChild(pointLight);
   // Add a basic ambient light for when all sounds (and
   // their lights) are off so that the world isn"t dark
   AmbientLight amb = new AmbientLight();
   amb.setEnable(true);
   amb.setColor(Gray);
   amb.setInfluencingBounds(worldBounds);
   amb.setCapability(Light.ALLOW_STATE_WRITE);
   scene.addChild(amb);
   // Build foreground geometry
   scene.addChild(buildForeground());
   return scene;
 }
 //--------------------------------------------------------------
 //  FOREGROUND CONTENT
 //--------------------------------------------------------------
 private Group buildForeground() {
   //
   //  Create a group for the foreground, and move
   //  everything up a bit.
   //
   TransformGroup group = new TransformGroup();
   Transform3D tr = new Transform3D();
   tr.setTranslation(new Vector3f(0.0f, -1.6f, 0.0f));
   group.setTransform(tr);
   //
   //  Load textures
   //
   if (debug)
     System.err.println("  textures...");
   Texture groundTex = null;
   Texture spurTex = null;
   Texture domeTex = null;
   TextureLoader texLoader = null;
   ImageComponent image = null;
   texLoader = new TextureLoader("flooring.jpg", this);
   image = texLoader.getImage();
   if (image == null)
     System.err.println("Cannot load flooring.jpg texture");
   else {
     groundTex = texLoader.getTexture();
     groundTex.setBoundaryModeS(Texture.WRAP);
     groundTex.setBoundaryModeT(Texture.WRAP);
     groundTex.setMinFilter(Texture.NICEST);
     groundTex.setMagFilter(Texture.NICEST);
     groundTex.setMipMapMode(Texture.BASE_LEVEL);
     groundTex.setEnable(true);
   }
   texLoader = new TextureLoader("granite07rev.jpg", this);
   Texture columnTex = texLoader.getTexture();
   if (columnTex == null)
     System.err.println("Cannot load granite07rev.jpg texture");
   else {
     columnTex.setBoundaryModeS(Texture.WRAP);
     columnTex.setBoundaryModeT(Texture.WRAP);
     columnTex.setMinFilter(Texture.NICEST);
     columnTex.setMagFilter(Texture.NICEST);
     columnTex.setMipMapMode(Texture.BASE_LEVEL);
     columnTex.setEnable(true);
   }
   texLoader = new TextureLoader("brtsky.jpg", this);
   Texture boxTex = texLoader.getTexture();
   if (boxTex == null)
     System.err.println("Cannot load brtsky.jpg texture");
   else {
     boxTex.setBoundaryModeS(Texture.WRAP);
     boxTex.setBoundaryModeT(Texture.WRAP);
     boxTex.setMinFilter(Texture.NICEST);
     boxTex.setMagFilter(Texture.NICEST);
     boxTex.setMipMapMode(Texture.BASE_LEVEL);
     boxTex.setEnable(true);
   }
   //
   //  Build the ground
   //
   if (debug)
     System.err.println("  ground...");
   Appearance groundApp = new Appearance();
   Material groundMat = new Material();
   groundMat.setAmbientColor(0.3f, 0.3f, 0.3f);
   groundMat.setDiffuseColor(0.7f, 0.7f, 0.7f);
   groundMat.setSpecularColor(0.0f, 0.0f, 0.0f);
   groundApp.setMaterial(groundMat);
   tr = new Transform3D();
   tr.setScale(new Vector3d(16.0, 4.0, 1.0));
   TextureAttributes groundTexAtt = new TextureAttributes();
   groundTexAtt.setTextureMode(TextureAttributes.MODULATE);
   groundTexAtt.setPerspectiveCorrectionMode(TextureAttributes.NICEST);
   groundTexAtt.setTextureTransform(tr);
   groundApp.setTextureAttributes(groundTexAtt);
   if (groundTex != null)
     groundApp.setTexture(groundTex);
   ElevationGrid ground = new ElevationGrid(11, // X dimension
       11, // Z dimension
       2.0f, // X spacing
       2.0f, // Z spacing
       // Automatically use zero heights
       groundApp); // Appearance
   group.addChild(ground);
   //
   //  Create a column appearance used for both columns.
   //
   Appearance columnApp = new Appearance();
   Material columnMat = new Material();
   columnMat.setAmbientColor(0.6f, 0.6f, 0.6f);
   columnMat.setDiffuseColor(1.0f, 1.0f, 1.0f);
   columnMat.setSpecularColor(0.0f, 0.0f, 0.0f);
   columnApp.setMaterial(columnMat);
   TextureAttributes columnTexAtt = new TextureAttributes();
   columnTexAtt.setTextureMode(TextureAttributes.MODULATE);
   columnTexAtt.setPerspectiveCorrectionMode(TextureAttributes.NICEST);
   columnApp.setTextureAttributes(columnTexAtt);
   if (columnTex != null)
     columnApp.setTexture(columnTex);
   //
   //  To give the point sound an apparent location,
   //  build a column and a set of three co-located
   //  tumbling boxes hovering above the column.
   //
   TransformGroup pointGroup = new TransformGroup();
   tr.setIdentity();
   tr.setTranslation(new Vector3f(pointX, 0.0f, 0.0f));
   pointGroup.setTransform(tr);
   GothicColumn column = new GothicColumn(1.0f, // height
       0.2f, // radius
       GothicColumn.BUILD_TOP, // flags
       columnApp); // appearance
   pointGroup.addChild(column);
   TransformGroup rotThing = new TransformGroup();
   tr.setIdentity();
   tr.setTranslation(new Vector3f(0.0f, soundHeight, 0.0f));
   rotThing.setTransform(tr);
   Appearance boxApp = new Appearance();
   // No material -- make it emissive
   TextureAttributes boxTexAtt = new TextureAttributes();
   boxTexAtt.setTextureMode(TextureAttributes.REPLACE);
   boxTexAtt.setPerspectiveCorrectionMode(TextureAttributes.NICEST);
   boxApp.setTextureAttributes(boxTexAtt);
   if (boxTex != null)
     boxApp.setTexture(boxTex);
   rotThing.addChild(buildTumblingBox(0.4f, 0.4f, 0.4f, // width, height,
       // depth
       boxApp, // Appearance
       40000, 32000, 26000));// XYZ tumble durations
   rotThing.addChild(buildTumblingBox(0.4f, 0.4f, 0.4f, // width, height,
       // depth
       boxApp, // Appearance
       38000, 30000, 28000));// XYZ tumble durations
   rotThing.addChild(buildTumblingBox(0.4f, 0.4f, 0.4f, // width, height,
       // depth
       boxApp, // Appearance
       30000, 26000, 34000));// XYZ tumble durations
   pointGroup.addChild(rotThing);
   group.addChild(pointGroup);
   return group;
 }
 private int[] coordinateIndices = { 0, 1, 5, 4, // front
     1, 2, 6, 5, // right
     2, 3, 7, 6, // back
     3, 0, 4, 7, // left
     4, 5, 6, 7, // top
     3, 2, 1, 0, // bottom
 };
 private float[] textureCoordinates = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
     0.0f, 1.0f, };
 private int[] textureCoordinateIndices = { 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2,
     3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, };
 private float[] normals = { 0.0f, 0.0f, 1.0f, // front
     1.0f, 0.0f, 0.0f, // right
     0.0f, 0.0f, -1.0f, // back
     -1.0f, 0.0f, 0.0f, // left
     0.0f, 1.0f, 0.0f, // top
     0.0f, -1.0f, 0.0f, // bottom
 };
 private int[] normalIndices = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3,
     3, 3, 4, 4, 4, 4, 5, 5, 5, 5, };
 private Shape3D buildBox(float width, float height, float depth,
     Appearance app) {
   float w2 = width / 2.0f;
   float h2 = height / 2.0f;
   float d2 = depth / 2.0f;
   float[] coordinates = new float[8 * 3];
   int n = 0;
   // Around the bottom of the box
   coordinates[n + 0] = -w2;
   coordinates[n + 1] = -h2;
   coordinates[n + 2] = d2;
   n += 3;
   coordinates[n + 0] = w2;
   coordinates[n + 1] = -h2;
   coordinates[n + 2] = d2;
   n += 3;
   coordinates[n + 0] = w2;
   coordinates[n + 1] = -h2;
   coordinates[n + 2] = -d2;
   n += 3;
   coordinates[n + 0] = -w2;
   coordinates[n + 1] = -h2;
   coordinates[n + 2] = -d2;
   n += 3;
   // Around the top of the box
   coordinates[n + 0] = -w2;
   coordinates[n + 1] = h2;
   coordinates[n + 2] = d2;
   n += 3;
   coordinates[n + 0] = w2;
   coordinates[n + 1] = h2;
   coordinates[n + 2] = d2;
   n += 3;
   coordinates[n + 0] = w2;
   coordinates[n + 1] = h2;
   coordinates[n + 2] = -d2;
   n += 3;
   coordinates[n + 0] = -w2;
   coordinates[n + 1] = h2;
   coordinates[n + 2] = -d2;
   n += 3;
   IndexedQuadArray quads = new IndexedQuadArray(coordinates.length, // vertex
       // count
       GeometryArray.COORDINATES | GeometryArray.NORMALS
           | GeometryArray.TEXTURE_COORDINATE_2,
       coordinateIndices.length);
   quads.setCoordinates(0, coordinates);
   quads.setCoordinateIndices(0, coordinateIndices);
   quads.setNormals(0, normals);
   quads.setNormalIndices(0, normalIndices);
   quads.setTextureCoordinates(0, textureCoordinates);
   quads.setTextureCoordinateIndices(0, textureCoordinateIndices);
   Shape3D shape = new Shape3D(quads, app);
   return shape;
 }
 private Group buildTumblingBox(float width, float height, float depth,
     Appearance app, int xDur, int yDur, int zDur) {
   BoundingSphere worldBounds = new BoundingSphere(new Point3d(0.0, 0.0,
       0.0), // Center
       1000.0); // Extent
   //  Build a box to tumble
   Shape3D box = buildBox(width, height, depth, app);
   //  Build a set of nested transform groups. Attach
   //  to each one a behavior that rotates around an X,
   //  Y, or Z axis. Use different rotation speeds for
   //  each axis to create a tumbling effect.
   TransformGroup outerGroup = new TransformGroup();
   outerGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
   Transform3D yAxis = new Transform3D();
   Alpha alpha = new Alpha(-1, // loop count: -1 = forever
       Alpha.INCREASING_ENABLE, // increasing
       0, // trigger time: 0 = now
       0, // delay: 0 = none
       xDur, // increasing duration
       0, // increasing ramp duration
       0, // at one (sustain) duration
       0, // decreasing duration
       0, // decreasing ramp duration
       0); // at zero duration
   RotationInterpolator rot = new RotationInterpolator(alpha, // Alpha
       // control
       outerGroup, // Target transform group
       yAxis, // Y axis rotation
       0.0f, // Minimum angle
       2.0f * (float) Math.PI);// Maximum angle
   rot.setSchedulingBounds(worldBounds);
   outerGroup.addChild(rot);
   TransformGroup middleGroup = new TransformGroup();
   middleGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
   Transform3D xAxis = new Transform3D();
   xAxis.rotZ(-1.571f);
   alpha = new Alpha(-1, // loop count: -1 = forever
       Alpha.INCREASING_ENABLE, // increasing
       0, // trigger time: 0 = now
       0, // delay: 0 = none
       yDur, // increasing duration
       0, // increasing ramp duration
       0, // at one (sustain) duration
       0, // decreasing duration
       0, // decreasing ramp duration
       0); // at zero duration
   rot = new RotationInterpolator(alpha, // Alpha control
       middleGroup, // Target transform group
       xAxis, // Y axis rotation
       0.0f, // Minimum angle
       2.0f * (float) Math.PI);// Maximum angle
   rot.setSchedulingBounds(worldBounds);
   middleGroup.addChild(rot);
   outerGroup.addChild(middleGroup);
   TransformGroup innerGroup = new TransformGroup();
   innerGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
   Transform3D zAxis = new Transform3D();
   zAxis.rotX(1.571f);
   alpha = new Alpha(-1, // loop count: -1 = forever
       Alpha.INCREASING_ENABLE, // increasing
       0, // trigger time: 0 = now
       0, // delay: 0 = none
       zDur, // increasing duration
       0, // increasing ramp duration
       0, // at one (sustain) duration
       0, // decreasing duration
       0, // decreasing ramp duration
       0); // at zero duration
   rot = new RotationInterpolator(alpha, // Alpha control
       innerGroup, // Target transform group
       zAxis, // Y axis rotation
       0.0f, // Minimum angle
       2.0f * (float) Math.PI);// Maximum angle
   rot.setSchedulingBounds(worldBounds);
   innerGroup.addChild(rot);
   middleGroup.addChild(innerGroup);
   innerGroup.addChild(box);
   return outerGroup;
 }
 //--------------------------------------------------------------
 //  USER INTERFACE
 //--------------------------------------------------------------
 //
 //  Main
 //
 public static void main(String[] args) {
   ExSound ex = new ExSound();
   ex.initialize(args);
   ex.buildUniverse();
   ex.showFrame();
 }
 //  On/off choices
 private boolean backgroundSoundOnOff = false;
 private CheckboxMenuItem backgroundSoundOnOffMenu = null;
 private boolean pointSoundOnOff = false;
 private CheckboxMenuItem pointSoundOnOffMenu = null;
 //  Volume menu choices
 private NameValue[] volumes = { new NameValue("Silent", new Float(0.0f)),
     new NameValue("Low volume", new Float(0.5f)),
     new NameValue("Medium volume", new Float(1.0f)),
     new NameValue("High volume", new Float(2.0f)), };
 private int currentVolume = 2;
 private CheckboxMenu volumeMenu = null;
 //
 //  Initialize the GUI (application and applet)
 //
 public void initialize(String[] args) {
   // Initialize the window, menubar, etc.
   super.initialize(args);
   exampleFrame.setTitle("Java 3D Sound Example");
   //
   //  Add a menubar menu to change node parameters
   //    Background sound on/off
   //    Point sound on/off
   //
   Menu m = new Menu("Sounds");
   backgroundSoundOnOffMenu = new CheckboxMenuItem(
       "Background sound on/off", backgroundSoundOnOff);
   backgroundSoundOnOffMenu.addItemListener(this);
   m.add(backgroundSoundOnOffMenu);
   pointSoundOnOffMenu = new CheckboxMenuItem("Point sound on/off",
       pointSoundOnOff);
   pointSoundOnOffMenu.addItemListener(this);
   m.add(pointSoundOnOffMenu);
   volumeMenu = new CheckboxMenu("Volume", volumes, currentVolume, this);
   m.add(volumeMenu);
   exampleMenuBar.add(m);
 }
 //
 //  Handle checkboxes and menu choices
 //
 public void checkboxChanged(CheckboxMenu menu, int check) {
   if (menu == volumeMenu) {
     // Change the sound volumes
     currentVolume = check;
     float vol = ((Float) volumes[check].value).floatValue();
     backgroundSound.setInitialGain(vol);
     pointSound.setInitialGain(vol);
     return;
   }
   // Handle all other checkboxes
   super.checkboxChanged(menu, check);
 }
 public void itemStateChanged(ItemEvent event) {
   Object src = event.getSource();
   if (src == backgroundSoundOnOffMenu) {
     // Turn the background sound on or off
     backgroundSoundOnOff = backgroundSoundOnOffMenu.getState();
     backgroundSound.setEnable(backgroundSoundOnOff);
     ambientLight.setEnable(backgroundSoundOnOff);
     return;
   }
   if (src == pointSoundOnOffMenu) {
     // Turn the point sound on or off
     pointSoundOnOff = pointSoundOnOffMenu.getState();
     pointSound.setEnable(pointSoundOnOff);
     pointLight.setEnable(pointSoundOnOff);
     return;
   }
   // Handle all other checkboxes
   super.itemStateChanged(event);
 }

} // //CLASS //ElevationGrid - a 3D terrain grid built from a list of heights // //DESCRIPTION //This class creates a 3D terrain on a grid whose X and Z dimensions, //and row/column spacing are parameters, along with a list of heights //(elevations), one per grid row/column pair. // class ElevationGrid extends Primitive {

 // Parameters
 protected int xDimension = 0, zDimension = 0;
 protected double xSpacing = 0.0, zSpacing = 0.0;
 protected double[] heights = null;
 // 3D nodes
 private Appearance mainAppearance = null;
 private Shape3D shape = null;
 private IndexedTriangleStripArray tristrip = null;
 //
 //  Construct an elevation grid
 //
 public ElevationGrid() {
   xDimension = 2;
   zDimension = 2;
   xSpacing = 1.0;
   zSpacing = 1.0;
   mainAppearance = null;
   zeroHeights();
   rebuild();
 }
 public ElevationGrid(int xDim, int zDim) {
   xDimension = xDim;
   zDimension = zDim;
   xSpacing = 1.0;
   zSpacing = 1.0;
   mainAppearance = null;
   zeroHeights();
   rebuild();
 }
 public ElevationGrid(int xDim, int zDim, Appearance app) {
   xDimension = xDim;
   zDimension = zDim;
   xSpacing = 1.0;
   zSpacing = 1.0;
   mainAppearance = app;
   zeroHeights();
   rebuild();
 }
 public ElevationGrid(int xDim, int zDim, double xSpace, double zSpace) {
   xDimension = xDim;
   zDimension = zDim;
   xSpacing = xSpace;
   zSpacing = zSpace;
   mainAppearance = null;
   zeroHeights();
   rebuild();
 }
 public ElevationGrid(int xDim, int zDim, double xSpace, double zSpace,
     Appearance app) {
   xDimension = xDim;
   zDimension = zDim;
   xSpacing = xSpace;
   zSpacing = zSpace;
   mainAppearance = app;
   zeroHeights();
   rebuild();
 }
 public ElevationGrid(int xDim, int zDim, double[] h) {
   this(xDim, zDim, 1.0, 1.0, h, null);
 }
 public ElevationGrid(int xDim, int zDim, double[] h, Appearance app) {
   this(xDim, zDim, 1.0, 1.0, h, app);
 }
 public ElevationGrid(int xDim, int zDim, double xSpace, double zSpace,
     double[] h) {
   this(xDim, zDim, xSpace, zSpace, h, null);
 }
 public ElevationGrid(int xDim, int zDim, double xSpace, double zSpace,
     double[] h, Appearance app) {
   xDimension = xDim;
   zDimension = zDim;
   xSpacing = xSpace;
   zSpacing = zSpace;
   mainAppearance = app;
   if (h == null)
     zeroHeights();
   else {
     heights = new double[h.length];
     for (int i = 0; i < h.length; i++)
       heights[i] = h[i];
   }
   rebuild();
 }
 private void zeroHeights() {
   int n = xDimension * zDimension;
   heights = new double[n];
   for (int i = 0; i < n; i++)
     heights[i] = 0.0;
 }
 private void rebuild() {
   // Build a shape
   if (shape == null) {
     shape = new Shape3D();
     shape.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
     shape.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
     shape.setAppearance(mainAppearance);
     addChild(shape);
   } else {
     shape.setAppearance(mainAppearance);
   }
   if (xDimension < 2 || zDimension < 2 || heights == null
       || heights.length < 4) {
     tristrip = null;
     shape.setGeometry(null);
     return;
   }
   // Create a list of coordinates, one per grid row/column
   double[] coordinates = new double[xDimension * zDimension * 3];
   double x, z;
   int n = 0, k = 0;
   z = ((double) (zDimension - 1)) * zSpacing / 2.0; // start at front edge
   for (int i = 0; i < zDimension; i++) {
     x = -((double) (xDimension - 1)) * xSpacing / 2.0;// start at left
     // edge
     for (int j = 0; j < xDimension; j++) {
       coordinates[n++] = x;
       coordinates[n++] = heights[k++];
       coordinates[n++] = z;
       x += xSpacing;
     }
     z -= zSpacing;
   }
   // Create a list of normals, one per grid row/column
   float[] normals = new float[xDimension * zDimension * 3];
   Vector3f one = new Vector3f(0.0f, 0.0f, 0.0f);
   Vector3f two = new Vector3f(0.0f, 0.0f, 0.0f);
   Vector3f norm = new Vector3f(0.0f, 0.0f, 0.0f);
   n = 0;
   k = 0;
   for (int i = 0; i < zDimension - 1; i++) {
     for (int j = 0; j < xDimension - 1; j++) {
       // Vector to right in X
       one.set((float) xSpacing,
           (float) (heights[k + 1] - heights[k]), 0.0f);
       // Vector back in Z
       two.set(0.0f, (float) (heights[k + xDimension] - heights[k]),
           (float) -zSpacing);
       // Cross them to get the normal
       norm.cross(one, two);
       normals[n++] = norm.x;
       normals[n++] = norm.y;
       normals[n++] = norm.z;
       k++;
     }
     // Last normal in row is a copy of the previous one
     normals[n] = normals[n - 3]; // X
     normals[n + 1] = normals[n - 2]; // Y
     normals[n + 2] = normals[n - 1]; // Z
     n += 3;
     k++;
   }
   // Last row of normals is a copy of the previous row
   for (int j = 0; j < xDimension; j++) {
     normals[n] = normals[n - xDimension * 3]; // X
     normals[n + 1] = normals[n - xDimension * 3 + 1]; // Y
     normals[n + 2] = normals[n - xDimension * 3 + 2]; // Z
     n += 3;
   }
   // Create a list of texture coordinates, one per grid row/column
   float[] texcoordinates = new float[xDimension * zDimension * 2];
   float deltaS = 1.0f / (float) (xDimension - 1);
   float deltaT = 1.0f / (float) (zDimension - 1);
   float s = 0.0f;
   float t = 0.0f;
   n = 0;
   for (int i = 0; i < zDimension; i++) {
     s = 0.0f;
     for (int j = 0; j < xDimension; j++) {
       texcoordinates[n++] = s;
       texcoordinates[n++] = t;
       s += deltaS;
     }
     t += deltaT;
   }
   // Create a list of triangle strip indexes. Each strip goes
   // down one row (X direction) of the elevation grid.
   int[] indexes = new int[xDimension * (zDimension - 1) * 2];
   int[] stripCounts = new int[zDimension - 1];
   n = 0;
   k = 0;
   for (int i = 0; i < zDimension - 1; i++) {
     stripCounts[i] = xDimension * 2;
     for (int j = 0; j < xDimension; j++) {
       indexes[n++] = k + xDimension;
       indexes[n++] = k;
       k++;
     }
   }
   // Create geometry for collection of triangle strips, one
   // strip per row of the elevation grid
   tristrip = new IndexedTriangleStripArray(coordinates.length,
       GeometryArray.COORDINATES | GeometryArray.NORMALS
           | GeometryArray.TEXTURE_COORDINATE_2, indexes.length,
       stripCounts);
   tristrip.setCoordinates(0, coordinates);
   tristrip.setNormals(0, normals);
   tristrip.setTextureCoordinates(0, texcoordinates);
   tristrip.setCoordinateIndices(0, indexes);
   tristrip.setNormalIndices(0, indexes);
   tristrip.setTextureCoordinateIndices(0, indexes);
   // Set the geometry for the shape
   shape.setGeometry(tristrip);
 }
 //
 //  Control the appearance
 //
 public void setAppearance(Appearance app) {
   mainAppearance = app;
   if (shape != null)
     shape.setAppearance(mainAppearance);
 }
 //
 //  Control grid parameters
 //
 public void setHeights(double[] h) {
   if (h == null)
     zeroHeights();
   else {
     heights = new double[h.length];
     for (int i = 0; i < h.length; i++)
       heights[i] = h[i];
   }
   rebuild();
 }
 public double[] getHeights() {
   return heights;
 }
 public void setXDimension(int xDim) {
   xDimension = xDim;
   rebuild();
 }
 public int getXDimension() {
   return xDimension;
 }
 public void setZDimension(int zDim) {
   zDimension = zDim;
   rebuild();
 }
 public int getZDimension() {
   return zDimension;
 }
 public void setXSpacing(double xSpace) {
   xSpacing = xSpace;
   rebuild();
 }
 public double getXSpacing() {
   return xSpacing;
 }
 public void setZSpacing(double zSpace) {
   zSpacing = zSpace;
   rebuild();
 }
 public double getZSpacing() {
   return zSpacing;
 }
 //
 //  Provide info on the shape and geometry
 //
 public Shape3D getShape(int partid) {
   return shape;
 }
 public int getNumTriangles() {
   return xDimension * zDimension * 2;
 }
 public int getNumVertices() {
   return xDimension * zDimension;
 }
 /*
  * (non-Javadoc)
  * 
  * @see com.sun.j3d.utils.geometry.Primitive#getAppearance(int)
  */
 public Appearance getAppearance(int arg0) {
   // TODO Auto-generated method stub
   return null;
 }

} // //CLASS //GothicColumn - Gothic-style column used in example scenes // //DESCRIPTION //This class builds a Gothic-column architectural column. // //SEE ALSO //? // //AUTHOR //David R. Nadeau / San Diego Supercomputer Center // // class GothicColumn extends Primitive {

 //
 //  Construction Flags
 //
 public final static int BUILD_TAPERED_CROWN = 0x1;
 public final static int BUILD_TOP = 0x2;
 public final static int BUILD_BOTTOM = 0x4;
 //
 //  3D nodes
 //
 private Appearance mainAppearance = null;
 //
 //  Construct a column
 //
 public GothicColumn(float height, float radius, Appearance app) {
   this(height, radius, 0, app);
 }
 public GothicColumn(float height, float radius, int flags, Appearance app) {
   mainAppearance = app;
   // Compute sizes and positions based upon the
   // desired main column radius
   // Base box
   float baseWidth = 2.7f * radius;
   float baseDepth = baseWidth;
   float baseHeight = 0.75f * radius / 2.0f;
   // Base box #2
   float base2Width = 0.8f * baseWidth;
   float base2Depth = base2Width;
   float base2Height = baseHeight / 2.0f;
   // Tapered crown
   float crownWidth1 = 2.0f * 0.707f * radius;
   float crownDepth1 = crownWidth1;
   float crownWidth2 = 1.0f * base2Width;
   float crownDepth2 = 1.0f * base2Depth;
   float crownHeight = 2.0f * baseHeight;
   // Box above tapered crown
   float crown2Width = 1.1f * base2Width;
   float crown2Depth = 1.1f * base2Depth;
   float crown2Height = base2Height;
   // Final crown box
   float crown3Width = 1.1f * baseWidth;
   float crown3Depth = 1.1f * baseDepth;
   float crown3Height = baseHeight;
   // Cylindrical column
   //   Extend it up and into the tapered crown
   float columnHeight = height - baseHeight - base2Height - crown2Height
       - crown3Height;
   float columnRadius = radius;
   float baseY = baseHeight / 2.0f;
   float base2Y = baseHeight + base2Height / 2.0f;
   float columnY = baseHeight + base2Height + columnHeight / 2.0f;
   float crown2Y = baseHeight + base2Height + columnHeight + crown2Height
       / 2.0f;
   float crown3Y = baseHeight + base2Height + columnHeight + crown2Height
       + crown3Height / 2.0f;
   float crownY = crown2Y - crown2Height / 2.0f - crownHeight / 2.0f;
   // Column base box
   int fl = BUILD_TOP;
   if ((flags & BUILD_BOTTOM) != 0)
     fl |= BUILD_BOTTOM;
   addBox(baseWidth, baseHeight, baseDepth, baseY, fl);
   // Column base box #2 (no bottom)
   addBox(base2Width, base2Height, base2Depth, base2Y, BUILD_TOP);
   // Main column (no top or bottom)
   addCylinder(columnRadius, columnHeight, columnY);
   // Column crown tapered box (no top or bottom)
   if ((flags & BUILD_TAPERED_CROWN) != 0) {
     addBox(crownWidth1, crownHeight, crownDepth1, crownY, crownWidth2,
         crownDepth2, 0);
   }
   // Box above tapered crown (no top)
   addBox(crown2Width, crown2Height, crown2Depth, crown2Y, BUILD_BOTTOM);
   // Final crown box
   fl = BUILD_BOTTOM;
   if ((flags & BUILD_TOP) != 0)
     fl |= BUILD_TOP;
   addBox(crown3Width, crown3Height, crown3Depth, crown3Y, fl);
 }
 //
 //  Add an untapered box
 //
 private void addBox(float width, float height, float depth, float y) {
   addBox(width, height, depth, y, width, depth, 0);
 }
 private void addBox(float width, float height, float depth, float y,
     int flags) {
   addBox(width, height, depth, y, width, depth, flags);
 }
 private void addBox(float width, float height, float depth, float y,
     float width2, float depth2) {
   addBox(width, height, depth, y, width2, depth2, 0);
 }
 //
 //  Add a tapered box
 //
 private void addBox(float width, float height, float depth, float y,
     float width2, float depth2, int flags) {
   float[] coordinates = {
   // around the bottom
       -width / 2.0f, -height / 2.0f, depth / 2.0f, width / 2.0f,
       -height / 2.0f, depth / 2.0f, width / 2.0f, -height / 2.0f,
       -depth / 2.0f, -width / 2.0f, -height / 2.0f, -depth / 2.0f,
       // around the top
       -width2 / 2.0f, height / 2.0f, depth2 / 2.0f, width2 / 2.0f,
       height / 2.0f, depth2 / 2.0f, width2 / 2.0f, height / 2.0f,
       -depth2 / 2.0f, -width2 / 2.0f, height / 2.0f, -depth2 / 2.0f, };
   int[] fullCoordinateIndexes = { 0, 1, 5, 4, // front
       1, 2, 6, 5, // right
       2, 3, 7, 6, // back
       3, 0, 4, 7, // left
       4, 5, 6, 7, // top
       3, 2, 1, 0, // bottom
   };
   float v = -(width2 - width) / height;
   float[] normals = { 0.0f, v, 1.0f, // front
       1.0f, v, 0.0f, // right
       0.0f, v, -1.0f, // back
       -1.0f, v, 0.0f, // left
       0.0f, 1.0f, 0.0f, // top
       0.0f, -1.0f, 0.0f, // bottom
   };
   int[] fullNormalIndexes = { 0, 0, 0, 0, // front
       1, 1, 1, 1, // right
       2, 2, 2, 2, // back
       3, 3, 3, 3, // left
       4, 4, 4, 4, // top
       5, 5, 5, 5, // bottom
   };
   float[] textureCoordinates = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
       0.0f, 1.0f, };
   int[] fullTextureCoordinateIndexes = { 0, 1, 2, 3, // front
       0, 1, 2, 3, // right
       0, 1, 2, 3, // back
       0, 1, 2, 3, // left
       0, 1, 2, 3, // top
       0, 1, 2, 3, // bottom
   };
   // Select indexes needed
   int[] coordinateIndexes;
   int[] normalIndexes;
   int[] textureCoordinateIndexes;
   if (flags == 0) {
     // build neither top or bottom
     coordinateIndexes = new int[4 * 4];
     textureCoordinateIndexes = new int[4 * 4];
     normalIndexes = new int[4 * 4];
     for (int i = 0; i < 4 * 4; i++) {
       coordinateIndexes[i] = fullCoordinateIndexes[i];
       textureCoordinateIndexes[i] = fullTextureCoordinateIndexes[i];
       normalIndexes[i] = fullNormalIndexes[i];
     }
   } else if ((flags & (BUILD_TOP | BUILD_BOTTOM)) == (BUILD_TOP | BUILD_BOTTOM)) {
     // build top and bottom
     coordinateIndexes = fullCoordinateIndexes;
     textureCoordinateIndexes = fullTextureCoordinateIndexes;
     normalIndexes = fullNormalIndexes;
   } else if ((flags & BUILD_TOP) != 0) {
     // build top but not bottom
     coordinateIndexes = new int[5 * 4];
     textureCoordinateIndexes = new int[5 * 4];
     normalIndexes = new int[5 * 4];
     for (int i = 0; i < 5 * 4; i++) {
       coordinateIndexes[i] = fullCoordinateIndexes[i];
       textureCoordinateIndexes[i] = fullTextureCoordinateIndexes[i];
       normalIndexes[i] = fullNormalIndexes[i];
     }
   } else {
     // build bottom but not top
     coordinateIndexes = new int[5 * 4];
     textureCoordinateIndexes = new int[5 * 4];
     normalIndexes = new int[5 * 4];
     for (int i = 0; i < 4 * 4; i++) {
       coordinateIndexes[i] = fullCoordinateIndexes[i];
       textureCoordinateIndexes[i] = fullTextureCoordinateIndexes[i];
       normalIndexes[i] = fullNormalIndexes[i];
     }
     for (int i = 5 * 4; i < 6 * 4; i++) {
       coordinateIndexes[i - 4] = fullCoordinateIndexes[i];
       textureCoordinateIndexes[i - 4] = fullTextureCoordinateIndexes[i];
       normalIndexes[i - 4] = fullNormalIndexes[i];
     }
   }
   IndexedQuadArray quads = new IndexedQuadArray(coordinates.length, // number
       // of
       // vertexes
       GeometryArray.COORDINATES | // vertex coordinates given
           GeometryArray.NORMALS | // normals given
           GeometryArray.TEXTURE_COORDINATE_2, // texture
       // coordinates given
       coordinateIndexes.length); // number of coordinate indexes
   quads.setCoordinates(0, coordinates);
   quads.setCoordinateIndices(0, coordinateIndexes);
   quads.setNormals(0, normals);
   quads.setNormalIndices(0, normalIndexes);
   quads.setTextureCoordinates(0, textureCoordinates);
   quads.setTextureCoordinateIndices(0, textureCoordinateIndexes);
   Shape3D box = new Shape3D(quads, mainAppearance);
   Vector3f trans = new Vector3f(0.0f, y, 0.0f);
   Transform3D tr = new Transform3D();
   tr.set(trans); // translate
   TransformGroup tg = new TransformGroup(tr);
   tg.addChild(box);
   addChild(tg);
 }
 private final static int NSTEPS = 16;
 private void addCylinder(float radius, float height, float y) {
   //
   //  Compute coordinates, normals, and texture coordinates
   //  around the top and bottom of a cylinder
   //
   float[] coordinates = new float[NSTEPS * 2 * 3]; // xyz
   float[] normals = new float[NSTEPS * 2 * 3]; // xyz vector
   float[] textureCoordinates = new float[NSTEPS * 2 * 2]; // st
   float angle = 0.0f;
   float deltaAngle = 2.0f * (float) Math.PI / ((float) NSTEPS - 1);
   float s = 0.0f;
   float deltaS = 1.0f / ((float) NSTEPS - 1);
   int n = 0;
   int tn = 0;
   float h2 = height / 2.0f;
   for (int i = 0; i < NSTEPS; i++) {
     // bottom
     normals[n + 0] = (float) Math.cos(angle);
     normals[n + 1] = 0.0f;
     normals[n + 2] = -(float) Math.sin(angle);
     coordinates[n + 0] = radius * normals[n + 0];
     coordinates[n + 1] = -h2;
     coordinates[n + 2] = radius * normals[n + 2];
     textureCoordinates[tn + 0] = s;
     textureCoordinates[tn + 1] = 0.0f;
     n += 3;
     tn += 2;
     // top
     normals[n + 0] = normals[n - 3];
     normals[n + 1] = 0.0f;
     normals[n + 2] = normals[n - 1];
     coordinates[n + 0] = coordinates[n - 3];
     coordinates[n + 1] = h2;
     coordinates[n + 2] = coordinates[n - 1];
     textureCoordinates[tn + 0] = s;
     textureCoordinates[tn + 1] = 1.0f;
     n += 3;
     tn += 2;
     angle += deltaAngle;
     s += deltaS;
   }
   //
   //  Compute coordinate indexes, normal indexes, and texture
   //  coordinate indexes awround the sides of a cylinder.
   //  For this application, we don"t need top or bottom, so
   //  skip them.
   //
   int[] indexes = new int[NSTEPS * 4];
   n = 0;
   int p = 0; // panel count
   for (int i = 0; i < NSTEPS - 1; i++) {
     indexes[n + 0] = p; // bottom left
     indexes[n + 1] = p + 2; // bottom right (next panel)
     indexes[n + 2] = p + 3; // top right (next panel)
     indexes[n + 3] = p + 1; // top left
     n += 4;
     p += 2;
   }
   indexes[n + 0] = p; // bottom left
   indexes[n + 1] = 0; // bottom right (next panel)
   indexes[n + 2] = 1; // top right (next panel)
   indexes[n + 3] = p + 1; // top left
   IndexedQuadArray quads = new IndexedQuadArray(coordinates.length / 3, // number
       // of
       // vertexes
       GeometryArray.COORDINATES | // format
           GeometryArray.NORMALS
           | GeometryArray.TEXTURE_COORDINATE_2, indexes.length); // number
   // of
   // indexes
   quads.setCoordinates(0, coordinates);
   quads.setTextureCoordinates(0, textureCoordinates);
   quads.setNormals(0, normals);
   quads.setCoordinateIndices(0, indexes);
   quads.setTextureCoordinateIndices(0, indexes);
   quads.setNormalIndices(0, indexes);
   Shape3D shape = new Shape3D(quads, mainAppearance);
   Vector3f trans = new Vector3f(0.0f, y, 0.0f);
   Transform3D tr = new Transform3D();
   tr.set(trans); // translate
   TransformGroup tg = new TransformGroup(tr);
   tg.addChild(shape);
   addChild(tg);
 }
 //
 //  Control the appearance
 //
 public void setAppearance(Appearance app) {
   mainAppearance = app;
 }
 //
 //  Provide info on the shape and geometry
 //
 public Shape3D getShape(int partid) {
   return null;
 }
 public int getNumTriangles() {
   return 0;
 }
 public int getNumVertices() {
   return 2;
 }
 /*
  * (non-Javadoc)
  * 
  * @see com.sun.j3d.utils.geometry.Primitive#getAppearance(int)
  */
 public Appearance getAppearance(int arg0) {
   // TODO Auto-generated method stub
   return null;
 }

} /**

* 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.
*

* 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.rupile(); 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: *

*
Button 1 (left) *
Horizontal movement --> Y-axis rotation *
Vertical movement --> Z-axis translation * *
Button 2 (middle) *
Horizontal movement --> Y-axis rotation *
Vertical movement --> X-axis rotation * *
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 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;
 }

}

      </source>
   
  
 
  



Sound Test

   <source lang="java">

/*

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

import java.applet.Applet; import java.awt.BorderLayout; import java.awt.GraphicsConfiguration; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.NumberFormat; import javax.media.j3d.BackgroundSound; import javax.media.j3d.BoundingSphere; import javax.media.j3d.BranchGroup; import javax.media.j3d.Canvas3D; import javax.media.j3d.MediaContainer; import javax.media.j3d.PointSound; import javax.media.j3d.Sound; import javax.media.j3d.Switch; import javax.media.j3d.Transform3D; import javax.media.j3d.View; import javax.swing.ButtonGroup; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.vecmath.AxisAngle4f; import javax.vecmath.Color3f; import javax.vecmath.Point2f; import javax.vecmath.Point3d; import javax.vecmath.Point3f; import javax.vecmath.Vector3f; import com.sun.j3d.utils.applet.MainFrame; import com.sun.j3d.utils.behaviors.vp.OrbitBehavior; import com.sun.j3d.utils.universe.SimpleUniverse; import com.sun.j3d.utils.universe.ViewingPlatform; public class SoundBug extends Applet implements ActionListener {

 // Scene graph items
 SimpleUniverse u;
 Switch spheresSwitch;
 Switch soundSwitch;
 // temp image grabber
 boolean isApplication;
 Canvas3D canvas;
 View view;
 // Sound items
 String soundNoneString = "No sound";
 String soundBackgroundString = "Background";
 String soundPointString = "Point";
 String soundConeString = "Cone";
 String soundBackgroundActionString = "SBackground";
 String soundPointActionString = "SPoint";
 JRadioButton soundNoneButton;
 JRadioButton soundBackgroundButton;
 JRadioButton soundPointButton;
 JRadioButton soundConeButton;
 BackgroundSound soundBackground;
 PointSound soundPoint;
 static final int SOUND_NONE = Switch.CHILD_NONE;
 static final int SOUND_BACKGROUND = 0;
 static final int SOUND_POINT = 1;
 // Temporaries that are reused
 Transform3D tmpTrans = new Transform3D();
 Vector3f tmpVector = new Vector3f();
 AxisAngle4f tmpAxisAngle = new AxisAngle4f();
 // colors
 Color3f red = new Color3f(1.0f, 0.0f, 0.0f);
 Color3f black = new Color3f(0.0f, 0.0f, 0.0f);
 Color3f white = new Color3f(1.0f, 1.0f, 1.0f);
 Color3f grey = new Color3f(0.1f, 0.1f, 0.1f);
 Color3f skyBlue = new Color3f(0.6f, 0.7f, 0.9f);
 // geometric constant
 Point3f origin = new Point3f();
 Vector3f yAxis = new Vector3f(0.0f, 1.0f, 0.0f);
 // NumberFormat to print out floats with only two digits
 NumberFormat nf;
 /*
  * Set up the sound switch. The child values are: SOUND_BACKGROUND:
  * BackgroundSound SOUND_POINT: PointSound
  */
 void setupSounds() {
   soundSwitch = new Switch(Switch.CHILD_NONE);
   soundSwitch.setWhichChild(Switch.CHILD_NONE);
   soundSwitch.setCapability(Switch.ALLOW_SWITCH_WRITE);
   // set up the BoundingSphere for all the sounds
   BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);
   // Set up the sound media container
   java.net.URL soundURL = null;
   String soundFile = "techno_machine.au";
   try {
     java.net.URL codeBase = null;
     try {
       codeBase = getCodeBase();
     } catch (Exception ex) {
       // got an exception, probably running as an application,
       // keep code base null...
     }
     if (codeBase != null) {
       soundURL = new java.net.URL(codeBase.toString() + soundFile);
     }
   } catch (java.net.MalformedURLException ex) {
     System.out.println(ex.getMessage());
     System.exit(1);
   }
   if (soundURL == null) { // application, try file URL
     try {
       soundURL = new java.net.URL("file:./" + soundFile);
     } catch (java.net.MalformedURLException ex) {
       System.out.println(ex.getMessage());
       System.exit(1);
     }
   }
   System.out.println("soundURL = " + soundURL);
   MediaContainer soundMC = new MediaContainer(soundURL);
   // set up the Background Sound
   soundBackground = new BackgroundSound();
   soundBackground.setCapability(Sound.ALLOW_ENABLE_WRITE);
   soundBackground.setSoundData(soundMC);
   soundBackground.setSchedulingBounds(bounds);
   soundBackground.setEnable(true);
   soundBackground.setLoop(Sound.INFINITE_LOOPS);
   soundSwitch.addChild(soundBackground);
   // set up the point sound
   soundPoint = new PointSound();
   soundPoint.setCapability(Sound.ALLOW_ENABLE_WRITE);
   soundPoint.setSoundData(soundMC);
   soundPoint.setSchedulingBounds(bounds);
   soundPoint.setEnable(true);
   soundPoint.setLoop(Sound.INFINITE_LOOPS);
   soundPoint.setPosition(-5.0f, -5.0f, 0.0f);
   Point2f[] distGain = new Point2f[2];
   // set the attenuation to linearly decrease volume from max at
   // source to 0 at a distance of 5m
   distGain[0] = new Point2f(0.0f, 1.0f);
   distGain[1] = new Point2f(5.0f, 0.0f);
   soundPoint.setDistanceGain(distGain);
   soundSwitch.addChild(soundPoint);
 }
 public void actionPerformed(ActionEvent e) {
   String action = e.getActionCommand();
   Object source = e.getSource();
   if (action == soundNoneString) {
     soundSwitch.setWhichChild(SOUND_NONE);
   } else if (action == soundBackgroundActionString) {
     soundSwitch.setWhichChild(SOUND_BACKGROUND);
   } else if (action == soundPointActionString) {
     soundSwitch.setWhichChild(SOUND_POINT);
   }
 }
 BranchGroup createSceneGraph() {
   // Create the root of the branch graph
   BranchGroup objRoot = new BranchGroup();
   // Add the primitives to the scene
   setupSounds();
   objRoot.addChild(soundSwitch);
   return objRoot;
 }
 public SoundBug(boolean isApplication) {
   this.isApplication = isApplication;
 }
 public SoundBug() {
   this(false);
 }
 public void init() {
   // set up a NumFormat object to print out float with only 3 fraction
   // digits
   nf = NumberFormat.getInstance();
   nf.setMaximumFractionDigits(3);
   setLayout(new BorderLayout());
   GraphicsConfiguration config = SimpleUniverse
       .getPreferredConfiguration();
   canvas = null;
   canvas = new Canvas3D(config);
   add("Center", canvas);
   // Create a simple scene and attach it to the virtual universe
   BranchGroup scene = createSceneGraph();
   u = new SimpleUniverse(canvas);
   // set up sound
   u.getViewer().createAudioDevice();
   // get the view
   view = u.getViewer().getView();
   // Get the viewing platform
   ViewingPlatform viewingPlatform = u.getViewingPlatform();
   // Move the viewing platform back to enclose the -4 -> 4 range
   double viewRadius = 4.0; // want to be able to see circle
   // of viewRadius size around origin
   // get the field of view
   double fov = u.getViewer().getView().getFieldOfView();
   // calc view distance to make circle view in fov
   float viewDistance = (float) (viewRadius / Math.tan(fov / 2.0));
   tmpVector.set(0.0f, 0.0f, viewDistance);// setup offset
   tmpTrans.set(tmpVector); // set trans to translate
   // move the view platform
   viewingPlatform.getViewPlatformTransform().setTransform(tmpTrans);
   // add an orbit behavior to move the viewing platform
   OrbitBehavior orbit = new OrbitBehavior(canvas, OrbitBehavior.STOP_ZOOM);
   BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
       100.0);
   orbit.setSchedulingBounds(bounds);
   viewingPlatform.setViewPlatformBehavior(orbit);
   u.addBranchGraph(scene);
   add("South", soundPanel());
 }
 JPanel soundPanel() {
   JPanel panel = new JPanel();
   panel.setLayout(new GridLayout(4, 1));
   // create the buttons
   soundNoneButton = new JRadioButton(soundNoneString);
   soundBackgroundButton = new JRadioButton(soundBackgroundString);
   soundPointButton = new JRadioButton(soundPointString);
   //soundConeButton = new JRadioButton(soundConeString);
   // set up the action commands
   soundNoneButton.setActionCommand(soundNoneString);
   soundBackgroundButton.setActionCommand(soundBackgroundActionString);
   soundPointButton.setActionCommand(soundPointActionString);
   //soundConeButton.setActionCommand(soundConeString);
   // add the buttons to a group so that only one can be selected
   ButtonGroup buttonGroup = new ButtonGroup();
   buttonGroup.add(soundNoneButton);
   buttonGroup.add(soundBackgroundButton);
   buttonGroup.add(soundPointButton);
   //buttonGroup.add(soundConeButton);
   // register the applet as the listener for the buttons
   soundNoneButton.addActionListener(this);
   soundBackgroundButton.addActionListener(this);
   soundPointButton.addActionListener(this);
   // soundConeButton.addActionListener(this);
   // add the buttons to the panel
   panel.add(soundNoneButton);
   panel.add(soundBackgroundButton);
   panel.add(soundPointButton);
   //panel.add(soundConeButton);
   // set the default
   soundNoneButton.setSelected(true);
   return panel;
 }
 public void destroy() {
   u.removeAllLocales();
 }
 // The following allows SoundBug to be run as an application
 // as well as an applet
 //
 public static void main(String[] args) {
   new MainFrame(new SoundBug(true), 600, 900);
 }

}


      </source>
   
  
 
  



This application demonstrates the use of 3D sound

   <source lang="java">

/* Essential Java 3D Fast Ian Palmer Publisher: Springer-Verlag ISBN: 1-85233-394-4

  • /

import java.awt.BorderLayout; import java.awt.Button; import java.awt.Frame; import java.awt.Panel; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.media.j3d.AmbientLight; import javax.media.j3d.Appearance; import javax.media.j3d.BackgroundSound; import javax.media.j3d.BoundingLeaf; import javax.media.j3d.BoundingSphere; import javax.media.j3d.BranchGroup; import javax.media.j3d.Canvas3D; import javax.media.j3d.DirectionalLight; import javax.media.j3d.Locale; import javax.media.j3d.Material; import javax.media.j3d.MediaContainer; import javax.media.j3d.PhysicalBody; import javax.media.j3d.PhysicalEnvironment; import javax.media.j3d.PointSound; import javax.media.j3d.Transform3D; import javax.media.j3d.TransformGroup; import javax.media.j3d.View; import javax.media.j3d.ViewPlatform; import javax.media.j3d.VirtualUniverse; import javax.vecmath.Color3f; import javax.vecmath.Point2f; import javax.vecmath.Point3d; import javax.vecmath.Point3f; import javax.vecmath.Vector3f; import com.sun.j3d.audioengines.javasound.JavaSoundMixer; import com.sun.j3d.utils.behaviors.keyboard.KeyNavigatorBehavior; import com.sun.j3d.utils.geometry.Box; /**

* This application demonstrates the use of 3D sound. It loads three sounds:
* loop3.wav is an ambient background sound and loop1.wav and loop2.wav are
* point sounds. The two point sounds can be switched on and off use AWT
* buttons. The user can navigate around the scene using the keyboard.
* 
* @author I.J.Palmer
* @version 1.0
*/

public class SimpleSounds extends Frame implements ActionListener {

 protected Canvas3D myCanvas3D = new Canvas3D(null);
 /** The exit button to quit the application */
 protected Button exitButton = new Button("Exit");
 /** The button to switch on and off the first sound */
 protected Button sound1Button = new Button("Sound 1");
 /** The button to switch on and off the second sound */
 protected Button sound2Button = new Button("Sound 2");
 protected BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0,
     0.0), 10000.0);
 //Create the two point sounds
 PointSound sound1 = new PointSound();
 PointSound sound2 = new PointSound();
 /**
  * Build the view branch of the scene graph. In this case a key navigation
  * utility object is created and associated with the view transform so that
  * the view can be changed via the keyboard.
  * 
  * @return BranchGroup that is the root of the view branch
  */
 protected BranchGroup buildViewBranch(Canvas3D c) {
   BranchGroup viewBranch = new BranchGroup();
   Transform3D viewXfm = new Transform3D();
   viewXfm.set(new Vector3f(0.0f, 0.0f, 30.0f));
   TransformGroup viewXfmGroup = new TransformGroup(viewXfm);
   viewXfmGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
   viewXfmGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
   ViewPlatform myViewPlatform = new ViewPlatform();
   BoundingSphere movingBounds = new BoundingSphere(new Point3d(0.0, 0.0,
       0.0), 100.0);
   BoundingLeaf boundLeaf = new BoundingLeaf(movingBounds);
   PhysicalBody myBody = new PhysicalBody();
   PhysicalEnvironment myEnvironment = new PhysicalEnvironment();
   viewXfmGroup.addChild(myViewPlatform);
   viewBranch.addChild(viewXfmGroup);
   View myView = new View();
   myView.addCanvas3D(c);
   myView.attachViewPlatform(myViewPlatform);
   myView.setPhysicalBody(myBody);
   myView.setPhysicalEnvironment(myEnvironment);
   KeyNavigatorBehavior keyNav = new KeyNavigatorBehavior(viewXfmGroup);
   keyNav.setSchedulingBounds(movingBounds);
   viewBranch.addChild(keyNav);
   //Create a sounds mixer to use our sounds with
   //and initialise it
   JavaSoundMixer myMixer = new JavaSoundMixer(myEnvironment);
   myMixer.initialize();
   return viewBranch;
 }
 /**
  * Add some lights to the scene graph
  * 
  * @param b
  *            BranchGroup that the lights are added to
  */
 protected void addLights(BranchGroup b) {
   Color3f ambLightColour = new Color3f(0.5f, 0.5f, 0.5f);
   AmbientLight ambLight = new AmbientLight(ambLightColour);
   ambLight.setInfluencingBounds(bounds);
   Color3f dirLightColour = new Color3f(1.0f, 1.0f, 1.0f);
   Vector3f dirLightDir = new Vector3f(-1.0f, -1.0f, -1.0f);
   DirectionalLight dirLight = new DirectionalLight(dirLightColour,
       dirLightDir);
   dirLight.setInfluencingBounds(bounds);
   b.addChild(ambLight);
   b.addChild(dirLight);
 }
 /**
  * This adds a continuous background sound to the branch group.
  * 
  * @param b
  *            BranchGroup to add the sound to.
  * @param soundFile
  *            String that is the name of the sound file.
  */
 protected void addBackgroundSound(BranchGroup b, String soundFile) {
   //Create a media container to load the file
   MediaContainer droneContainer = new MediaContainer(soundFile);
   //Create the background sound from the media container
   BackgroundSound drone = new BackgroundSound(droneContainer, 1.0f);
   //Activate the sound
   drone.setSchedulingBounds(bounds);
   drone.setEnable(true);
   //Set the sound to loop forever
   drone.setLoop(BackgroundSound.INFINITE_LOOPS);
   //Add it to the group
   b.addChild(drone);
 }
 /**
  * Add a sound to the transform group. This takes a point sound object and
  * loads into it a sounds from a given file. The edge of the sound"s extent
  * is also defined in a parameter.
  * 
  * @param tg
  *            TransformGroup that the sound is to be added to
  * @param sound
  *            PointSound to be used
  * @param soundFile
  *            String that is the name of the sound file to be loaded
  * @param edge
  *            float that represents the sound"s maximum extent
  */
 protected void addObjectSound(TransformGroup tg, PointSound sound,
     String soundFile, float edge) {
   //First we get the current transform so that we can
   //position the sound in the same place
   Transform3D objXfm = new Transform3D();
   Vector3f objPosition = new Vector3f();
   tg.getTransform(objXfm);
   objXfm.get(objPosition);
   //Create the media container to load the sound
   MediaContainer soundContainer = new MediaContainer(soundFile);
   //Use the loaded data in the sound
   sound.setSoundData(soundContainer);
   sound.setInitialGain(1.0f);
   //Set the position to that of the given transform
   sound.setPosition(new Point3f(objPosition));
   //Allow use to switch the sound on and off
   sound.setCapability(PointSound.ALLOW_ENABLE_READ);
   sound.setCapability(PointSound.ALLOW_ENABLE_WRITE);
   sound.setSchedulingBounds(bounds);
   //Set it off to start with
   sound.setEnable(false);
   //Set it to loop forever
   sound.setLoop(BackgroundSound.INFINITE_LOOPS);
   //Use the edge value to set to extent of the sound
   Point2f[] attenuation = { new Point2f(0.0f, 1.0f),
       new Point2f(edge, 0.1f) };
   sound.setDistanceGain(attenuation);
   //Add the sound to the transform group
   tg.addChild(sound);
 }
 protected BranchGroup buildContentBranch() {
   //Create the appearance
   Appearance app = new Appearance();
   Color3f ambientColour = new Color3f(1.0f, 0.0f, 0.0f);
   Color3f emissiveColour = new Color3f(0.0f, 0.0f, 0.0f);
   Color3f specularColour = new Color3f(1.0f, 1.0f, 1.0f);
   Color3f diffuseColour = new Color3f(1.0f, 0.0f, 0.0f);
   float shininess = 20.0f;
   app.setMaterial(new Material(ambientColour, emissiveColour,
       diffuseColour, specularColour, shininess));
   //Make the cube
   Box myCube = new Box(1.0f, 1.0f, 1.0f, app);
   TransformGroup cubeGroup = new TransformGroup();
   BranchGroup contentBranch = new BranchGroup();
   addLights(contentBranch);
   addObjectSound(cubeGroup, sound1, new String("file:./loop1.wav"), 10.0f);
   addObjectSound(cubeGroup, sound2, new String("file:./loop2.wav"), 20.0f);
   addBackgroundSound(contentBranch, new String("file:./loop3.wav"));
   cubeGroup.addChild(myCube);
   contentBranch.addChild(cubeGroup);
   return contentBranch;
 }
 public void actionPerformed(ActionEvent e) {
   if (e.getSource() == exitButton) {
     dispose();
     System.exit(0);
   } else if (e.getSource() == sound1Button) {
     if (sound1.getEnable())
       sound1.setEnable(false);
     else
       sound1.setEnable(true);
   } else if (e.getSource() == sound2Button) {
     sound2.setEnable(!sound2.getEnable());
   }
 }
 public SimpleSounds() {
   VirtualUniverse myUniverse = new VirtualUniverse();
   Locale myLocale = new Locale(myUniverse);
   //              buildUniverse(myCanvas3D);
   myLocale.addBranchGraph(buildContentBranch());
   myLocale.addBranchGraph(buildViewBranch(myCanvas3D));
   setTitle("SimpleSounds");
   setSize(400, 400);
   setLayout(new BorderLayout());
   Panel bottom = new Panel();
   bottom.add(sound1Button);
   bottom.add(sound2Button);
   bottom.add(exitButton);
   add(BorderLayout.CENTER, myCanvas3D);
   add(BorderLayout.SOUTH, bottom);
   exitButton.addActionListener(this);
   sound1Button.addActionListener(this);
   sound2Button.addActionListener(this);
   setVisible(true);
 }
 public static void main(String[] args) {
   SimpleSounds sw = new SimpleSounds();
 }

}


      </source>