Java tutorial
/* Essential Java 3D Fast Ian Palmer Publisher: Springer-Verlag ISBN: 1-85233-394-4 */ import java.awt.AWTEvent; import java.awt.BorderLayout; import java.awt.Button; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.util.Enumeration; import javax.media.j3d.Alpha; import javax.media.j3d.AmbientLight; import javax.media.j3d.Appearance; import javax.media.j3d.Behavior; import javax.media.j3d.BoundingSphere; import javax.media.j3d.Bounds; import javax.media.j3d.BranchGroup; import javax.media.j3d.Canvas3D; import javax.media.j3d.DirectionalLight; import javax.media.j3d.Locale; import javax.media.j3d.Material; import javax.media.j3d.Node; import javax.media.j3d.PhysicalBody; import javax.media.j3d.PhysicalEnvironment; import javax.media.j3d.PositionInterpolator; import javax.media.j3d.Switch; import javax.media.j3d.Transform3D; import javax.media.j3d.TransformGroup; import javax.media.j3d.View; import javax.media.j3d.ViewPlatform; import javax.media.j3d.VirtualUniverse; import javax.media.j3d.WakeupCriterion; import javax.media.j3d.WakeupOnAWTEvent; import javax.media.j3d.WakeupOnCollisionEntry; import javax.media.j3d.WakeupOnElapsedTime; import javax.media.j3d.WakeupOr; import javax.vecmath.Color3f; import javax.vecmath.Matrix3d; import javax.vecmath.Point3d; import javax.vecmath.Vector3d; import javax.vecmath.Vector3f; import com.sun.j3d.loaders.Scene; import com.sun.j3d.loaders.objectfile.ObjectFile; import com.sun.j3d.utils.geometry.Box; import com.sun.j3d.utils.geometry.Cylinder; import com.sun.j3d.utils.geometry.Sphere; /** * This application demonstrates a number of things in the implementation of a * simple shooting game. The object of the the game is to shoot a duck that * repeatedly moves across the screen from left to right. There are two duck * models, one for the 'live' duck and one for the 'dead' one. These are loaded * from 'duck.obj' and 'deadduck.obj' files. The 'gun' is built from primitives. * The duck and the ball that is used to shoot the duck use interpolators for * their animation. The gun uses key board input to aim and fire it, and * collision detection is used to 'kill' the duck. * * @author I.J.Palmer * @version 1.0 */ public class SimpleGame extends Frame implements ActionListener { protected Canvas3D myCanvas3D = new Canvas3D(null); protected Button exitButton = new Button("Exit"); protected BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 100.0); /** Switch that is used to swap the duck models */ Switch duckSwitch; /** Alpha used to drive the duck animation */ Alpha duckAlpha; /** Used to drive the ball animation */ Alpha ballAlpha; /** Used to move the ball */ PositionInterpolator moveBall; /** Used to rotate the gun */ TransformGroup gunXfmGrp = new TransformGroup(); /** * This builds the view branch of the scene graph. * * @return BranchGroup with viewing objects attached. */ protected BranchGroup buildViewBranch(Canvas3D c) { BranchGroup viewBranch = new BranchGroup(); Transform3D viewXfm = new Transform3D(); Matrix3d viewTilt = new Matrix3d(); viewTilt.rotX(Math.PI / -6); viewXfm.set(viewTilt, new Vector3d(0.0, 10.0, 10.0), 1.0); TransformGroup viewXfmGroup = new TransformGroup(viewXfm); ViewPlatform myViewPlatform = new ViewPlatform(); PhysicalBody myBody = new PhysicalBody(); PhysicalEnvironment myEnvironment = new PhysicalEnvironment(); viewXfmGroup.addChild(myViewPlatform); viewBranch.addChild(viewXfmGroup); View myView = new View(); myView.addCanvas3D(c); myView.attachViewPlatform(myViewPlatform); myView.setPhysicalBody(myBody); myView.setPhysicalEnvironment(myEnvironment); return viewBranch; } /** * This adds some lights to the content branch of the scene graph. * * @param b * The BranchGroup to add the lights to. */ protected void addLights(BranchGroup b) { Color3f ambLightColour = new Color3f(0.5f, 0.5f, 0.5f); AmbientLight ambLight = new AmbientLight(ambLightColour); ambLight.setInfluencingBounds(bounds); Color3f dirLightColour = new Color3f(1.0f, 1.0f, 1.0f); Vector3f dirLightDir = new Vector3f(-1.0f, -1.0f, -1.0f); DirectionalLight dirLight = new DirectionalLight(dirLightColour, dirLightDir); dirLight.setInfluencingBounds(bounds); b.addChild(ambLight); b.addChild(dirLight); } /** * This builds the gun geometry. It uses box and cylinder primitives and * sets up a transform group so that we can rotate the gun. */ protected BranchGroup buildGun() { BranchGroup theGun = new BranchGroup(); Appearance gunApp = new Appearance(); Color3f ambientColour = new Color3f(0.5f, 0.5f, 0.5f); Color3f emissiveColour = new Color3f(0.0f, 0.0f, 0.0f); Color3f specularColour = new Color3f(1.0f, 1.0f, 1.0f); Color3f diffuseColour = new Color3f(0.5f, 0.5f, 0.5f); float shininess = 20.0f; gunApp.setMaterial(new Material(ambientColour, emissiveColour, diffuseColour, specularColour, shininess)); TransformGroup init = new TransformGroup(); TransformGroup barrel = new TransformGroup(); Transform3D gunXfm = new Transform3D(); Transform3D barrelXfm = new Transform3D(); barrelXfm.set(new Vector3d(0.0, -2.0, 0.0)); barrel.setTransform(barrelXfm); Matrix3d gunXfmMat = new Matrix3d(); gunXfmMat.rotX(Math.PI / 2); gunXfm.set(gunXfmMat, new Vector3d(0.0, 0.0, 0.0), 1.0); init.setTransform(gunXfm); gunXfmGrp.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); gunXfmGrp.addChild(new Box(1.0f, 1.0f, 0.5f, gunApp)); barrel.addChild(new Cylinder(0.3f, 4.0f, gunApp)); gunXfmGrp.addChild(barrel); theGun.addChild(init); init.addChild(gunXfmGrp); return theGun; } /** * Creates the duck. This loads the two duck geometries from the files * 'duck.obj' and 'deadduck.obj' and loads these into a switch. The access * rights to the switch are then set so we can write to this switch to swap * between the two duck models. It also creates a transform group and an * interpolator to move the duck. * * @return BranchGroup with content attached. */ protected BranchGroup buildDuck() { BranchGroup theDuck = new BranchGroup(); duckSwitch = new Switch(0); duckSwitch.setCapability(Switch.ALLOW_SWITCH_WRITE); ObjectFile f1 = new ObjectFile(); ObjectFile f2 = new ObjectFile(); Scene s1 = null; Scene s2 = null; try { s1 = f1.load("duck.obj"); s2 = f2.load("deadduck.obj"); } catch (Exception e) { System.exit(1); } TransformGroup duckRotXfmGrp = new TransformGroup(); Transform3D duckRotXfm = new Transform3D(); Matrix3d duckRotMat = new Matrix3d(); duckRotMat.rotY(Math.PI / 2); duckRotXfm.set(duckRotMat, new Vector3d(0.0, 0.0, -30.0), 1.0); duckRotXfmGrp.setTransform(duckRotXfm); duckRotXfmGrp.addChild(duckSwitch); duckSwitch.addChild(s1.getSceneGroup()); duckSwitch.addChild(s2.getSceneGroup()); TransformGroup duckMovXfmGrp = new TransformGroup(); duckMovXfmGrp.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); duckMovXfmGrp.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); duckMovXfmGrp.addChild(duckRotXfmGrp); duckAlpha = new Alpha(-1, 0, 0, 3000, 0, 0); Transform3D axis = new Transform3D(); PositionInterpolator moveDuck = new PositionInterpolator(duckAlpha, duckMovXfmGrp, axis, -30.0f, 30.0f); moveDuck.setSchedulingBounds(bounds); theDuck.addChild(moveDuck); theDuck.addChild(duckMovXfmGrp); return theDuck; } /** * This builds the ball that acts as the bullet for our gun. The ball is * created from a sphere primitive, and a transform group and interpolator * are added so that we can 'fire' the bullet. * * @return BranchGroup that is the root of the ball branch. */ protected BranchGroup buildBall() { BranchGroup theBall = new BranchGroup(); Appearance ballApp = new Appearance(); Color3f ambientColour = new Color3f(1.0f, 0.0f, 0.0f); Color3f emissiveColour = new Color3f(0.0f, 0.0f, 0.0f); Color3f specularColour = new Color3f(1.0f, 1.0f, 1.0f); Color3f diffuseColour = new Color3f(1.0f, 0.0f, 0.0f); float shininess = 20.0f; ballApp.setMaterial(new Material(ambientColour, emissiveColour, diffuseColour, specularColour, shininess)); Sphere ball = new Sphere(0.2f, ballApp); TransformGroup ballMovXfmGrp = new TransformGroup(); ballMovXfmGrp.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); ballMovXfmGrp.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); ballMovXfmGrp.addChild(ball); theBall.addChild(ballMovXfmGrp); ballAlpha = new Alpha(1, 0, 0, 500, 0, 0); Transform3D axis = new Transform3D(); axis.rotY(Math.PI / 2); moveBall = new PositionInterpolator(ballAlpha, ballMovXfmGrp, axis, 0.0f, 50.0f); moveBall.setSchedulingBounds(bounds); theBall.addChild(moveBall); return theBall; } /** * This puts all the content togther. It used the three 'build' functions to * create the duck, the gun and the ball. It also creates the two behaviours * from the DuckBehaviour and GunBehaviour classes. It then puts all this * together. * * @return BranchGroup that is the root of the content. */ protected BranchGroup buildContentBranch() { BranchGroup contentBranch = new BranchGroup(); Node theDuck = buildDuck(); contentBranch.addChild(theDuck); Node theBall = buildBall(); contentBranch.addChild(theBall); DuckBehaviour hitTheDuck = new DuckBehaviour(theDuck, duckSwitch, duckAlpha, bounds); GunBehaviour shootTheGun = new GunBehaviour(ballAlpha, moveBall, gunXfmGrp, bounds); contentBranch.addChild(hitTheDuck); contentBranch.addChild(shootTheGun); contentBranch.addChild(buildGun()); addLights(contentBranch); return contentBranch; } /** Exit the application */ public void actionPerformed(ActionEvent e) { dispose(); System.exit(0); } public SimpleGame() { VirtualUniverse myUniverse = new VirtualUniverse(); Locale myLocale = new Locale(myUniverse); myLocale.addBranchGraph(buildViewBranch(myCanvas3D)); myLocale.addBranchGraph(buildContentBranch()); setTitle("Duck Shoot!"); setSize(400, 400); setLayout(new BorderLayout()); add("Center", myCanvas3D); exitButton.addActionListener(this); add("South", exitButton); setVisible(true); } public static void main(String[] args) { SimpleGame sg = new SimpleGame(); } } /** * This is used in the SimpleGame application. It defines the behaviour for the * duck, which is the target in the shooting game. If something collides with * the duck, it swaps a switch value to 'kill' the duck The duck is revived when * it's alpha value passes through zero. * * @author I.J.Palmer * @version 1.0 */ class DuckBehaviour extends Behavior { /** The shape that is being watched for collisions. */ protected Node collidingShape; /** The separate criteria that trigger this behaviour */ protected WakeupCriterion[] theCriteria; /** The result of the 'OR' of the separate criteria */ protected WakeupOr oredCriteria; /** The switch that is used to swap the duck shapes */ protected Switch theSwitch; /** The alpha generator that drives the animation */ protected Alpha theTargetAlpha; /** Defines whether the duck is dead or alive */ protected boolean dead = false; /** * This sets up the data for the behaviour. * * @param theShape * Node that is to be watched for collisions. * @param sw * Switch that is used to swap shapes. * @param a1 * Alpha that drives the duck's animation. * @param theBounds * Bounds that define the active region for this behaviour. */ public DuckBehaviour(Node theShape, Switch sw, Alpha a1, Bounds theBounds) { collidingShape = theShape; theSwitch = sw; theTargetAlpha = a1; setSchedulingBounds(theBounds); } /** * This sets up the criteria for triggering the behaviour. It creates an * collision crtiterion and a time elapsed criterion, OR's these together * and then sets the OR'ed criterion as the wake up condition. */ public void initialize() { theCriteria = new WakeupCriterion[2]; theCriteria[0] = new WakeupOnCollisionEntry(collidingShape); theCriteria[1] = new WakeupOnElapsedTime(1); oredCriteria = new WakeupOr(theCriteria); wakeupOn(oredCriteria); } /** * This is where the work is done. If there is a collision, then if the duck * is alive we switch to the dead duck. If the duck was already dead then we * take no action. The other case we need to check for is when the alpha * value is zero, when we need to set the duck back to the live one for its * next traversal of the screen. Finally, the wake up condition is set to be * the OR'ed criterion again. */ public void processStimulus(Enumeration criteria) { while (criteria.hasMoreElements()) { WakeupCriterion theCriterion = (WakeupCriterion) criteria.nextElement(); if (theCriterion instanceof WakeupOnCollisionEntry) { //There'sa collision so if the duck is alive swap //it to the dead one if (dead == false) { theSwitch.setWhichChild(1); dead = true; } } else if (theCriterion instanceof WakeupOnElapsedTime) { //If there isn't a collision, then check the alpha //value and if it's zero, revive the duck if (theTargetAlpha.value() < 0.1) { theSwitch.setWhichChild(0); dead = false; } } } wakeupOn(oredCriteria); } } /** * This is used in the SimpleGame application. It defines a behaviour that * allows a 'gun' to be rotated when left and right cursor keys are pressed and * then a ball is 'fired' when the space bar is pressed. The 'firing' is * achieved by setting the start time of an interpolator to the current time. * * @author I.J.Palmer * @version 1.0 */ class GunBehaviour extends Behavior { /** The separate criteria that trigger this behaviour */ protected WakeupCriterion theCriterion; /** The alpha that is used to 'fire' the ball */ protected Alpha theGunAlpha; /** Used to animate the ball */ protected PositionInterpolator theInterpolator; /** Used to calculate the current direction of the gun */ protected int aim = 0; /** This is used to rotate the gun */ protected TransformGroup aimXfmGrp; /** Used to aim the ball */ protected Matrix3d aimShotMat = new Matrix3d(); /** Used to aim the gun */ protected Matrix3d aimGunMat = new Matrix3d(); /** Used to define the ball's direction */ protected Transform3D aimShotXfm = new Transform3D(); /** Used to define the gun's direction */ protected Transform3D aimGunXfm = new Transform3D(); /** * Set up the data for the behaviour. * * @param a1 * Alpha that drives the ball's animation. * @param pi * PositionInterpolator used for the ball. * @param gunRotGrp * TransformGroup that is used to rotate the gun. * @param theBounds * Bounds that define the active region for this behaviour. */ public GunBehaviour(Alpha a1, PositionInterpolator pi, TransformGroup gunRotGrp, Bounds theBounds) { theGunAlpha = a1; theInterpolator = pi; setSchedulingBounds(theBounds); aimXfmGrp = gunRotGrp; } /** * This sets up the criteria for triggering the behaviour. We simple want to * wait for a key to be pressed. */ public void initialize() { theCriterion = new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED); wakeupOn(theCriterion); } /** * This is where the work is done. This identifies which key has been * pressed and acts accordingly: left key cursor rotate left, right cursor * key rotate right, spacebar fire. * * @criteria Enumeration that represents the trigger conditions. */ public void processStimulus(Enumeration criteria) { while (criteria.hasMoreElements()) { WakeupCriterion theCriterion = (WakeupCriterion) criteria.nextElement(); if (theCriterion instanceof WakeupOnAWTEvent) { AWTEvent[] triggers = ((WakeupOnAWTEvent) theCriterion).getAWTEvent(); //Check if it's a keyboard event if (triggers[0] instanceof KeyEvent) { int keyPressed = ((KeyEvent) triggers[0]).getKeyCode(); if (keyPressed == KeyEvent.VK_LEFT) { //It's a left key so move the turret //and the aim of the gun left unless //we're at our maximum angle if (aim < 8) aim += 1; System.out.println("Left " + aim); aimShotMat.rotY(((aim / 32.0) + 0.5) * Math.PI); aimGunMat.rotZ(((aim / -32.0)) * Math.PI); aimShotXfm.setRotation(aimShotMat); aimGunXfm.setRotation(aimGunMat); aimXfmGrp.setTransform(aimGunXfm); theInterpolator.setAxisOfTranslation(aimShotXfm); } else if (keyPressed == KeyEvent.VK_RIGHT) { //It's the right key so do the same but rotate right if (aim > -8) aim -= 1; System.out.println("Right " + aim); aimShotMat.rotY(((aim / 32.0) + 0.5) * Math.PI); aimGunMat.rotZ(((aim / -32.0)) * Math.PI); aimGunXfm.setRotation(aimGunMat); aimShotXfm.setRotation(aimShotMat); aimXfmGrp.setTransform(aimGunXfm); theInterpolator.setAxisOfTranslation(aimShotXfm); } else if (keyPressed == KeyEvent.VK_SPACE) { //It's the spacebar so reset the start time //of the ball's animation theGunAlpha.setStartTime(System.currentTimeMillis()); } } } } wakeupOn(theCriterion); } }