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 11

Behaviors—navigation, alignment, and LOD

11.1 Introduction

11.2 Behavior class

11.3 Anatomy of a typical behavior

11.4 Overview of the built-in behaviors

11.5 Overview of Wakeup criteria

11.6 Using keyboard behaviors

11.7 Using mouse behaviors

11.8 Billboard behavior

11.9 Using LOD behaviors

11.10 Summary

Java 3D includes a rich set of built-in behaviors that can be used for everything from automatically modifying the transparency of objects to keyboard navigation and collision detection.

Some behaviors automatically execute complex code to modify objects within the scenegraph, so take care to ensure that behavior processing does not bog down application performance. With prudent design and knowledge of some of the limitations, behaviors can be a powerful asset in quickly developing or prototyping application logic.

By the time you finish this chapter, you should have a broad knowledge of the behaviors built into Java 3D and a good sense of how to develop your own behaviors. By mixing and matching your own behaviors and built-in behaviors, you should be able to design your application logic within Java 3D’s behavior model.

11.1 Introduction

We should start by taking a look at the general Java 3D behavior architecture. What is a behavior anyway?

Just as you can add geometry (Shape3D), transformations (TransformGroup), or lights (Light) to the scenegraph, Java 3D also allows you to add blocks of executable code to the scenegraph. This code is packaged within a class derived from Behavior and is automatically executed by Java 3D in response to a set of criteria. In Java 3D, the criteria for a Behavior being executed are called wake up criteria and are defined using classes derived from WakeUpCondition.

The behavior architecture is based on the following series of events and notifications:

1. Create a Behavior and register its WakeUpCondition.

2. Java 3D enters its Behavior processing loop.

3. Java 3D checks whether the WakeUpCondition for the current Behavior is satisfied.

4. Java 3D calls Behavior.processStimulus if the WakeUpCondition is met.

5. Behavior code executes within processStimulus call.

6. Behavior sets its next WakeUpCondition.

7. The next Behavior is processed by the Behavior processing loop.

The behavior architecture facilitates good application design and code reuse because a scenegraph branch can contain not only the complex geometry for a hierarchical scenegraph element but also the code to control the scenegraph element. For example, an application might define a car that drives around a race-circuit (figure 11.1). The application allows the user to replay a prerecorded racing sequence. Just as the car itself has been decomposed into a hierarchical model, behaviors can also be modeled hierarchically. A Behavior attached to each wheel of the car will rotate the wheel and its child nodes (spokes, etc.), while a Behavior attached to the top-level node of the car allows the entire car to be moved through a prerecorded trajectory using a Behavior to interpolate between positions.

img134.png

Figure 11.1 A scenegraph branch for the Formula 1 racing car. The branch contains five Behaviors, the first (RotPosPathInterpolator)  to replay the car through a recorded trajectory, and four RotationInterpolator Behaviors—one to rotate each wheel

Good, reusable, class design results. Every wheel has the ability to be rotated, while every car on the track can be replayed through a trajectory. The higher level elements in the scenegraph need have no knowledge of these properties of the car object—indeed they can be added and refined as the application is developed.

At least, that’s the goal. As with everything in computers, things are never that simple and some careful design must go into ensuring that your application’s Behaviors can be executed efficiently. Imagine your application with 50 cars, each with four wheels; that comes out to 200 Behaviors to be scheduled, their WakeUp criteria checked, and their processStimulus methods to be called—potentially on every frame. Not a very efficient way to rotate the wheels on the cars! Perhaps a better way would be to create a single Behavior that could rotate every car’s wheels on the circuit—not as architecturally clean a design but a lot more efficient and scaleable.

Section 11.2 on the Behavior class will describe the capabilities of all general Behaviors and show you with the basics of writing your own Behaviors.

11.2 Behavior class

java.lang.Object

 |

 +--javax.media.j3d.SceneGraphObject

  |

  +--javax.media.j3d.Node

   |

   +--javax.media.j3d.Leaf

    |

    +--javax.media.j3d.Behavior

The Behavior class defines the following basic components of the behavior architecture:

Scheduling bounds

The behavior processor executes a Behavior only if the scheduling Bounds for the Behavior intersect the ViewPlatform’s activation region. That is, you define the 3D volume within which the Behavior is to be activated. For the racing car example, you might define that a car’s wheels are to be rotated only if the car is within 20 meters of the viewer.

The scheduling Bounds for a Behavior are specified using the setSchedulingBounds or setSchedulingBoundingLeaf methods. If a BoundingLeaf is specified, it takes precedence over Bounds set using setSchedulingBounds.

The Bounds for the ViewPlatform are set using the ViewPlatform.setActivationRadius method. When the Bounds for a Behavior and the ViewPlatform’s activation radius intersect, the Behavior is scheduled for processing.

Enable State

Behaviors can be manually switched on or off by modifying their enable states using the setEnable method.

WakeUp Condition

The Java 3D behavior processor calls the processStimulus method when the WakeUp condition for the behavior has been met. The WakeUp condition for a Behavior is set using the wakeupOn method. There are two opportunities to register or update a Behavior’s WakeUp condition: at the end of the initialize method and at the end of the processStimulus method. The different types of WakeUpCondition classes are described in the coming sections.

processStimulus method

A Behavior does all of its application- or behavior-specific work within the processStimulus method. Because a Behavior could have been activated by a variety of WakeUp criteria, an Enumeration is passed to the processStimulus method to allow the Behavior to evaluate exactly why it was activated and take appropriate action.

After executing its behavior-specific code, the Behavior must call the wakeupOn method to register continued interest in its WakeUp condition. If the Behavior does not call wakeupOn, it will not be activated again.

initialize method

The Java 3D behavior processor calls the initialize method to allow each Behavior in the scenegraph to set its initial WakeUpCondition.

When do Behaviors run?

A Behavior’s processStimulus methods will be called if all the following conditions are met:

  • The Behavior has been added to the scenegraph.
  • The Behavior’s scheduling Bounds intersect the ViewPlatform’s activation region.
  • The Behavior is enabled.
  • The Behavior’s WakeUpCondition is true. The WakeUpCondition is set in the initialize method and must be reset after each call to processStimulus.
  • View.isBehaviorSchedulerRunning() returns true.

11.3 Anatomy of a typical behavior

A typical Behavior implements the following three methods: constructor, initialize, and processStimulus.

Constructor

The Behavior constructor typically receives references to the scenegraph elements that will be affected by the Behavior. For example, a Behavior to move an object would receive a reference to the object’s parent TransformGroup. These external references will be stored in member variables so that they can be accessed within the processStimulus method when the Behavior is activated.

initialize method

The initialize method must be overridden to register the initial WakeUp conditions for the Behavior. The WakeupCondition object for the Behavior will typically be stored in a member variable so that the WakeupCondition can be reapplied at the end of the processStimulus method.

The last line of the initialize method will call the wakeupOn method to register the WakeupCondition object for the Behavior.

processStimulus method

A simple Behavior can ignore the Enumeration passed to the processStimulus method—the Behavior knows why it was activated and can proceed to the application-specific code. A more complex or composite Behavior will have to query the Enumeration and first determine what mode it was activated in before it can execute the appropriate Behavior code.

Within the Behavior’s processStimulus code, the Behavior will call methods on the member variables it stored in the constructor. For example, the Behavior might call Transform3D methods and then call TransformGroup.setTransform to update the position or orientation of an object.

At the end of the processStimulus method, the Behavior will almost certainly call the wakeupOn method, with a previously stored WakeupCondition, to ensure that it receives continued notifications and is rescheduled for processing.

11.4 Overview of the built-in behaviors

There are 26 classes derived from Behavior in Java 3D. To describe each in detail is beyond the scope of this chapter. Please refer to the Sun API documentation for a description of each Behavior’s methods. Interpolator, mouse, and keyboard Behaviors are described in more detail in subsequent sections.

See table 11.1 for help in selecting an appropriate built-in Behavior for your application.

Table 11.1 Built-in behaviors

img135.png

img136.png

img137.png

11.5 Overview of Wakeup criteria

Java 3D includes 14 Wakeup criteria (or triggers) for Behaviors. These simple criteria are specified and derived from the WakeupCriterion class. In addition, Boolean combinations of simple criteria can be specified using the classes derived from WakeupCondition.

For example, you can either define a Behavior that is activated every time an object is moved (i.e., WakeupOnTransformChange, which is a WakeupCriterion):

wakeupOn( new WakeupOnTransformChange( tg ) );

Or define a Behavior that is activated every time an object is moved and a specified number of frames have elapsed (i.e., WakeupAnd, which is a WakeupCondition):

WakeupCriterion criterionArray[] = new WakeupCriterion[2];

criterionArray[0] = new WakeupOnTransformChange( tg );

criterionArray[1] = new WakeupOnElapsedFrames( 20 );

WakeupCondition wakeUpCondition = new WakeupAnd( criterionArray );

wakeupOn( wakeUpCondition );

WakeupCriterion

java.lang.Object

 |

 +--javax.media.j3d.WakeupCondition

  |

  +--javax.media.j3d.WakeupCriterion

The WakeupCriterion derived classes (table 11.2) define the atomic events that can be used on their own or combined using the WakeupCondition (table 11.3).

Table 11.2 WakeupCriterion for triggering behaviors

img138.png

img139.png

img140.png

WakeupCondition

java.lang.Object

 |

 +--javax.media.j3d.WakeupCondition

The WakeupCondition-derived classes are used to specify Boolean combinations of WakeupCriterion classes (table 11.3). In this way composite Wakeup conditions can be specified.

Table 11.3 WakeupConditions for combining WakeupCriterion instances

img141.png

A composite Wakeup condition can be created by the following code:

 From StretchBehavior.java

protected WakeupCondition  m_WakeupCondition = null;

//create the WakeupCriterion for the behavior

WakeupCriterion criterionArray[] = new WakeupCriterion[2]; criterionArray[0] = new WakeupOnAWTEvent( KeyEvent.KEY_PRESSED );

criterionArray[1] = new WakeupOnElapsedFrames( 1 );

//save the WakeupCriterion for the behavior

m_WakeupCondition = new WakeupOr( criterionArray );

_________________________________________________________________

Summary

The behavior model provides a powerful mechanism to enable application code to be called in a demand-driven manner. Events that occur within the scenegraph can trigger code to be executed, analogous to event processing for GUI elements.

Behaviors can be very useful, but they hinge upon Java 3D’s behavior processor to call them when their WakeUp criteria has been met. Your Behaviors can only be as functionally rich as combinations of WakeUp criteria will allow—there is no point writing a Behavior that should be invoked when the user scratches his head, because Java 3D will never invoke it.

Java 3D provides a relatively complete set of WakeupCondition-derived classes; however, not all eventualities have been covered, and there may be occasions where there is not a suitable WakeupCondition-derived class. For example, there is currently no equivalent to VRML’s VisibilitySensor Node. Chapter 13 builds upon these concepts and illustrates how to write your own custom Behaviors.

The next sections will describe some of the most useful built-in behaviors: keyboard and mouse navigation, aligning objects relative to the viewer, and choosing different object models based on the distance from the viewer.

11.6 Using keyboard behaviors

The Java 3D keyboard behavior responds to AWT key-press events (KeyEvent.KEY_ PRESSED and KeyEvent.KEY_RELEASED) and modifies the 4x4 transformation matrix within a TransformGroup. The changes to the TransformGroup can affect the size, position, and rotation of the TranformGroup’s child Nodes.

Keyboard navigation is typically used to simulate moving the viewer of a 3D scene, as opposed to manipulating individual objects within a scene. Mouse behaviors, described in section 11.7, are usually used for object manipulation, although there is considerable overlap between the two areas.

In terms of visual effect, there is no difference between moving the viewer (by attaching the keyboard behavior to a TransformGroup on the view side of the scenegraph) and moving the scene itself (by attaching the keyboard behavior to a root TransformGroup on the scene side of the scenegraph). Figure 11.2 shows a diagram of the typical scenegraph structure that illustrates these two approaches.

img142.png

Figure 11.2 A Java 3D scenegraph. A keyboard behavior can be added either to the scene side of the scenegraph (left) or to the view side of the scenegraph (right)

There are advantages and disadvantages to both approaches. If the keyboard behavior is added on the scene side of the scenegraph, you really are moving the scene. For a single view and with a single ViewPlatform, this will not be noticeable; however, once there are multiple views of the scene or predefined views have been set up using numerous ViewPlatforms, the effects of moving the scene will be noticeable.

The advantage of attaching the behavior to the scene side of the scenegraph is that most keyboard behaviors expect this to be the case (including the built-in behavior). Remember that the inverse of the multiplication of the Transform3Ds above the ViewPlatform is used to set the view projection matrix. If the keyboard behavior is added on the view side of the scenegraph, the motion and rotation keys are reversed.

If you need to support multiple views, add the keyboard behavior to the view side of the scenegraph and modify the behavior to account for the reversal in movement direction. Otherwise, it will probably be easier to add the behavior to a root TransformGroup on the scene side of the scenegraph.

KeyNavigatorBehavior

java.lang.Object

 |

 +--javax.media.j3d.SceneGraphObject

  |

  +--javax.media.j3d.Node

   |

   +--javax.media.j3d.Leaf

    |

    +--javax.media.j3d.Behavior

     |

     +--com.sun.j3d.utils.behaviors.keyboard.

         KeyNavigatorBehavior

The KeyNavigatorBehavior is the built-in Java 3D keyboard navigation behavior—part of the utilities package. The KeyNavigatorBehavior is very easy to use and it typically consists of: creating a KeyNavigatorBehavior object and passing it the TransformGroup that the Behavior should modify, setting the scheduling Bounds for the Behavior, ensuring the ALLOW_TRANSFORM_WRITE capability bit is set on the TransformGroup, and adding the KeyNavigatorBehavior to the scenegraph.

 From AppearanceTest.java

//create the TransformGroup that the Key behavior will affect

TransformGroup zoomTg = new TransformGroup();

zoomTg.setCapability( TransformGroup.ALLOW_TRANSFORM_WRITE );

zoomTg.setCapability( TransformGroup.ALLOW_TRANSFORM_READ );

//create the Key behavior and add to the scenegraph

KeyNavigatorBehavior key = new KeyNavigatorBehavior( zoomTg );

key.setSchedulingBounds( createApplicationBounds() );

key.setEnable( true );

objRoot.addChild( key );

_________________________________________________________________

The keyboard controls for the KeyNavigatorBehavior are implemented in the KeyNavigator class, which performs the matrix modifications based on key presses. The KeyNavigator class has a fairly sophisticated implementation that includes a large number of movement key combinations and acceleration of movement. The key events that the KeyNavigator class handles are shown in table 11.4.

Table 11.4 KeyNavigatorBehavior keyboard controls

img143.png

img144.png

Writing a simple keyboard behavior

Simple keyboard processing is easy to accomplish; for example, the following class simply modifies a TransformGroup in response to presses of the left and right arrow keys.

 From the AvatarTest example, CarSteering.java

public class CarSteering extends Behavior

{

 private WakeupOnAWTEvent   wakeupOne = null;

 private WakeupCriterion[]  wakeupArray = new

WakeupCriterion[1];

 private WakeupCondition    wakeupCondition = null;

 private final float        TRANSLATE_LEFT = -0.05f;

 private final float        TRANSLATE_RIGHT = 0.05f;

 TransformGroup             m_TransformGroup = null;

 public CarSteering( TransformGroup tg )

 {

  m_TransformGroup = tg;

  wakeupOne = new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED);

  wakeupArray[0] = wakeupOne;

  wakeupCondition = new WakeupOr(wakeupArray);

 }

 //Override Behavior's initialize method to set up wakeup

 criteria public void initialize()

 {

  //Establish initial wakeup criteria

  wakeupOn(wakeupCondition);

 }

 //Override Behavior's stimulus method to handle the event.

 public void processStimulus(Enumeration criteria)

 {

  WakeupOnAWTEvent ev;

  WakeupCriterion genericEvt;

  AWTEvent[] events;

  while (criteria.hasMoreElements())

  {

   genericEvt = (WakeupCriterion) criteria.nextElement();

   if (genericEvt instanceof WakeupOnAWTEvent)

   {

    ev = (WakeupOnAWTEvent) genericEvt;

    events = ev.getAWTEvent();

    processAWTEvent(events);

   }

  }

  //Set wakeup criteria for next time

  wakeupOn(wakeupCondition);

 }

 //Process a keyboard event

 private void processAWTEvent(AWTEvent[] events)

 {

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

  {

   if( events[n] instanceof KeyEvent)

   {

    KeyEvent eventKey = (KeyEvent) events[n];

    if( eventKey.getID() == KeyEvent.KEY_PRESSED )

    {

     int keyCode = eventKey.getKeyCode();

     int keyChar = eventKey.getKeyChar();

     Vector3f translate = new Vector3f();

     Transfo