< Zurück | Inhalt | Weiter >

From SwingTest.java

//These are the fairly standard imports that we will be using

//for many of the examples in the book. The “core” Java 3D code

//resides in the javax.media.j3d package. import java.applet.Applet;

import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.io.*;

import javax.media.j3d.*; import javax.vecmath.*; import javax.swing.*;

import com.sun.image.codec.jpeg.*;


* The SwingTest class extends the Swing JPanel and

* can therefore be added to a Swing JFrame or JPanel as a component

* of the user interface. The SwingTest class contains the 3D display,

* and responds to Swing user interface events by implementing

* the ActionListener interface. The main entry point for the

* application creates the JFrame that will house the SwingTest JPanel

* as well as the JmenuBar, which will generate the User Interface

* events.


public class SwingTest extends JPanel implements ActionListener



* Java 3D applications describe a 3D scene to the rendering system

* that produces the rendered frames. The 3D scene description

* is added to a tree (directed−acyclic−graph) data structure

* called a scenegraph. The scenegraph tree has two major branches:

* the scene branch describes the objects and lights in the scene

* as well as any behaviors that act upon the scene. The view branch

* of the scenegraph describes the viewer of the scene, including

* their position, screen device, and viewing parameters.


* SwingTest needs to modify the objects on the scene side

* of the scenegraph at runtime, so we keep a reference to it in the

* sceneBranchGroup member variable below.


private BranchGroup sceneBranchGroup = null;


* SwingTest rotates the objects in the scene using a

* RotationInterpolator behavior that will automatically modify

* the rotation components of a 4 × 4 transformation matrix.

* Objects that are attached to the transformation matrix object

* (TransformGroup) will therefore be automatically rotated.


private RotationInterpolator rotator = null;

/* Java 3D will render the scene into a Canvas3D component.

* To perform frame captures and save the frames to disk, we keep

* a reference to an offscreen (nonvisible) Canvas3D component that

* can be explicitly called to render individual frames.


private Canvas3D offScreenCanvas3D = null;


* The image that is attached to the off−screen Canvas3D and

* contains the results of screen captures


private ImageComponent2D imageComponent = null;

//The width of the offscreen Canvas3D

private static final int offScreenWidth = 400;

//The height of the offscreen Canvas3D

private static final int offScreenHeight = 400;


//Set the layout algorithm for the panel and initialize Java 3D

//and the scene. public SwingTest()


setLayout( new BorderLayout() ); init();


//The init method does all of the work of setting up and

//populating the scenegraph. protected void init()



* Every Java 3D application has an instance of a class derived from

* VirtualUniverse. The VirtualUniverse class contains a number of

* Locales objects, each of which describes a discrete region

* within the scene, and has its own coordinate system. By deriving

* your own class from VirtualUniverse you can define utility methods

* or additional datastructures for your application.


VirtualUniverse universe = createVirtualUniverse();


* A Locale object allows a coordinate system to be specified

* for a region within the scene. By having multiple Locales

* in your scene you can have multiple levels of detail without

* losing coordinate precision due to rounding errors.


Locale locale = createLocale( universe );


* A BranchGroup is a branch of the scenegraph tree. A BranchGroup

* has a single parent Node and can have multiple child Nodes. The

* sceneBranchGroup created below contains the graphical objects,

* lights, and behaviors that will compose the rendered scene.


BranchGroup sceneBranchGroup = createSceneBranchGroup();


* A Background Node allows you to specify a colored background,

* background image, or background geometry for your application.

* In this example we simply create a light−gray background color and

* add it to the scene side of the scenegraph. Java 3D will

* automatically detect that it is a Background Node and

* paint the background color into the Canvas3D prior to rendering

* the scene geometry.


Background background = createBackground();

if( background != null ) sceneBranchGroup.addChild( background );


* We must now define the view side of the scenegraph. First

* we create a ViewPlatform. The ViewPlatform defines a location

* in the scene from which the scene can be viewed. The scene

* can contain multiple ViewPlatforms, and View objects can be moved

* between them at runtime.


ViewPlatform vp = createViewPlatform();


* To contain the ViewPlatform we create a scenegraph branch.

* We create a BranchGroup that is the top of the view branch.

* Underneath it we create a series of TransformGroup, and then

* finally we attach the ViewPlatform to the lowest TransformGroup.

* The TransformGroups (which contain a 4 × 4 transformation matrix)

* allow the ViewPlatform to be rotate, scaled, and translated within

* the scene.


BranchGroup viewBranchGroup =

createViewBranchGroup( getViewTransformGroupArray(), vp );

//We then have to add the scene branch to the Locale

//we added previously to the VirtualUniverse. locale.addBranchGraph( sceneBranchGroup );

//Add the view branch to the Locale addViewBranchGroup( locale, viewBranchGroup );


* Finally, create the View object and attach it to

* the ViewPlatform. The View object has an associated

* PhysicalEnvironment and PhysicalBody that defines the

* characteristics of the viewer and their display hardware.

* A Canvas3D rendering component is attached to the View which

* is used to display the frames rendered.


createView( vp );



* Callback to allow the Canvas3D to be added to a Panel. This method

* is called by createView and allows the Canvas3D to be added to its

* parent GUI components, in this can as SwingTest is extends JPanel we

* can just add it directly to SwingTest.


protected void addCanvas3D( Canvas3D c3d )


add( "Center", c3d );


//Helper method to create a Java 3D View and

//attach it to a ViewPlatform .

protected View createView( ViewPlatform vp )


View view = new View();

//We create a default PhysicalBody and PhysicalEnvironment and

//associate them with the View. PhysicalBody pb = createPhysicalBody();

PhysicalEnvironment pe = createPhysicalEnvironment(); view.setPhysicalEnvironment( pe ); view.setPhysicalBody( pb );

//Add the View to the ViewPlatform if( vp != null )

view.attachViewPlatform( vp );


* Set the locations of the clipping planes for the View.

* Java 3D uses a finite number of bits (in a depth−buffer) to

* track the relative distances of objects from the viewer.

* These depth−buffer bits are used to track objects between

* the front clipping plane and the rear clipping plane. Only objects

* that fall between the two clipping planes will be rendered. As the

* depth−buffer bits have a finite length (usually 16 or 24 bits)

* the ratio between the front clipping plane and the rear clipping

* plane should be less than about 1000, or the depth−buffer will be

* very coarsely quantized and accuracy will be lost. In this example

* we use 1.0 for the front clipping plane and 100.0 for the rear

* clipping plane.


view.setBackClipDistance( getBackClipDistance() ); view.setFrontClipDistance( getFrontClipDistance() );

//Create the Canvas3D used to display the rendered scene Canvas3D c3d = createCanvas3D( false );

//Add the Canvas3D to the View so that the View has a component

//to render into. view.addCanvas3D( c3d );

//Here we create and add on the offscreen Canvas3D instance

//that we use for screen captures. view.addCanvas3D( createOffscreenCanvas3D() );

//Finally, invoke the addCanvas3D callback method that will add

//the visible Canvas3D to a GUI component (JPanel) addCanvas3D( c3d );

return view;


/Simple utility method to create a solid colored background for

//the Canvas3D.

protected Background createBackground()


//We create a color by specifying the Red, Green, and Blue

//components, in this case a light gray. Background back = new Background(

new Color3f( 0.9f, 0.9f, 0.9f ) );

//We need to set the volume within the scene within which the

//Background is active

back.setApplicationBounds( createApplicationBounds() ); return back;



* Simple utility method that returns a bounding volume

* for the application. In this case we create a spherical volume,

* centered at 0,0,0 and with a radius of 100.


protected Bounds createApplicationBounds()


return new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);


//Utility method to create a Canvas3D GUI component. The Canvas3D

//is used by Java 3D to output rendered frames. protected Canvas3D createCanvas3D( boolean offscreen )



* First we query Java 3D for the available device information.

* We set up a GraphicsConfigTemplate3D and specify that we would

* prefer a device configuration that supports antialiased output.


GraphicsConfigTemplate3D gc3D = new GraphicsConfigTemplate3D(); gc3D.setSceneAntialiasing( GraphicsConfigTemplate.PREFERRED );

//We then get a list of all the screen devices for the

//local graphics environment GraphicsDevice gd[] = GraphicsEnvironment.

getLocalGraphicsEnvironment(). getScreenDevices();

//We select the best configuration supported by the first screen

//device, and specify whether we are creating an onscreen or

//an offscreen Canvas3D.

Canvas3D c3d = new Canvas3D( gd[0].getBestConfiguration( gc3D ), offscreen );


* Here we have hard−coded the initial size of the Canvas3D.

* However, because we have used a BorderLayout layout algorithm,

* this will be automatically resized to fit—−as the parent JFrame

* is resized.


c3d.setSize( 500, 500 );

return c3d;


//Callback to get the scale factor for the View side of the


protected double getScale()


return 3;



* Get a TransformGroup array for the View side of the scenegraph.

* We create a single TransformGroup (which wraps a 4 × 4 transformation

* matrix) and modify the transformation matrix to apply a scale to

* the view of the scene, as well as move the ViewPlatform back

* by 20 meters so that we can see the origin (0,0,0). The objects

* that we create in the scene will be centered at the origin, so if

* we are going to be able to see them, we need to move the

* ViewPlatform backward.


public TransformGroup[] getViewTransformGroupArray()


TransformGroup[] tgArray = new TransformGroup[1]; tgArray[0] = new TransformGroup();


* Here we move the camera BACK a little so that we can see

* the origin (0,0,0). Note that we have to invert the matrix as

* we are moving the viewer not the scene.


Transform3D t3d = new Transform3D(); t3d.setScale( getScale() );

t3d.setTranslation( new Vector3d( 0.0, 0.0, −20.0 ) ); t3d.invert();

tgArray[0].setTransform( t3d );

return tgArray;


//Simple utility method that adds the View side of the scenegraph

//to the Locale

protected void addViewBranchGroup( Locale locale, BranchGroup bg )


locale.addBranchGraph( bg );


//Simple utility method that creates a Locale for the


protected Locale createLocale( VirtualUniverse u )


return new Locale( u );


//Create the PhysicalBody for the View. We just use a default


protected PhysicalBody createPhysicalBody()


return new PhysicalBody();


//Create the PhysicalEnvironment for the View. We just use a

//default PhysicalEnvironment.

protected PhysicalEnvironment createPhysicalEnvironment()


return new PhysicalEnvironment();


//Return the View Platform Activation Radius. protected float getViewPlatformActivationRadius()


return 100;


//Create the View Platform for the View. protected ViewPlatform createViewPlatform()


ViewPlatform vp = new ViewPlatform(); vp.setViewAttachPolicy( View.RELATIVE_TO_FIELD_OF_VIEW ); vp.setActivationRadius( getViewPlatformActivationRadius() );

return vp;


//Return the distance to the rear clipping plane. protected double getBackClipDistance()


return 100.0;


//Return the distance to the near clipping plane. protected double getFrontClipDistance()


return 1.0;


//Create the View side BranchGroup. The ViewPlatform is wired in

//beneath the TransformGroups.

protected BranchGroup createViewBranchGroup( TransformGroup[] tgArray, ViewPlatform vp )


BranchGroup vpBranchGroup = new BranchGroup();

if( tgArray != null &tgArray.length > 0 )


Group parentGroup = vpBranchGroup; TransformGroup curTg = null;

for( int n = 0; n <tgArray.length; n++ )


curTg = tgArray[n]; parentGroup.addChild( curTg ); parentGroup = curTg;


tgArray[tgArray.length−1].addChild( vp );



vpBranchGroup.addChild( vp );

return vpBranchGroup;


//Create the VirtualUniverse for the application. protected VirtualUniverse createVirtualUniverse()


return new VirtualUniverse();


//Utility method that performs some additional initialization

//for an offscreen Canvas3D.

protected Canvas3D createOffscreenCanvas3D()


//First we create a Canvas3D and specify that it is to be used

//for offscreen rendering. offScreenCanvas3D = createCanvas3D( true );

//We then need to explicitly set the size of the off screen


offScreenCanvas3D.getScreen3D().setSize( offScreenWidth,

offScreenHeight );

//This calculation returns the physical size of the screen and

//is based on 90 display pixels per inch offScreenCanvas3D.getScreen3D().

setPhysicalScreenHeight( 0.0254/90 * offScreenHeight ); offScreenCanvas3D.getScreen3D().

setPhysicalScreenWidth( 0.0254/90 * offScreenWidth );

//We then create an AWT RenderedImage that the Canvas3D will

//render into. We create a simple 3 Byte RGB format image. RenderedImage renderedImage =

new BufferedImage( offScreenWidth, offScreenHeight,

BufferedImage.TYPE_3BYTE_BGR );

//The AWT RenderedImage needs to be wrapped in a Java 3D

//ImageComponent2D before it can be assigned to the

//Canvas3D for rendering imageComponent =

new ImageComponent2D( ImageComponent.FORMAT_RGB8,

renderedImage );

//This call notifies Java 3D that we require read−access to the

//ImageComponent2D. We will be reading the pixels in the image

//when we output it to disk.

imageComponent.setCapability( ImageComponent2D.ALLOW_IMAGE_READ );

//Finally, we assign the ImageComponent2D to the offscreen

//Canvas3D for rendering offScreenCanvas3D.setOffScreenBuffer( imageComponent );

return offScreenCanvas3D;


//Create the scene side of the scenegraph. This method does

//all the work of creating the scene branch—containing graphical

//objects, lights, and rotation behaviors to rotate the objects. protected BranchGroup createSceneBranchGroup()


//First we create the root of the scene side scenegraph. We will

//add other Nodes as children of this root BranchGroup. BranchGroup objRoot = new BranchGroup();


* Create a TransformGroup to rotate the objects in the scene

* and set the capability bits on the TransformGroup so that

* it can be modified at runtime by the rotation behavior.


TransformGroup objTrans = new TransformGroup(); objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);


//Create a spherical bounding volume that will define the volume

//within which the rotation behavior is active. BoundingSphere bounds = new BoundingSphere(

new Point3d(0.0,0.0,0.0), 100.0);

//Create a 4 × 4 transformation matrix Transform3D yAxis = new Transform3D();


* Create an Alpha interpolator to automatically generate

* modifications to the rotation component of the transformation

* matrix. This Alpha loops indefinitely and generates numbers

* from 0 to 1 every 4000 milliseconds.


Alpha rotationAlpha = new Alpha(−1, Alpha.INCREASING_ENABLE, 0, 0,

4000, 0, 0,

0, 0, 0);


* Create a RotationInterpolator behavior to effect the

* TransformGroup. Here we will rotate from 0 to 2p degrees about

* the Y−axis based on the output of rotationAlpha.


rotator = new RotationInterpolator( rotationAlpha,

objTrans, yAxis, 0.0f, (float) Math.PI*2.0f );

//Set the scheduling bounds on the behavior. This defines the

//volume within which this behavior will be active. rotator.setSchedulingBounds( bounds );

//Add the behavior to the scenegraph so that Java 3D

//can schedule it for activation. objTrans.addChild(rotator);


* Create the BranchGroup which contains the objects we add/remove

* to and from the scenegraph. We store a reference to this subbranch

* of the scene side of the scenegraph in a member variable

* as we need to modify the contents of the branch at runtime.


sceneBranchGroup = new BranchGroup();

//Allow the BranchGroup to have children added and removed

//at runtime

sceneBranchGroup.setCapability( Group.ALLOW_CHILDREN_EXTEND ); sceneBranchGroup.setCapability( Group.ALLOW_CHILDREN_READ ); sceneBranchGroup.setCapability( Group.ALLOW_CHILDREN_WRITE );

//Add the subbranches for both the cube and the sphere to

//the BranchGroup sceneBranchGroup.addChild( createCube() );

sceneBranchGroup.addChild( createSphere() );

//Create the colors for the lights

Color3f lColor1 = new Color3f( 0.7f,0.7f,0.7f ); Vector3f lDir1 = new Vector3f( −1.0f,−1.0f,−1.0f ); Color3f alColor = new Color3f( 0.2f,0.2f,0.2f );

//Create an ambient light

AmbientLight aLgt = new AmbientLight( alColor ); aLgt.setInfluencingBounds( bounds );

//Create a directional light

DirectionalLight lgt1 = new DirectionalLight( lColor1, lDir1 ); lgt1.setInfluencingBounds( bounds );

//Add the lights to the scenegraph objRoot.addChild(aLgt); objRoot.addChild(lgt1);


* Wire the scenegraph together. It is useful to do this

* in the reverse order that the branches were created—

* rather like closing parentheses, that way you will not forget

* to add a child branch to its parent. If you forget to add a branch

* that you have created and populated then it will just not

* show up in the scene!


objTrans.addChild( sceneBranchGroup ); objRoot.addChild( objTrans );

//Return the root of the scene side of the scenegraph return objRoot;



* Create a BranchGroup that contains a Cube. The User Data

* for the BranchGroup is set so the BranchGroup can be

* identified later. User Data is a field that you can set

* on all Nodes in the scenegraph to allow you to associate

* your own data with particular scenegraph elements. The Cube

* must wrapped in a BranchGroup as only BranchGroups can be

* added and removed from the scenegraph at runtime—

* not Shape3Ds themselves which describe the geometry.


protected BranchGroup createCube()


//Create a parent BranchGroup for the Cube BranchGroup bg = new BranchGroup();

//Tell Java 3D that we need the ability to detach this BranchGroup

//from its parent Node.

bg.setCapability( BranchGroup.ALLOW_DETACH );

//Add a Shape3D (geometry) Node to the BranchGroup bg.addChild( new com.sun.j3d.utils.geometry.ColorCube() );

//Set the User Data on the BranchGroup so that we can easily

//identify this BranchGroup later, when we need to remove it. bg.setUserData( "Cube" );

return bg;


//Create a BranchGroup that contains a Sphere. The user data for

//the BranchGroup is set so the BranchGroup can be identified. protected BranchGroup createSphere()


BranchGroup bg = new BranchGroup();

//Tell Java 3D that we need the ability to detach this BranchGroup

//from its parent Node.

bg.setCapability( BranchGroup.ALLOW_DETACH );

//So that the Sphere is nicely shaded and responds to the lights

//in the scene, we create an Appearance with a Material

//for the Sphere.

Appearance app = new Appearance();

Color3f objColor = new Color3f(1.0f, 0.7f, 0.8f); Color3f black = new Color3f(0.0f, 0.0f, 0.0f);

app.setMaterial(new Material(objColor, black, objColor, black, 80.0f));

//Create the Sphere and assign the Appearance.

bg.addChild( new com.sun.j3d.utils.geometry.Sphere( 1, app ) );

//Set the User Data on the BranchGroup so that we can easily

//identify this BranchGroup later, when we need to remove it. bg.setUserData( "Sphere" );

return bg;


//Remove a BranchGroup from the scene based on the User Data.

//This allows us to dynamically remove the “Cube” or “Sphere”

//BranchGroups at runtime.

protected void removeShape( String name )




//First we get all the child Nodes from the parent of the Cube

//and/or Sphere BranchGroups

java.util.Enumeration enum = sceneBranchGroup.getAllChildren(); int index = 0;

//We then need to iterate through the Nodes to find the one with

//the User Data that we would like to remove while ( enum.hasMoreElements() != false )


SceneGraphObject sgObject = (SceneGraphObject) enum.nextElement();

//Get the User Data for the ScenegraphObject Object userData = sgObject.getUserData();

//Compare the current ScenegraphObject’s User Data with

//what we are looking for, if they match then we can remove

//the BranchGroup.

if ( userData instanceof String &

((String) userData).compareTo( name ) == 0 )


System.out.println( "Removing: " + sgObject.getUserData() ); sceneBranchGroup.removeChild( index );





catch( Exception e )



* The scenegraph may not have yet been synchronized. It is possible

* for an exception to be thrown here as the removing a BranchGroup

* is not instantaneous.




//Called to render the scene into the offscreen Canvas3D and

//save the image (as a JPEG) to disk. protected void onSaveImage()


offScreenCanvas3D.renderOffScreenBuffer(); offScreenCanvas3D.waitForOffScreenRendering(); System.out.println( "Rendered to offscreen" );



FileOutputStream fileOut = new FileOutputStream( "image.jpg" );

JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder( fileOut );

encoder.encode( imageComponent.getImage() );

fileOut.flush(); fileOut.close();


catch( Exception e )


System.err.println( "Failed to save image: " + e );


System.out.println( "Saved image." );



* AWT callback to indicate that an items has been selected

* from a menu. This is not the way to implement menu handling

* for a large application (!) but it serves for our simple example.


public void actionPerformed( ActionEvent ae )



println( "Action Performed: " + ae.getActionCommand() );

java.util.StringTokenizer toker =

new java.util.StringTokenizer( ae.getActionCommand(), "|" );

String menu = toker.nextToken(); String command = toker.nextToken();

if ( menu.equals( "File" ) )


if ( command.equals( "Exit" ) )


System.exit( 0 );


else if ( command.equals( "Save Image" ) )





else if ( menu.equals( "View" ) )


if ( command.equals( "Cube" ) )


removeShape( "Sphere" ); sceneBranchGroup.addChild( createCube() );


else if ( command.equals( "Sphere" ) )


removeShape( "Cube" );

sceneBranchGroup.addChild( createSphere() );



else if ( menu.equals( "Rotate" ) )


if ( command.equals( "On" ) )


rotator.setEnable( true );


else if ( command.equals( "Off" ) )


rotator.setEnable( false );




//Helper method to creates a Swing JmenuItem and set the action

//command to something we can distinguish while handling menu events. private JMenuItem createMenuItem( String menuText,

String buttonText, ActionListener listener )


JMenuItem menuItem = new JMenuItem( buttonText ); menuItem.addActionListener( listener ); menuItem.setActionCommand( menuText + "|" + buttonText ); return menuItem;



* Registers a window listener to handle ALT+F4 window closing.

* Otherwise the Swing application will just be made invisible when

* the parent frame is closed.


static protected void registerWindowListener( JFrame frame )


//Disable automatic close support for Swing frame. frame.

setDefaultCloseOperation( WindowConstants.DO_NOTHING_ON_CLOSE );

//Add the window listener frame.addWindowListener(

new WindowAdapter()


//Handles the system exit window message public void windowClosing( WindowEvent e )


System.exit( 1 );






* Main entry point for the application. Creates the parent JFrame,

* the JMenuBar and creates the JPanel which is the application

* itself.


public static void main( String[] args )



* Tell Swing that we need Popup Menus to be heavyweight. The Java 3D

* window is a heavyweight window – that is, the window is a native

* window, and therefore any windows that must overlap it must also be

* native. Our menu items will be dropped down in front of the

* Java 3D Canvas3D so they must be created as heavyweight windows.


JPopupMenu.setDefaultLightWeightPopupEnabled( false );


* Similarly we can declare that ToolTip windows are created

* as heavyweight. Our application does not use tooltips. However,

* if a toolbar was added the tooltips would overlap the Canvas3D

* and would also need to be heavyweight windows.


ToolTipManager ttm = ToolTipManager.sharedInstance(); ttm.setLightWeightPopupEnabled( false );

//Create the outermost frame for the application JFrame frame = new JFrame();

//Create the application JPanel, which contains the Canvas3D

//with the 3D view.

SwingTest swingTest = new SwingTest();


* Create a JMenuBar that will generate the events for the

* application. We register the swingTest instance as a listener

* for the action events generated by the menu items.


JMenuBar menuBar = new JMenuBar(); JMenu menu = null;

//Create some menu items and add them to the JMenuBar menu = new JMenu( "File" );


swingTest.createMenuItem( "File", "Save Image", swingTest ) ); menu.add(

swingTest.createMenuItem( "File", "Exit", swingTest ) ); menuBar.add( menu );

menu = new JMenu( "View" ); menu.add(

swingTest.createMenuItem( "View", "Cube", swingTest ) ); menu.add(

swingTest.createMenuItem( "View", "Sphere", swingTest ) ); menuBar.add( menu );

menu = new JMenu( "Rotate" ); menu.add(

swingTest.createMenuItem( "Rotate", "On", swingTest ) ); menu.add(

swingTest.createMenuItem( "Rotate", "Off", swingTest ) ); menuBar.add( menu );

//Assign the JMenuBar to the parent frame. frame.setJMenuBar( menuBar );

//Add the SwingTest JPanel to the parent frame. frame.getContentPane().add( swingTest );

//Set the initial size of the parent frame frame.setSize( 550, 550 );

//Register a window listener to intercept the closing

//of the parent frame. registerWindowListener( frame );

//Finally, make the parent frame visible! frame.setVisible( true );