Java 3D Programming by Daniel Selman - HTML preview

PLEASE NOTE: This is an HTML preview only and some elements such as links or page numbers may be incorrect.
Download the book in PDF, ePub, Kindle for a complete version.

CHAPTER 6

Defining the universe

6.1 Locales and HiResCoord

6.2 View, ViewPlatform, and Locale

6.3 SimpleUniverse

6.4 Background geometry

6.5 Using multiple views

6.6 Summary

One of the fundamental choices you will have to make in your choice of VirtualUniverse configuration is whether to use the SimpleUniverse utility classes or to rely upon the lower level VirtualUniverse classes. By the end of this chapter you should understand the elements of the Java 3D scenegraph rendering model.

Essential elements of the Java 3D scenegraph are covered:

  • Defining regions within your universe using Locales
  • Attaching the View model to the VirtualUniverse through the ViewPlatform
  • Using multiple Views for rendering
  • SimpleUniverse utility classes
  • Creating Views, Geometry, and PlatformGeometry for Avatars

6.1 Locales and HiResCoord

The VirtualUniverse class contains the virtual world that an application developer populates with Geometry, Behaviors, Lights, and so forth. The VirtualUniverse consists of a collection of Locales. A Locale defines a geographical area within the VirtualUniverse and is anchored at a given 3D coordinate. A Locale uses 256-bit coordinates to specify the anchored x, y, and z position. The 256-bit coordinates are stored in a HiResCoord object and allow a Locale to be positioned within a virtual space the size of the known (physical) universe yet also maintain a precision of a Planck length (smaller than the size of a proton).

Most applications do not require more than one Locale; hence the SimpleUniverse class creates a VirtualUniverse instance with a single Locale. SimpleUniverse is derived from VirtualUniverse and is covered in detail in chapter 3. The default constructor for a Locale positions the Locale at the origin of the VirtualUniverse. Within a Locale, doubles or floats are used to specify the positions of objects. If a double is large enough to represent the positions of all the objects within your scene accurately, then a single Locale should be sufficient.

However, imagine a scenario: your application is to model parts of the galaxy. The model is to contain the planets orbiting the Sun. On the Earth, the model contains a geometry object a few meters across to represent a house. All the objects in the model are to be created to scale.

How would one go about building such a model for the viewer to be able to switch between three different viewing modes?

  • Galaxy: See the Sun with the planets rotating about it.
  • Earth: See the planet Earth as it spins about its axis.
  • House: View the house on earth.

Locales were designed to handle applications such as that just described. The HiResCoordTest example implements the application shown in figure 6.1. Creating multiple Locales is a fairly lengthy process, so the whole example cannot be included here.

img44.png

Figure 6.1 Views of the three Locales defined in HiResCoordTest.java

The first problem encountered is how to specify the location of a Locale.

 From HiResCoordTest.java

protected Locale createLocaleEarth( VirtualUniverse u )

{

int[] xPos = { 0, 0, 0, 0, 0, 0, 0, 0 };

int[] yPos = { 0, 0, 0, 0, 0, 0, 0, 0 };

int[] zPos = { 0, 0, 0, 1, 0, 0, 0, 0 };

HiResCoord hiResCoord = new HiResCoord( xPos, yPos, zPos );

return new Locale( u, hiResCoord );

}

A HiResCoord is created using three 8-element integer arrays. An int is 32 bits, so 8 * 32 = 256 bits.

NOTE

The integer at index 0 contains the most significant bit, while the integer at index 7 contains the least significant bit. The decimal point is defined to lie between indexes 3 and 4, that is,

0x0 0x0 0x0 0x0 . 0x0 0x0 0x0 0x0

The 8-element integer array to specify a coordinate can be considered an 8-digit number in base 232. The numbers that can be expressed by such an entity are mind-boggling, but table 6.1 (from the API specification) can be used to get you into the right ballpark for the quantity you are trying to express.

Table 6.1 Physical dimensions expressed as a power of 2

img45.png

For example, to specify a distance in light-years (9,460 billion kilometers), find from table 6.1 that 253.07 is equal to 9,460 billion kilometers. That is, a 1 at the 53rd bit position will be approximately one light-year. When mapped into the integer array, the 53rd bit is located within the integer at index 3 – 53/32 = 2. It is the 21st bit (53 – 32) within the third integer of the array. The number 221 equals 0x200000, so setting the integer at index 2 of the integer array to 0x200000 will create a HiResCoord instance that stores approximately a light-year.

The integer at index 3 is defined to store meters, so simply setting the integer at index 3 will define a HiResCoord in meters. Conversely, the 20th bit to the right of the decimal point position is approximately a micron.

A useful exercise would be to develop a Java class that returned a HiResCoord for quantities expressed in the various units shown in table 6.1.

//creates a Locale that is positioned (232 + 5) meters

//away from the origin in the +Z direction.

int[] xPos = { 0, 0, 0, 0, 0, 0, 0, 0 };

int[] yPos = { 0, 0, 0, 0, 0, 0, 0, 0 };

int[] zPos = { 0, 0, 1, 5, 0, 0, 0, 0 };

HiResCoord hiResCoord = new HiResCoord( xPos, yPos, zPos );

Once a Locale has been created and positioned, it can be populated with geometry by attaching a BranchGroup containing scenegraph elements:

//create the Universe

m_Universe = new VirtualUniverse();

//create the position for the Locale

int[] xPos = { 0, 0, 0, 0, 0, 0, 0, 0 };

int[] yPos = { 0, 0, 0, 0, 0, 0, 0, 0 };

int[] zPos = { 0, 0, 1, 5, 0, 0, 0, 0 };

HiResCoord hiResCoord = new HiResCoord( xPos, yPos, zPos );

//create the Locale and attach to the VirtualUniverse

Locale locale = new Locale( u, hiResCoord );

//create the BranchGroup containing the Geometry for the scene

BranchGroup sceneBranchGroup = createSceneBranchGroup();

sceneBranchGroup.compile();

//add the scene BranchGroup to the Locale locale.addBranchGraph( sceneBranchGroup );

Note that this code will not yet make the geometry visible until a View and a ViewPlatform are created. This is the subject of the next section.

6.2 View, ViewPlatform, and Locale

Now that a Locale has been created and populated with geometry, Java 3D must be instructed to render the Locale. The key to Java 3D rendering is the View class which attaches to the ViewPlatform instance that is within the scenegraph. The View controls Java 3D rendering and must have an attached Canvas3D component to render into. Multiple Canvas3D instances can be attached to the View, allowing multiple (identical) copies of the View to be rendered simultaneously.

CLIPPING PLANES

The clipping planes for the View control how much of the scene is rendered. In the example, the front plane is 10 meters from the viewer, while the back clipping plane is 110 meters from the viewer. However, since the viewer has been moved back 20 meters from the origin, the scene will be rendered from –10 to +90 in the Z direction. Note that one cannot set arbitrary clipping planes. The clipping planes have a physical significance in that they define a view frustum. The view frustum defines the pyramidal volume of 3D space that is rendered. The ViewPlatform is a simple Java 3D Leaf Node and can be added to the Locale as shown by the Java3dApplet code:

 Based on Java3dApplet.java

//create the ViewPlatform BranchGroup

BranchGroup vpBranchGroup = new BranchGroup();

//create a TransformGroup to scale the ViewPlatform

//(and hence View)

TransformGroup tg = new TransformGroup();

//create the ViewPlatform

ViewPlatform vp = new ViewPlatform();

vp.setViewAttachPolicy( View.RELATIVE_TO_FIELD_OF_VIEW );

//attach the ViewPlatform to the TransformGroup tg.addChild( vp );

//attach the TransformGroup to the BranchGroup vpBranchGroup.addChild( tg );

//finally, add the ViewPlatform BranchGroup to the Locale locale.addBranchGraph( vpBranchGroup );

img46.png

Note that the TransformGroup created just before the ViewPlatform can be used to scale, translate, or rotate the scene rendered by the View attached to the ViewPlatform. For example:

//Move the camera BACK a little. Note that Transformation

//matrices above the ViewPlatform are inverted by the View

//renderer prior to rendering. By moving the camera back 20

//meters, you can see geometry objects that are positioned at 0,0,0.

Transform3D t3d = new Transform3D();

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

tg.setTransform( t3d );

Now we need to create the View object itself and attach it to the ViewPlatform that was added to the scenegraph.

//create the View object

View view = new View();

//create the PhysicalBody and PhysicalEnvironment for the View

//and attach to the View

PhysicalBody pb = new PhysicalBody();

PhysicalEnvironment pe = new PhysicalEnvironment(); view.setPhysicalEnvironment( pe );

view.setPhysicalBody( pb );

//attach the View to the ViewPlatform

view.attachViewPlatform( vp );

//set the near and far clipping planes for the View view.setBackClipDistance( 110 );

view.setFrontClipDistance( 10 );

Finally, create a Canvas3D component (an AWT object) and add it to the View’s list of Canvases to be rendered into.

//create the Canvas3D that the View will render into.

//get the graphics capabilities of the system and create

//the best Canvas3D possible.

GraphicsConfigTemplate3D gc3D = new GraphicsConfigTemplate3D(); gc3D.setSceneAntialiasing( GraphicsConfigTemplate.PREFERRED ); GraphicsDevice gd[] = GraphicsEnvironment.

getLocalGraphicsEnvironment().getScreenDevices();

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

//set the size of the Canvas3D

c3d.setSize( 512, 512 );

//add the Canvas3D to the View so that it is rendered into view.addCanvas3D( c3d );

//add the Canvas3D component to a parent AWT or Swing Panel

add( c3d );

6.3 SimpleUniverse

To simplify creating the View side of the scenegraph, Sun has provided the SimpleUniverse class (figure 6.2). SimpleUniverse is defined in the com.sun.j3d.utils package and as such should not be considered part of the core Java 3D API. The SimpleUniverse class hides some of the complexity of manually defining the View side of the scenegraph at the expense of the flexibility of using the core API classes only.

img47.png

Figure 6.2 The SimpleUniverse class encapsulates the details of the view side of the scenegraph (lower branch). The ViewingPlatform has been highlighted and contains a MultiTransformGroup composed of two TransformGroups. Attached to the ViewPlatform are a PlatformGeometry Group and a ViewerAvatar Group. By attaching a Key behavior to one of the TransformGroups in the MultiTransformGroup, the View, PlatformGeometry, and ViewerAvatar can all be moved simultaneously and rendered into the Canvas3D attached to the view

SimpleUniverse is a bit of a misnomer since the class is anything but simple, and this can cause initial confusion because of the plethora of support classes that it relies upon. SimpleUniverse introduces five new (non-core-API) classes:

  • Viewer, a container class that keeps references to the following:

ViewerAvatar, a representation of the viewer of the scene.

Canvas3D, used for rendering the scene.

AWT Frame contains the Canvas3D used for rendering.

PhysicalBody references the view’s PhysicalBody.

PhysicalEnvironment references the view’s PhysicalEnvironment.

View is used for the viewer of the scene.

  • ViewingPlatform (extends BranchGroup) helps set up the View side of the scenegraph by creating a hierarchy of TransformGroups above the ViewPlatform where the view is attached to the scenegraph. The hierarchy of TransformGroups is encapsulated in a MultiTransformGroup. In this way a series of transformations can be applied to the view irrespective of transformations that are applied on the geometry side of the scenegraph. ViewerAvatar (extends BranchGroup) contains the geometry used to render the viewer’s virtual self in the virtual environment.
  • MultiTransformGroup is a simple encapsulation of a Vector of TransformGroups.
  • PlatformGeometry (extends BranchGroup) contains the geometry associated with the viewer’s ViewingPlatform. For example, in a multiplayer gaming scenario, each player might be able to drive one of several vehicles. A ViewingPlatform would represent each vehicle in the scenegraph, and the geometry for the vehicle would be attached to the ViewingPlatform.The player would be represented by geometry attached to the ViewerAvatar, which in turn would govern another player’s view of the player, while the ViewingPlatform would contain the geometry to describe the internal characteristics of the vehicle the player was riding in. By attaching the view to different ViewingPlatforms, the player can move between vehicles.

In the PlatformTest example, two avatars (Dan and Jim) and three views are created: overhead, Dan’s, and Jim’s (figures 6.3–6.5). Each Avatar is assigned ViewerAvatar geometry (a simple Cone pointing in the direction of view) and PlatformGeometry (a text label to identify the avatar).

img48.png

Figure 6.3 Overhead view of the virtual arena with two avatars: Dan and Jim

img49.png

Figure 6.4 The view for avatar Jim—Jim can see Dan

img50.png

Figure 6.5 The view for avatar Dan—Dan can see Jim

The example is too lengthy to be included in its entirety but some illustrative excerpts have been extracted:

 From PlatformTest.java

//This method creates the SimpleUniverse View and ViewPlatform

//scenegraph elements to create an avatar that has an associated

//Canvas3D for rendering, a PlatformGeometry, ViewerAvatar, and a

//KeyNavigator to allow movement of the ViewerAvatar with the

//keyboard.

ViewingPlatform createViewer( Canvas3D c, String szName, Color3f objColor, double x, double z )

{

 //create a Viewer and attach to its canvas. A Canvas3D can

 //only be attached to a single Viewer.

 Viewer viewer2 = new Viewer( c );

 //create a ViewingPlatform with 1 TransformGroup above the

 //ViewPlatform

 ViewingPlatform vp2 = new ViewingPlatform( 1 );

 //create and assign the PlatformGeometry to the Viewer

 vp2.setPlatformGeometry( createPlatformGeometry( szName ) );

 //create and assign the ViewerAvatar to the Viewer

 viewer2.setAvatar( createViewerAvatar( szName, objColor ) );

 //set the initial position for the Viewer

 Transform3D t3d = new Transform3D();

 t3d.setTranslation( new Vector3d( x, 0, z ) );

 vp2.getViewPlatformTransform().setTransform( t3d );

 //set capabilities on the TransformGroup so that the

 //KeyNavigatorBehavior can modify the Viewer's position

 vp2.getViewPlatformTransform().setCapability(

 TransformGroup.ALLOW_TRANSFORM_WRITE );

 vp2.getViewPlatformTransform().setCapability(

 TransformGroup.ALLOW_TRANSFORM_READ );

 //attach a navigation behavior to the position of the viewer

 KeyNavigatorBehavior key = new KeyNavigatorBehavior(

 vp2.getViewPlatformTransform() );

 key.setSchedulingBounds( m_Bounds );

 key.setEnable( false );

 //add the KeyNavigatorBehavior to the ViewingPlatform

 vp2.addChild( key );

 //set the ViewingPlatform for the Viewer

 viewer2.setViewingPlatform( vp2 );

 return vp2;

}

//creates and positions a simple Cone to represent the Viewer.

//The Cone is aligned and scaled such that it is similar to a 3D

//"turtle".

ViewerAvatar createViewerAvatar( String szText, Color3f objColor )

{

 ViewerAvatar viewerAvatar = new ViewerAvatar();

 //rotate the Cone so that it is lying down and the sharp end

 //is pointed toward the Viewer's field of view.

 TransformGroup tg = new TransformGroup();

 Transform3D t3d = new Transform3D();

 t3d.setEuler( new Vector3d( Math.PI / 2.0, Math.PI, 0 ) );

 tg.setTransform( t3d );

 //create appearance and material for the Cone

 Appearance app = new Appearance();

 Color3f black = new Color3f(0.4f, 0.2f, 0.1f);

 app.setMaterial(new Material(objColor, black, objColor,

 black, 90.0f));

 //create the Primitive and add to the parent BranchGroup

 tg.addChild( new Cone( 1, 3, Primitive.GENERATE_NORMALS, app ));

 viewerAvatar.addChild( tg );

 return viewerAvatar;

}

//create a simple Raster text label used to help identify

//the viewer.

PlatformGeometry createPlatformGeometry( String szText )

{

 PlatformGeometry pg = new PlatformGeometry();

 pg.addChild( createLabel( szText, 0f, 2f, 0f ) );

 return pg;

}

//creates a simple Raster text label (similar to Text2D)

private Shape3D createLabel( String szText, float x, float y, float z )

{

 BufferedImage bufferedImage = new BufferedImage( 25, 14, BufferedImage.TYPE_INT_RGB );

 Graphics g = bufferedImage.getGraphics();

 g.setColor( Color.white );

 g.drawString( szText, 2, 12 );

 ImageComponent2D imageComponent2D = new ImageComponent2D( ImageComponent2D.FORMAT_RGB, bufferedImage );

 //create the Raster for the image

 javax.media.j3d.Raster renderRaster = new

javax.media.j3d.Raster( new Point3f( x, y, z ),

javax.media.j3d.Raster.RASTER_COLOR,

0, 0,

bufferedImage.getWidth(),

bufferedImage.getHeight(),

imageComponent2D,

null );

 return new Shape3D( renderRaster );

}

6.3.1 Avatars and platform geometry

The SimpleUniverse class allows you to attach geometry to the viewer of your 3D scene. There are two methods of attaching viewer geometry using the SimpleUniverse class: ViewingPlatform.setPlatformGeometry or Viewer.setAvatar. The ViewingPlatform can be accessed from the SimpleUniverse by calling getViewingPlatform or calling getViewer to access the Viewer object. There does not seem to be any difference between these two methods.

There is nothing magical about the geometry that represents the viewer; it may be easier, and more consistent in your application, to represent the viewer using a separate BranchGroup attached to the root of your scenegraph. SimpleUniverse is defined in the Java 3D utilities package (com.sun.java.j3d.utils) along with the Viewer, ViewerAvatar, and ViewingPlatform classes. These classes are merely defined for convenience; it may be safer to use the core Java 3D scenegraph management classes.

That said, here is a short example of using setPlatformGeometry to assign geometry for the Viewer; the output is shown in figures 6.6–6.7.

img51.png

Figure 6.6 A frame rendered by AvatarTest. The viewer’s avatar is the large cube to the right of the frame

img52.png

Figure 6.7 A frame rendered by AvatarTest. The viewer’s avatar is the large cube in the center of the frame

 From AvatarTest.java (see also PlatformTest.java)

//Create a simple scene and attach it to the virtual universe

SimpleUniverse u = new SimpleUniverse();

//Add everything to the scene graph—it will now be displayed.

BranchGroup scene = sg.createSceneGraph();

u.addBranchGraph(scene);

PlatformGeometry pg = sg.createPlatformGeometry();

//set the just created PlatformGeometry.

ViewingPlatform vp = u.getViewingPlatform();

vp.setPlatformGeometry(pg);

//or

Viewer viewer = u.getViewer();

ViewerAvatar viewerAvatar = new ViewerAvatar();

viewerAvatar.addChild( sg.createViewerAvatar() );

viewer.setAvatar( viewerAvatar );

6.4 Background geometry