|
This Java tip creates five sample behaviors and applies them to an object in a scene:
The behaviors are as follows:
- Object Size - displays the size of the geometry in the object
- Explode - explodes the geometry after 10 seconds
- Stretch - allows the geometry to be stretched using the spacebar (simple physics)
- Bounds - displays the bounds of the object
- FPS - displays frames-per-seconds rendered
import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.GraphicsConfigTemplate;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Panel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.File;
import java.net.URL;
import javax.media.j3d.Alpha;
import javax.media.j3d.Appearance;
import javax.media.j3d.AudioDevice;
import javax.media.j3d.Background;
import javax.media.j3d.Behavior;
import javax.media.j3d.BoundingBox;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.Bounds;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.GraphicsConfigTemplate3D;
import javax.media.j3d.Group;
import javax.media.j3d.Locale;
import javax.media.j3d.Node;
import javax.media.j3d.PhysicalBody;
import javax.media.j3d.PhysicalEnvironment;
import javax.media.j3d.PointAttributes;
import javax.media.j3d.PolygonAttributes;
import javax.media.j3d.RotationInterpolator;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Switch;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.TransparencyAttributes;
import javax.media.j3d.View;
import javax.media.j3d.ViewPlatform;
import javax.media.j3d.VirtualUniverse;
import javax.media.j3d.WakeupCondition;
import javax.media.j3d.WakeupCriterion;
import javax.media.j3d.WakeupOnAWTEvent;
import javax.media.j3d.WakeupOnElapsedFrames;
import javax.media.j3d.WakeupOnElapsedTime;
import javax.media.j3d.WakeupOr;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;
import com.sun.j3d.audioengines.javasound.JavaSoundMixer;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.geometry.ColorCube;
import com.sun.j3d.utils.geometry.Primitive;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.image.TextureLoader;
/**
* Creates five sample behaviors and applies them to an object in a scene:
* <p>
* 1. Object Size - displays the size of the geometry in the object 2. Explode -
* explodes the geometry after 10 seconds 3. Stretch - allows the geometry to be
* stretched using the spacebar (simple physics) 4. Bounds - displays the bounds
* of the object 5. FPS - displays frames-per-seconds rendered
*/
public class BehaviorTest extends Java3dApplet implements ExplosionListener,
ActionListener {
private static int m_kWidth = 350;
private static int m_kHeight = 400;
private RotationInterpolator m_RotationInterpolator = null;
private StretchBehavior m_StretchBehavior = null;
private ObjectSizeBehavior m_SizeBehavior = null;
private ExplodeBehavior m_ExplodeBehavior = null;
private FpsBehavior m_FpsBehavior = null;
private BoundsBehavior m_BoundsBehavior = null;
public BehaviorTest() {
initJava3d();
Panel controlPanel = new Panel();
Button rotateButton = new Button("Rotate");
rotateButton.addActionListener(this);
controlPanel.add(rotateButton);
Button objSizeButton = new Button("Object Size");
objSizeButton.addActionListener(this);
controlPanel.add(objSizeButton);
Button explodeButton = new Button("Explode");
explodeButton.addActionListener(this);
controlPanel.add(explodeButton);
Button stretchButton = new Button("Stretch");
stretchButton.addActionListener(this);
controlPanel.add(stretchButton);
Button boundsButton = new Button("Bounds");
boundsButton.addActionListener(this);
controlPanel.add(boundsButton);
Button fpsButton = new Button("FPS");
fpsButton.addActionListener(this);
controlPanel.add(fpsButton);
add(controlPanel, BorderLayout.SOUTH);
}
// handle event from the GUI components we created
public void actionPerformed(ActionEvent event) {
if (event.getActionCommand().equals("Object Size") != false)
m_SizeBehavior.setEnable(!m_SizeBehavior.getEnable());
else if (event.getActionCommand().equals("Explode") != false)
m_ExplodeBehavior.setEnable(!m_ExplodeBehavior.getEnable());
else if (event.getActionCommand().equals("Stretch") != false)
m_StretchBehavior.setEnable(!m_StretchBehavior.getEnable());
else if (event.getActionCommand().equals("Rotate") != false)
m_RotationInterpolator.setEnable(!m_RotationInterpolator
.getEnable());
else if (event.getActionCommand().equals("Bounds") != false)
m_BoundsBehavior.setEnable(!m_BoundsBehavior.getEnable());
else if (event.getActionCommand().equals("FPS") != false)
m_FpsBehavior.setEnable(!m_FpsBehavior.getEnable());
}
protected Background createBackground() {
return null;
}
protected BranchGroup createSceneBranchGroup() {
BranchGroup objRoot = super.createSceneBranchGroup();
// create a TransformGroup to rotate the hand
TransformGroup objTrans = new TransformGroup();
objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
// create a RotationInterpolator behavior to rotate the hand
Transform3D yAxis = new Transform3D();
Alpha rotationAlpha = new Alpha(-1, Alpha.INCREASING_ENABLE, 0, 0,
4000, 0, 0, 0, 0, 0);
m_RotationInterpolator = new RotationInterpolator(rotationAlpha,
objTrans, yAxis, 0.0f, (float) Math.PI * 2.0f);
m_RotationInterpolator.setSchedulingBounds(createApplicationBounds());
objTrans.addChild(m_RotationInterpolator);
// create an Appearance and Material
Appearance app = new Appearance();
TextureLoader tex = new TextureLoader("earth.jpg", this);
app.setTexture(tex.getTexture());
Sphere sphere = new Sphere(3, Primitive.GENERATE_NORMALS
| Primitive.GENERATE_TEXTURE_COORDS, 32, app);
// connect the scenegraph
objTrans.addChild(sphere);
objRoot.addChild(objTrans);
m_FpsBehavior = new FpsBehavior();
m_FpsBehavior.setSchedulingBounds(getApplicationBounds());
objRoot.addChild(m_FpsBehavior);
m_BoundsBehavior = new BoundsBehavior(sphere);
m_BoundsBehavior.setSchedulingBounds(getApplicationBounds());
m_BoundsBehavior.addBehaviorToParentGroup(objTrans);
m_StretchBehavior = new StretchBehavior((GeometryArray) sphere
.getShape().getGeometry());
m_StretchBehavior.setSchedulingBounds(getApplicationBounds());
objRoot.addChild(m_StretchBehavior);
m_StretchBehavior.setEnable(false);
m_SizeBehavior = new ObjectSizeBehavior((GeometryArray) sphere
.getShape().getGeometry());
m_SizeBehavior.setSchedulingBounds(getApplicationBounds());
objRoot.addChild(m_SizeBehavior);
m_SizeBehavior.setEnable(false);
m_ExplodeBehavior = new ExplodeBehavior(sphere.getShape(), 10000, 20,
this);
m_ExplodeBehavior.setSchedulingBounds(getApplicationBounds());
objRoot.addChild(m_ExplodeBehavior);
return objRoot;
}
public WakeupCondition onExplosionFinished(ExplodeBehavior explodeBehavior,
Shape3D shape3D) {
System.out.println("Explosion Finished.");
return explodeBehavior.restart(shape3D, 10000, 20, this);
}
public static void main(String[] args) {
BehaviorTest behaviorTest = new BehaviorTest();
behaviorTest.saveCommandLineArguments(args);
new MainFrame(behaviorTest, m_kWidth, m_kHeight);
}
}
//this class implements a simple behavior that
//output the rendered Frames Per Second.
class FpsBehavior extends Behavior {
// the wake up condition for the behavior
protected WakeupCondition m_WakeupCondition = null;
protected long m_StartTime = 0;
private final int m_knReportInterval = 100;
public FpsBehavior() {
// save the WakeupCriterion for the behavior
m_WakeupCondition = new WakeupOnElapsedFrames(m_knReportInterval);
}
public void initialize() {
// apply the initial WakeupCriterion
wakeupOn(m_WakeupCondition);
}
public void processStimulus(java.util.Enumeration criteria) {
while (criteria.hasMoreElements()) {
WakeupCriterion wakeUp = (WakeupCriterion) criteria.nextElement();
// every N frames, update position of the graphic
if (wakeUp instanceof WakeupOnElapsedFrames) {
if (m_StartTime > 0) {
final long interval = System.currentTimeMillis()
- m_StartTime;
System.out.println("FPS: " + (m_knReportInterval * 1000)
/ interval);
}
m_StartTime = System.currentTimeMillis();
}
}
// assign the next WakeUpCondition, so we are notified again
wakeupOn(m_WakeupCondition);
}
}
//this class implements a simple behavior that
//displays a graphical representation of the Bounds
//of a Node.
class BoundsBehavior extends Behavior {
// the wake up condition for the behavior
protected WakeupCondition m_WakeupCondition = null;
// the Node that we are tracking
protected Node m_Node = null;
protected TransformGroup m_TransformGroup = null;
protected Switch m_BoundsSwitch = null;
protected Transform3D m_Transform3D = null;
protected Vector3d m_Scale = null;
protected Vector3d m_Vector3d = null;
protected Point3d m_Point3d1 = null;
protected Point3d m_Point3d2 = null;
private final int m_kSphereBounds = 0;
private final int m_kBoxBounds = 1;
public BoundsBehavior(Node node) {
// save the GeometryArray that we are modifying
m_Node = node;
m_Transform3D = new Transform3D();
m_Scale = new Vector3d();
m_Vector3d = new Vector3d();
m_Point3d1 = new Point3d();
m_Point3d2 = new Point3d();
// set the capability bits that the behavior requires
m_Node.setCapability(Node.ALLOW_BOUNDS_READ);
// save the WakeupCriterion for the behavior
m_WakeupCondition = new WakeupOnElapsedFrames(10);
}
public void addBehaviorToParentGroup(Group nodeParentGroup) {
nodeParentGroup.addChild(this);
m_TransformGroup = new TransformGroup();
m_TransformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
m_BoundsSwitch = new Switch();
m_BoundsSwitch.setCapability(Switch.ALLOW_SWITCH_WRITE);
Appearance app = new Appearance();
PolygonAttributes polyAttrbutes = new PolygonAttributes();
polyAttrbutes.setPolygonMode(PolygonAttributes.POLYGON_LINE);
polyAttrbutes.setCullFace(PolygonAttributes.CULL_NONE);
app.setPolygonAttributes(polyAttrbutes);
m_BoundsSwitch.addChild(new Sphere(1, app));
ColorCube cube = new ColorCube();
cube.setAppearance(app);
Group g = new Group();
g.addChild(cube);
m_BoundsSwitch.addChild(g);
m_BoundsSwitch.setWhichChild(Switch.CHILD_NONE);
m_TransformGroup.addChild(m_BoundsSwitch);
nodeParentGroup.addChild(m_TransformGroup);
}
public void setEnable(boolean bEnable) {
super.setEnable(bEnable);
if (m_BoundsSwitch != null) {
if (bEnable == false)
m_BoundsSwitch.setWhichChild(Switch.CHILD_NONE);
}
}
public void initialize() {
// apply the initial WakeupCriterion
wakeupOn(m_WakeupCondition);
}
public void processStimulus(java.util.Enumeration criteria) {
while (criteria.hasMoreElements()) {
WakeupCriterion wakeUp = (WakeupCriterion) criteria.nextElement();
// every N frames, update position of the graphic
if (wakeUp instanceof WakeupOnElapsedFrames) {
if (m_TransformGroup != null) {
Bounds bounds = m_Node.getBounds();
int nBoundsType = m_kBoxBounds;
m_Transform3D.setIdentity();
if (bounds instanceof BoundingSphere) {
nBoundsType = m_kSphereBounds;
((BoundingSphere) bounds).getCenter(m_Point3d1);
m_Vector3d.x = m_Point3d1.x;
m_Vector3d.y = m_Point3d1.y;
m_Vector3d.z = m_Point3d1.z;
m_Scale.x = ((BoundingSphere) bounds).getRadius() / 2;
m_Scale.y = m_Scale.x;
m_Scale.z = m_Scale.y;
} else if (bounds instanceof BoundingBox) {
nBoundsType = m_kBoxBounds;
((BoundingBox) bounds).getLower(m_Point3d1);
((BoundingBox) bounds).getUpper(m_Point3d2);
m_Vector3d.x = (m_Point3d1.x + m_Point3d2.x) / 2;
m_Vector3d.y = (m_Point3d1.y + m_Point3d2.y) / 2;
m_Vector3d.z = (m_Point3d1.z + m_Point3d2.z) / 2;
m_Scale.x = Math.abs(m_Point3d1.x - m_Point3d2.x) / 2;
m_Scale.y = Math.abs(m_Point3d1.y - m_Point3d2.y) / 2;
m_Scale.z = Math.abs(m_Point3d1.z - m_Point3d2.z) / 2;
} else
System.err
.println("BoundsBehavior found a Bounds it cannot represent: "
+ bounds);
m_Transform3D.setScale(m_Scale);
m_Transform3D.setTranslation(m_Vector3d);
m_TransformGroup.setTransform(m_Transform3D);
m_BoundsSwitch.setWhichChild(nBoundsType);
} else {
System.err
.println("Call addBehaviorToParentGroup for BoundsBehavior.");
}
}
}
// assign the next WakeUpCondition, so we are notified again
wakeupOn(m_WakeupCondition);
}
}
class ExplodeBehavior extends Behavior {
// the wake up condition for the behavior
protected WakeupCondition m_InitialWakeupCondition = null;
protected WakeupCondition m_FrameWakeupCondition = null;
// the GeometryArray for the Shape3D that we are modifying
protected Shape3D m_Shape3D = null;
protected GeometryArray m_GeometryArray = null;
protected float[] m_CoordinateArray = null;
protected float[] m_OriginalCoordinateArray = null;
protected Appearance m_Appearance = null;
protected TransparencyAttributes m_TransparencyAttributes = null;
protected int m_nElapsedTime = 0;
protected int m_nNumFrames = 0;
protected int m_nFrameNumber = 0;
protected Vector3f m_Vector = null;
ExplosionListener m_Listener = null;
public ExplodeBehavior(Shape3D shape3D, int nElapsedTime, int nNumFrames,
ExplosionListener listener) {
// allocate a temporary vector
m_Vector = new Vector3f();
m_FrameWakeupCondition = new WakeupOnElapsedFrames(1);
restart(shape3D, nElapsedTime, nNumFrames, listener);
}
public WakeupCondition restart(Shape3D shape3D, int nElapsedTime,
int nNumFrames, ExplosionListener listener) {
System.out.println("Will explode after: " + nElapsedTime / 1000
+ " secs.");
m_Shape3D = shape3D;
m_nElapsedTime = nElapsedTime;
m_nNumFrames = nNumFrames;
m_nFrameNumber = 0;
// create the WakeupCriterion for the behavior
m_InitialWakeupCondition = new WakeupOnElapsedTime(m_nElapsedTime);
m_Listener = listener;
// save the GeometryArray that we are modifying
m_GeometryArray = (GeometryArray) m_Shape3D.getGeometry();
if (m_Shape3D.isLive() == false && m_Shape3D.isCompiled() == false) {
// set the capability bits that the behavior requires
m_Shape3D.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
m_Shape3D.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
m_Shape3D.getAppearance().setCapability(
Appearance.ALLOW_POINT_ATTRIBUTES_WRITE);
m_Shape3D.getAppearance().setCapability(
Appearance.ALLOW_POLYGON_ATTRIBUTES_WRITE);
m_Shape3D.getAppearance().setCapability(
Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE);
m_Shape3D.getAppearance().setCapability(
Appearance.ALLOW_TEXTURE_WRITE);
m_GeometryArray.setCapability(GeometryArray.ALLOW_COORDINATE_READ);
m_GeometryArray.setCapability(GeometryArray.ALLOW_COORDINATE_WRITE);
m_GeometryArray.setCapability(GeometryArray.ALLOW_COUNT_READ);
}
// make a copy of the object's original appearance
m_Appearance = new Appearance();
m_Appearance = (Appearance) m_Shape3D.getAppearance()
.cloneNodeComponent(true);
// allocate an array for the model coordinates
m_CoordinateArray = new float[3 * m_GeometryArray.getVertexCount()];
// make a copy of the models original coordinates
m_OriginalCoordinateArray = new float[3 * m_GeometryArray
.getVertexCount()];
m_GeometryArray.getCoordinates(0, m_OriginalCoordinateArray);
// start (or restart) the behavior
setEnable(true);
return m_InitialWakeupCondition;
}
public void initialize() {
// apply the initial WakeupCriterion
wakeupOn(m_InitialWakeupCondition);
}
public void processStimulus(java.util.Enumeration criteria) {
while (criteria.hasMoreElements()) {
WakeupCriterion wakeUp = (WakeupCriterion) criteria.nextElement();
if (wakeUp instanceof WakeupOnElapsedTime) {
// we are starting the explosion, apply the
// appearance changes we require
PolygonAttributes polyAttribs = new PolygonAttributes(
PolygonAttributes.POLYGON_POINT,
PolygonAttributes.CULL_NONE, 0);
m_Shape3D.getAppearance().setPolygonAttributes(polyAttribs);
PointAttributes pointAttribs = new PointAttributes(3, false);
m_Shape3D.getAppearance().setPointAttributes(pointAttribs);
m_Shape3D.getAppearance().setTexture(null);
m_TransparencyAttributes = new TransparencyAttributes(
TransparencyAttributes.NICEST, 0);
m_TransparencyAttributes
.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE);
m_Shape3D.getAppearance().setTransparencyAttributes(
m_TransparencyAttributes);
} else {
// we are mid explosion, modify the GeometryArray
m_nFrameNumber++;
m_GeometryArray.getCoordinates(0, m_CoordinateArray);
m_TransparencyAttributes
.setTransparency(((float) m_nFrameNumber)
/ ((float) m_nNumFrames));
m_Shape3D.getAppearance().setTransparencyAttributes(
m_TransparencyAttributes);
for (int n = 0; n < m_CoordinateArray.length; n += 3) {
m_Vector.x = m_CoordinateArray[n];
m_Vector.y = m_CoordinateArray[n + 1];
m_Vector.z = m_CoordinateArray[n + 2];
m_Vector.normalize();
m_CoordinateArray[n] += m_Vector.x * Math.random()
+ Math.random();
m_CoordinateArray[n + 1] += m_Vector.y * Math.random()
+ Math.random();
m_CoordinateArray[n + 2] += m_Vector.z * Math.random()
+ Math.random();
}
// assign the new coordinates
m_GeometryArray.setCoordinates(0, m_CoordinateArray);
}
}
if (m_nFrameNumber < m_nNumFrames) {
// assign the next WakeUpCondition, so we are notified again
wakeupOn(m_FrameWakeupCondition);
} else {
// we are at the end of the explosion
// reapply the original appearance and GeometryArray
// coordinates
setEnable(false);
m_Shape3D.setAppearance(m_Appearance);
m_GeometryArray.setCoordinates(0, m_OriginalCoordinateArray);
m_OriginalCoordinateArray = null;
m_GeometryArray = null;
m_CoordinateArray = null;
m_TransparencyAttributes = null;
// if we have a listener notify them that we are done
if (m_Listener != null)
wakeupOn(m_Listener.onExplosionFinished(this, m_Shape3D));
}
}
}
interface ExplosionListener {
abstract public WakeupCondition onExplosionFinished(
ExplodeBehavior explodeBehavior, Shape3D shape3D);
}
//this class implements a simple behavior that
//calculates and prints the size of an object
//based on the vertices in its GeometryArray
class ObjectSizeBehavior extends Behavior {
// the wake up condition for the behavior
protected WakeupCondition m_WakeupCondition = null;
// the GeometryArray for the Shape3D that we are querying
protected GeometryArray m_GeometryArray = null;
// cache some information on the model to save reallocation
protected float[] m_CoordinateArray = null;
protected BoundingBox m_BoundingBox = null;
protected Point3d m_Point = null;;
public ObjectSizeBehavior(GeometryArray geomArray) {
// save the GeometryArray that we are modifying
m_GeometryArray = geomArray;
// set the capability bits that the behavior requires
m_GeometryArray.setCapability(GeometryArray.ALLOW_COORDINATE_READ);
m_GeometryArray.setCapability(GeometryArray.ALLOW_COUNT_READ);
// allocate an array for the coordinates
m_CoordinateArray = new float[3 * m_GeometryArray.getVertexCount()];
// create the BoundingBox used to
// calculate the size of the object
m_BoundingBox = new BoundingBox();
// create a temporary point
m_Point = new Point3d();
// create the WakeupCriterion for the behavior
WakeupCriterion criterionArray[] = new WakeupCriterion[1];
criterionArray[0] = new WakeupOnElapsedFrames(20);
// save the WakeupCriterion for the behavior
m_WakeupCondition = new WakeupOr(criterionArray) | |