The Behavior Class

The Behavior class is an abstract class in which code for manipulating the scene graph is typically placed. We say typically because you will see that a Behavior can perform important functions not related to the manipulation of the scene graph. Nevertheless, Behavior objects are the primary way in which user input can communicate with the virtual universe. Recall that an abstract class is never instantiated directly but rather is subclassed (extended) to accomplish some particular goal.

Similar to an event listener, the general idea of a Behavior object is to specify what to do and when to do it. As you will soon see, these two functions are handled in the processStimulus() and initialization() methods, respectively. A big part of the initialization() method is to specify one or more wakeup conditions. The wakeup conditions designate the specific condition that activates the processStimulus() method of the Behavior.

Basically all the work in writing a Behavior occurs in overriding these two methods: processStimulus() and initialization(). A constructor is also needed, but writing that is, of course, typically simple.

An important conceptual point should be understood at this time. A Behavior does not run synchronously with the Java 3D renderer. In other words, there is no guarantee that a Behavior's action will impact the current frame. One exception to this rule is wakeupOnElapsedFrames().

It should also be noted that many useful Behaviors already exist and are provided in the utilities package. It is obviously always a good idea to first see if an existing class exists or can be extended or modified. For example, the MouseMover and MouseRotation Behaviors are frequently and conveniently used. Nevertheless, it is important to understand how Behaviors work and in what situations they are used.

The initialize() Method

The first method that we must override is the initialize() method. The purpose of this method is to specify what events will awaken the quiescent Behavior and cause it to act. It is probably worthwhile to diverge for a moment and discuss terminology and class hierarchy of wakeup conditions and criteria.

The WakeupCondition class is the highest level of abstraction used to represent a condition that activates the processStimulus() method. It has several subclasses, the most elementary of which is the WakeupCriterion class. The WakeupCriterion class represents all unitary events that cause WakeupCondition to call processStimulus() and is itself extended into numerous subclasses. Combinations of WakeupCirteria can be specified with Boolean operations (see the section “Boolean WakeupCriteria").

There are several general categories of wakeup criteria, and these are listed in Table 12.1.

Table 12.1. General Classes of Wakeup Criterion
WakeupCriterion Usage
ViewPlatform entry/exit Collision avoidance; turning on lights upon entering an area
Behavior Post Wake up or go to sleep when another Behavior posts a specific event
TransformGroup changes Link to any TransformGroup and monitor changes to it
AWT Event Making changes based on key strokes
Geometry collision/decollision Wake up upon collision of a Shape3D node's Geometry with any other object
Elapsed time or frames User gets specified time to make decision
Sensor activation Entry or exit of a Sensor

The processStimulus() Method

The majority of the programming work necessary to develop a custom Behavior occurs in the processStimulus() method. The purpose of the processStimulus() method is to handle all the internal messages (stimuli) of the Behavior. These stimuli result from the activation of one of more wakeup criterions (as described previously). To repeat, the processStimulus() method implements the “what to do” function.

The first part of this process is to determine which stimulus prompted the particular incoming call to the processStimulus() method. This sorting out of stimuli is usually accomplished in a series of if else or case switch statements and is directly analogous to what happens in EventListeners in Java.

The next step is typically to make a change to the scene based on the stimulus. We emphasize the word typically to note that the processStimulus() method can execute any kind of Java code, not just methods related to the scene graph. You could just as easily invoke an RMI method, access a JNI executable, or launch another application. Generally, however, the developer will be making changes to the scene graph.

The scene element(s) to be acted on are often termed the object(s) of change, which simply means that the enumerated objects are candidates for the Behavior to act on. The objects of change must have the proper capability bits set for the manipulation to take place.

Writing a Behavior is about as easy as writing a Listener using AWT except that the methods have different names. Listings 12.1 and 12.2 demonstrate the simplest possible Behavior that can be written. In this case, the Behavior is set to wake up when any key is pressed on the keyboard. Instead of specifying an object of change, the Behavior just prints that the key has been pressed.

Listing 12.1 SimpleBehaviorApp.java
import java.awt.event.*;
import javax.media.j3d.*;
import java.util.Enumeration;

  public class SimpleBehavior extends Behavior {

        WakeupCriterion criterion;

        public SimpleBehavior() {
            super();
        }

        public void initialize() {
           criterion = new WakeupOnAWTEvent( KeyEvent.KEY_PRESSED );

           wakeupOn(criterion);
        }

        public void processStimulus( Enumeration criteria) {
                System.out.println("processStimulus of SimpleBehavior");

            wakeupOn(criterion);
        }
    }


We now show how an application sets up the simple Behavior.

Listing 12.2 SimpleBehavior.java
import java.applet.Applet;
import java.awt.BorderLayout;
import com.sun.j3d.utils.applet.MainFrame;

import java.awt.event.*;
import java.awt.GraphicsConfiguration;
import com.sun.j3d.utils.universe.*;
import javax.media.j3d.*;
import javax.vecmath.*;

public class SimpleBehaviorEx extends Applet {

    SimpleBehavior  sb;

    public BranchGroup createSceneGraph() {
        // Create the root of the branch graph
        BranchGroup objRoot = new BranchGroup();

        // Create a SimpleBehavior ;

        sb = new SimpleBehavior();
        // set scheduling bounds
        BoundingSphere bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);
        sb.setSchedulingBounds(bounds);
        // add the SimpleBehavior to the scene graph
        objRoot.addChild(sb);

        return objRoot;
    }

    public SimpleBehaviorEx() {
        GraphicsConfiguration config =
           SimpleUniverse.getPreferredConfiguration();

        Canvas3D canvas = new Canvas3D(config);
        canvas.setSize(800, 800);
        add("Center", canvas);
        // Create an empty scene and attach it to the virtual universe
        BranchGroup scene = createSceneGraph();
        SimpleUniverse u = new SimpleUniverse(canvas);

        u.getViewingPlatform().setNominalViewingTransform();

        u.addBranchGraph(scene);

    }

. . .

Figure 12.1 shows a screenshot after running SimpleBehavior.java. A text string is printed to the output window each time the user presses a key. This program is the simplest possible Behavior and it does no more than a simple key listener.

Figure 12.1. Running SimpleBehavior.java.


Note that in this case we have done little more than create a simple key listener using the Java 3D version of a listener. The one major difference is that the Java 3D Behavior runs in a single thread. Therefore, scene graph changes are grouped to avoid frame discontinuities. There is some talk of changing the single thread rule in a future release but as of the writing of this book, all Behaviors run in a single thread.

We now provide a second example (see Listings 12.3 and 12.4) that demonstrates using a Behavior in combination with Swing components to translate and rotate boxes depending on the state of JRadioButtons.

Listing 12.3 MoveBoxBehavior.java
import java.awt.AWTEvent;
import java.awt.event.*;
import java.util.Enumeration;
import javax.media.j3d.*;
import javax.vecmath.*;

import com.sun.j3d.utils.universe.*;
import javax.swing.JRadioButton;

public class MoveBoxBehavior extends Behavior {


 // protected static final double FAST_SPEED = 2.0;
 // protected static final double NORMAL_SPEED = 1.0;
 // protected static final double SLOW_SPEED = 0.5;

  private TransformGroup tg;
  private Transform3D transform3D;
  private WakeupCondition keyCriterion;

  JRadioButton rb;

  public MoveBoxBehavior(JRadioButton rb, TransformGroup tg )
  {
    this.rb = rb;
    this.tg = tg;
    transform3D = new Transform3D();
  }

  public void initialize()
  {
    WakeupCriterion[] keyEvents = new WakeupCriterion[2];
    keyEvents[0] = new WakeupOnAWTEvent( KeyEvent.KEY_PRESSED );
    keyEvents[1] = new WakeupOnAWTEvent( KeyEvent.KEY_RELEASED );
    keyCriterion = new WakeupOr( keyEvents );
    wakeupOn( keyCriterion );
  }



  public void processStimulus( Enumeration criteria )
  {

       WakeupCriterion wakeup;
       AWTEvent[] event;

       while( criteria.hasMoreElements() )
       {
        wakeup = (WakeupCriterion) criteria.nextElement();
        if( !(wakeup instanceof WakeupOnAWTEvent) )
         continue;

      event = ((WakeupOnAWTEvent)wakeup).getAWTEvent();
      for( int i = 0; i < event.length; i++ ) {
          if( event[i].getID() == KeyEvent.KEY_PRESSED )
          {
            if (rb.isSelected()==true)
                processKeyEvent((KeyEvent)event[i]);
          }
        }
      }
      wakeupOn( keyCriterion );
  }
  protected void processKeyEvent(KeyEvent event)  {
    int keycode = event.getKeyCode();
    tg.getTransform(transform3D);
    Transform3D t = new Transform3D();

    if(keycode == KeyEvent.VK_UP)
     t.setTranslation(new Vector3d(0.0, 0.0, 0.3));
    else if(keycode == KeyEvent.VK_DOWN)
     t.setTranslation(new Vector3d(0.0, 0.0, -0.3));
    else if(keycode == KeyEvent.VK_LEFT)
      t.rotY((2*Math.PI)/36);
    else if(keycode == KeyEvent.VK_RIGHT)
      t.rotY((-2*Math.PI)/36);

    transform3D.mul(t);
    tg.setTransform(transform3D);

  }

}

The following application uses the preceding MoveBoxBehavior class.

Listing 12.4 MoveBox.java
import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Panel;
import java.awt.event.*;
import java.awt.GraphicsConfiguration;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.geometry.Box;

import com.sun.j3d.utils.geometry.Primitive;
import com.sun.j3d.utils.universe.*;
import com.sun.j3d.utils.behaviors.mouse.MouseRotate;
import javax.media.j3d.*;
import javax.vecmath.*;
import javax.swing.JRadioButton;

public class MoveBox extends Applet {
  VirtualUniverse universe;
  Locale locale;
  TransformGroup vpTrans;
  TransformGroup[] boxTGs;
  JRadioButton rb0, rb1, rb2, rb3, rb4;
  View view;
  Bounds bounds;

    public BranchGroup createSceneGraph() {
        // Create the root of the branch graph; this will be returned

        BranchGroup objRoot = new BranchGroup();

        TransformGroup geoTG = new TransformGroup();
        geoTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    geoTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);

        BoundingSphere bounds =
             new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);

        MouseRotate mouseBeh = new MouseRotate(geoTG);
    geoTG.addChild(mouseBeh);
    mouseBeh.setSchedulingBounds(bounds);

        boxTGs = new TransformGroup[4];

    for (int ii=0; ii<4; ii++) {
           boxTGs[ii] = new TransformGroup();
        boxTGs[ii].setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
               boxTGs[ii].setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
    }

        Transform3D t3d = new Transform3D();

        Material mat0 = new Material();
        mat0.setDiffuseColor(new Color3f(.8f, 0.f, 0.f));
        mat0.setSpecularColor(new Color3f(.9f, 0.f, 0.f));
        Appearance ap0 = new Appearance();
        ap0.setMaterial(mat0);

        t3d.set(new Vector3f(-1.f, 0.f, -1.f));
        boxTGs[0].setTransform(t3d);
        boxTGs[0].addChild(new Box(.3f,.3f,.3f,ap0));
        geoTG.addChild(boxTGs[0]);

        Material mat1 = new Material();
        mat1.setDiffuseColor(new Color3f(.0f, 0.f, 0.8f));
        mat1.setSpecularColor(new Color3f(.0f, 0.f, 0.9f));
        Appearance ap1 = new Appearance();
        ap1.setMaterial(mat1);

        t3d.set(new Vector3f(1.f, 0.f, -1.f));
        boxTGs[1].setTransform(t3d);
        boxTGs[1].addChild(new Box(.3f,.3f,.3f,ap1));
        geoTG.addChild(boxTGs[1]);

. . .

        //add the Behaviors
        MoveBoxBehavior mbb0 = new MoveBoxBehavior(rb0,boxTGs[0]);
        mbb0.setSchedulingBounds(bounds);
        objRoot.addChild(mbb0);

        MoveBoxBehavior mbb1 = new MoveBoxBehavior(rb1,boxTGs[1]);
        mbb1.setSchedulingBounds(bounds);
        objRoot.addChild(mbb1);

        MoveBoxBehavior mbb2 = new MoveBoxBehavior(rb2,boxTGs[2]);
        mbb2.setSchedulingBounds(bounds);
        objRoot.addChild(mbb2);

        MoveBoxBehavior mbb3 = new MoveBoxBehavior(rb3,boxTGs[3]);
        mbb3.setSchedulingBounds(bounds);
        objRoot.addChild(mbb3);

        Color3f lcolor = new Color3f(0.9f, 0.9f, 0.9f);
        Vector3f ldir  = new Vector3f(0.0f, -8.0f, -8.0f);

        DirectionalLight dirlight = new DirectionalLight(lcolor, ldir);

        dirlight.setInfluencingBounds(bounds);


       objRoot.addChild(dirlight);

        objRoot.compile();
    return objRoot;
     }

. . .

    public MoveBox() {
    setLayout(new BorderLayout());

. . .

         Panel uipanel = new Panel();

         rb0 = new JRadioButton("Box 1");
         rb1 = new JRadioButton("Box 2");
         rb2 = new JRadioButton("Box 3");
         rb3 = new JRadioButton("Box 4");
         rb4 = new JRadioButton("ViewPlatform");

         uipanel.add(rb0);
         uipanel.add(rb1);
         uipanel.add(rb2);
         uipanel.add(rb3);
         uipanel.add(rb4);

         add("North", uipanel);
         bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);
         BranchGroup scene = createSceneGraph();

. . .

}

Figure 12.2 show the screen output from MoveBox.java. By selecting different radio buttons, the user can move and rotate any combination of boxes and the ViewPlatform.

Figure 12.2. Screen output from MoveBox.java.


In order to reinforce the fundamental concepts for developing a custom Behavior, we include a third example. Listings 12.5 and 12.6 demonstrate the use of a Behavior for changing the emissive color of an object in the scene graph.

Listing 12.5 EmissiveBall.java
public class EmissiveBall extends Applet {

    private SimpleUniverse u = null;
public EmissiveBall() {
  super();
}


public void init() {
  setLayout(new BorderLayout());

  GraphicsConfiguration config =
           SimpleUniverse.getPreferredConfiguration();
  Canvas3D c = new Canvas3D(config);
  add("Center", c);


  u = new SimpleUniverse(c);
  BranchGroup scene = createSceneGraph(u);
        // This will move the ViewPlatform back a bit so the
        // objects in the scene can be viewed.
        u.getViewingPlatform().setNominalViewingTransform();
  u.addBranchGraph(scene);
    }

    public BranchGroup createSceneGraph(SimpleUniverse su) {
  // Create the root of the branch graph
  BranchGroup objRoot = new BranchGroup();
  TransformGroup rotation = new TransformGroup();
  rotation.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
  rotation.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
  objRoot.addChild(rotation);

    Transform3D sphereOffset = new Transform3D();
    sphereOffset.set(new Vector3d(1.1,0.7,0));
    TransformGroup sphereGroup = new TransformGroup(sphereOffset);
    rotation.addChild(sphereGroup);
    Appearance sphereAppearance = new Appearance();

    Material ballMaterial = new Material(
      new Color3f(Color.black), new Color3f(Color.black), new Color3f(Color.black),
      new Color3f(Color.white), 100f);
    sphereAppearance.setMaterial(ballMaterial);
    sphereGroup.addChild(new Sphere(0.6f,sphereAppearance));
    // Create the behaviour that will control the varying of the
    // emissive colour of the sphere. In this case it will vary
    // from red to green, be updated at 10 millisecond intervals,
    // and take 50 updates to go from one colour to the other. It
    // will also cycle (meaning it will go back and forwards between
    // the two colours rather than stopping after 1 complete change.

    EmissiveBehaviour ballLight = new EmissiveBehaviour(
      ballMaterial,new Color3f(Color.red), new Color3f(Color.green),
      10,50,true);
    ballLight.setSchedulingBounds(new BoundingSphere(new Point3d(),100.0));
    rotation.addChild(ballLight);

    Transform3D boxRotation = new Transform3D();
    boxRotation.setRotation(new AxisAngle4d(1.0,1.0,0.0,-Math.PI/1.3));
    TransformGroup boxGroup = new TransformGroup(boxRotation);
    rotation.addChild(boxGroup);
    Appearance cubeAppearance = new Appearance();
    cubeAppearance.setMaterial(new Material(
      new Color3f(Color.blue), new Color3f(Color.black), new Color3f(Color.blue),
      new Color3f(Color.white), 40f));
    Box ourBox = new Box(0.5f,0.5f,0.5f,cubeAppearance);
    boxGroup.addChild(ourBox);

    DirectionalLight light1 = new DirectionalLight(
      new Color3f(Color.white), new Vector3f(-1.0f,-1.0f,-1.0f));
    light1.setInfluencingBounds(new BoundingSphere(new Point3d(),100.0));
    rotation.addChild(light1);

    KeyNavigatorBehavior navigator = new KeyNavigatorBehavior(
          su.getViewingPlatform().getViewPlatformTransform());
    navigator.setSchedulingBounds(new BoundingSphere(
          new Point3d(), 1000.0));
    objRoot.addChild(navigator);

    MouseRotate rotator = new MouseRotate();
    rotator.setTransformGroup(rotation);
    rotator.setSchedulingBounds(new BoundingSphere(
          new Point3d(), 1000.0));
    objRoot.addChild(rotator);

    return objRoot;
}

/***************************************************************
* Free up all resources.
****************************************************************/
public void destroy() {
  u.removeAllLocales();
    }

public static void main(String[] args) {
    new MainFrame(new EmissiveBall(), 512, 512);
 }
}

Listing 12.6 EmissiveBehavior.java
import java.util.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import java.awt.*;

public class EmissiveBehaviour extends Behavior {

  /** What makes the behaviour wake. */
  protected WakeupOnElapsedTime wakeCriteria;

  protected Material    material;
  protected Color3f     startColour;

  protected Color3f     stopColour;

  /** Time (in milliseconds) between updates of the colour. */
  protected int         timeBetweenUpdates;

  /** Number of updates for the colour to max one complete
  * cycle from the start to stop colour. ******************/
  protected int         totalUpdates;

  /** Whether the Behavior will continue to cycle. **/
  protected boolean     cycle;

  /** Current emissiveColor for the Material. */
  protected Color3f     currentColour;

  /** The amount of colour change each update. */
  protected Color3f     colourStep;

  /** Current count of number of updates performed. */
  protected int         stepNumber;


/*************************************************************
* Constructs an EmissiveBehaviour object that will vary the
* emissiveColr of the passed material between the 2 colours
* passed and according to the timing information provided.
**************************************************************/
EmissiveBehaviour(Material mat,Color3f start, Color3f stop,
  int updateInterval, int numUpdates, boolean cyclic) {

  material = mat;
  mat.setCapability(Material.ALLOW_COMPONENT_WRITE);
  startColour = start;
  stopColour = stop;
  timeBetweenUpdates = updateInterval;
  wakeCriteria = new WakeupOnElapsedTime(timeBetweenUpdates);
  totalUpdates = numUpdates;
  cycle = cyclic;
  stepNumber = 0;

  currentColour = startColour;
  ////////////////////////////////////
  // The colour change at each update
  ///////////////////////////////////
  colourStep = new Color3f(
    (stopColour.x-startColour.x)/totalUpdates,
    (stopColour.y-startColour.y)/totalUpdates,
    (stopColour.z-startColour.z)/totalUpdates);
}


public void initialize() {

  wakeupOn(wakeCriteria);
  material.setEmissiveColor(currentColour);
}


public void processStimulus(Enumeration criteria) {

  currentColour.add(colourStep);
  stepNumber++;
  material.setEmissiveColor(currentColour);
  /////////////////////////////////////////////
  // If cyclic and reached the end then colour
  // change needs to occur in other direction
  // (negate RGB change values).
  ////////////////////////////////////////////
  if (stepNumber==totalUpdates && cycle) {
    stepNumber = 0;
    colourStep.x = -colourStep.x;
    colourStep.y = -colourStep.y;
    colourStep.z = -colourStep.z;
  }
  ////////////////////////////////////////////////
  // Set "alarm" for next update as long as it is
  // appropriate
  ///////////////////////////////////////////////
  if (cycle || stepNumber!=totalUpdates)
    wakeupOn(wakeCriteria);
}
}

Figure 12.3 shows the screen output from the Emissive Ball application. Behavior cycles through different levels of emissiveness using a Behavior that wakes up on elapsed time.

Figure 12.3. Screen output from Emissive Ball application.


Boolean WakeupCriteria

As we stated previously, a special class, WakeupCriterion, is used to encapsulate information about singleton wakeup conditions. The WakeupCriterion is an extension of the abstract class WakeupCondition. WakeupCriterion is itself extended to provide for Boolean operations on these conditions. For example, if the programmer wants the Behavior to wake up when both the ViewPlatform intersects the object Bounds and the key k is pressed, the user would use the WakeupAnd class and specify the two conditions that have to be met. The full list of these Boolean WakeupCriteria is given in Table 12.2 below. Note that a WakeupCriteria represents multiple WakeupCriterion.

Table 12.2. Boolean WakeupConditions
Boolean WakeupCondition Usage
WakeupAnd Like an AND gate, returns true when all inputs are simultaneously true.
WakeupOr Like an OR gate, returns true when any inputs are true.
WakeupAndOfOrs Triggers when a series of WakeUpOrs are all true.
WakeupOrOfAnds Triggers when any of a series of WakeUpAnds are true.

Using the postId() Mechanism

In many of the examples used so far, we have used Listeners to monitor Swing and AWT events generated by the user interface. This is a perfectly fine solution when only single changes to the scene graph are required (for example, turn on/off a light or move an object, and so on). However, this approach can introduce problems when multiple and complex changes are made to the scene graph. One particular problem is that a time-consuming set of scene graph changes might not necessarily be ready in its entirety at the same frame/time. In these cases, the changes occur over several frames and can cause some undesirable transitions with part of one scene intermixing with parts of another.

When several changes are to be made to the scene, the recommended approach is to use a Behavior that is activated with the postId() method. The postId() method is used to cause another Behavior to be run and can thus be used to set up a sequential run of Behaviors. Using this method can at first seem a little unintuitive. Therefore, we present a rather simple example that has no 3D content. Listing 12.7 uses a KeyListener to call the postId() method of a Behavior.

Listing 12.7 BehaviorPostEx.java
import javax.swing.*;
import javax.swing.event.*;
import java.awt.BorderLayout;
import java.awt.event.*;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.universe.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import java.util.Enumeration;

public class BehaviorPostEx extends JFrame {

    PostBehavior  pb;

    public BranchGroup createSceneGraph() {

        BranchGroup objRoot = new BranchGroup();

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

        // Create a PostBehavior to handle external scene graph changes
         pb = new PostBehavior(this);
        // set scheduling bounds
        BoundingSphere bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);

        pb.setSchedulingBounds(bounds);

        objTrans.addChild(pb);

        return objRoot;
    }


    public void updateScene() {
       System.out.println("Update scene here");
    }


     public BehaviorPostEx() {
        super("BehaviorPostEx");

        JPanel contentPane = new JPanel();
        contentPane.setLayout(new BorderLayout());

        GraphicsConfiguration config =
           SimpleUniverse.getPreferredConfiguration();

        Canvas3D canvas = new Canvas3D(config);
        canvas.setSize(800, 800);

        KeypressHandler kh = new KeypressHandler(this);
        canvas.addKeyListener(kh);
        contentPane.add(canvas, BorderLayout.CENTER);
        setContentPane(contentPane);

        // Create a simple scene and attach it to the virtual universe
        BranchGroup scene = createSceneGraph();
        SimpleUniverse u = new SimpleUniverse(canvas);

        // This will move the ViewPlatform back a bit so the
        // objects in the scene can be viewed.
        u.getViewingPlatform().setNominalViewingTransform();

        u.addBranchGraph(scene);


        pack();
        show();
    }

public static void main(String[] args) {
        new BehaviorPostEx();
    }
}

//inner class for handling key events

class KeypressHandler implements KeyListener {

    BehaviorPostEx bpe;

     public KeypressHandler(BehaviorPostEx bpe) {
         this.bpe = bpe;
         System.out.println("constructor of KeypressHandler");
     }
     public void keyReleased(java.awt.event.KeyEvent p1) {
     }

     public void keyPressed(java.awt.event.KeyEvent p1) {
       System.out.println("keyPressed");
       bpe.pb.postId(1);

     }

     public void keyTyped(java.awt.event.KeyEvent p1) {
     }

}

The PostBehavior class (as shown in Listing 12.8) is only intended to demonstrate the postId() method. The idea in this case is not to make any changes to the scene graph, but rather to show how the mechanism works by printing messages to the screen. In practice, it is only necessary to use the posted() method when the scene changes are complex.

Indeed, in most of the cases we examined in Chapter 11, “Creating and Viewing the Virtual World,” the changes to the scene graph were such that they easily play out within one frame (sometimes two). In many other cases, the changes to the scene graph occur over a longer period of time, and these are the cases when postId() is needed. The postId() method allows you to specify changes to the scene graph in a Behavior and this guarantees that all changes show up in the same frame. One example that illustrates the need for this mechanism is a particle system where it is likely that a great number of objects would be changing in the scene graph. The calculations that occur over these objects is likely complex. Data coming back from these operations might arrive at different times and on different frames, thus producing the artifact. By virtue of taking part in the general Java 3D behavior loop, the postId() method guarantees that all changes will be gathered together and executed at the same time.

Note that this does not mean that the rendering loop and the behavior loop are synchronized to each other. They are indeed independent. The example in Listing 12.8 demonstrates using the postId() method.

Listing 12.8 PostBehavior.java
import javax.swing.*;
import javax.swing.event.*;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.*;
import java.awt.GraphicsConfiguration;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.universe.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import java.util.Enumeration;

    public class PostBehavior extends Behavior {
        BehaviorPostEx     master; // whom to notify

        // Define a number of postids.
        public static final int POST1_CHANGE = 1;
        public static final int POST2_CHANGE = 1;

        // Add a criteria for each post id
        WakeupCriterion criterion[] = {
                new WakeupOnBehaviorPost(null, POST1_CHANGE)
                };
        WakeupCondition conditions = new WakeupOr( criterion );

        public PostBehavior(BehaviorPostEx owner) {
            super();
            this.owner = owner;
        }

        public void initialize() {
            wakeupOn(conditions);
        }

        public void processStimulus( Enumeration criteria) {
            while (criteria.hasMoreElements()) {
                System.out.println("processStimulus of PostBehavior");
                WakeupOnBehaviorPost post =
                                (WakeupOnBehaviorPost)criteria.nextElement();
                switch (post.getPostId()) {
                  case POST1_CHANGE:
                    master.updateScene();
                    break;
                  default:
                    System.out.println("Unknown post id: " +
                                                post.getPostId());
                    break;
                }
            }

            wakeupOn(conditions);
        }
    }
						

Behavior Culling

Another advantage to using a Behavior is the potential optimizations that can be gained through the use of scheduling bounds. Remember that when Java 3D is creating optimizations during rendering, a big savings can be achieved by disregarding objects that are not within the spatial sphere of the View Platform. This can be a useful assumption. In many cases, however we do indeed want computations to occur in the background. For example in simulation, we do not necessarily want the world to stop just because our View Platform is not nearby. This situation can be rectified by having the bounds set to infinity for objects that should always be active thereby ensuring that activation is always met.

Other Uses of Behaviors

The main purpose of a Behavior is to control scene graph elements. However, we note again that Behaviors can be used in a variety of contexts. In our research on navigation, for example, we often need to move the ViewPlatform in response to a joystick or keyboard user event. At the same time, we need to have file output relating to the current location of the platform as well as precise timing and user event data. This is all accomplished in a series of custom Behaviors. We also use Behaviors to start movies inside the 3D environment, which is a topic we will cover in Chapter 14, “Integrating the Java Media APIs.”

Most, if not all, user interface procedures are achieved through Behaviors. Now that you have the fundamentals of Behaviors, it is possible for you to understand more advanced topics such as picking and navigation. We begin with the important topic of picking.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset