KeyNavigateTest.java Source code

Java tutorial

Introduction

Here is the source code for KeyNavigateTest.java

Source

/**********************************************************
 Copyright (C) 2001    Daniel Selman
    
 First distributed with the book "Java 3D Programming"
 by Daniel Selman and published by Manning Publications.
 http://manning.com/selman
    
 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation, version 2.
    
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
    
 The license can be found on the WWW at:
 http://www.fsf.org/copyleft/gpl.html
    
 Or by writing to:
 Free Software Foundation, Inc.,
 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    
 Author can be contacted at:
 Daniel Selman: daniel@selman.org
    
 If you make changes you think others would like please
 contact Daniel Selman.
 **************************************************************/

import java.applet.Applet;
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.GraphicsConfigTemplate;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.Vector;

import javax.media.j3d.Alpha;
import javax.media.j3d.Appearance;
import javax.media.j3d.AudioDevice;
import javax.media.j3d.Background;
import javax.media.j3d.BackgroundSound;
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.ColoringAttributes;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.GraphicsConfigTemplate3D;
import javax.media.j3d.Group;
import javax.media.j3d.ImageComponent2D;
import javax.media.j3d.Locale;
import javax.media.j3d.MediaContainer;
import javax.media.j3d.Node;
import javax.media.j3d.PhysicalBody;
import javax.media.j3d.PhysicalEnvironment;
import javax.media.j3d.PointSound;
import javax.media.j3d.PolygonAttributes;
import javax.media.j3d.QuadArray;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Sound;
import javax.media.j3d.Texture;
import javax.media.j3d.TextureAttributes;
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.WakeupOnCollisionEntry;
import javax.media.j3d.WakeupOnCollisionExit;
import javax.media.j3d.WakeupOnElapsedTime;
import javax.media.j3d.WakeupOr;
import javax.vecmath.Color3f;
import javax.vecmath.Color4f;
import javax.vecmath.Point2d;
import javax.vecmath.Point2f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Quat4f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;
import javax.vecmath.Vector4f;

import com.sun.j3d.audioengines.javasound.JavaSoundMixer;
import com.sun.j3d.loaders.Scene;
import com.sun.j3d.loaders.objectfile.ObjectFile;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.behaviors.interpolators.RotPosScaleTCBSplinePathInterpolator;
import com.sun.j3d.utils.behaviors.interpolators.TCBKeyFrame;
import com.sun.j3d.utils.geometry.Cone;
import com.sun.j3d.utils.geometry.Primitive;
import com.sun.j3d.utils.image.TextureLoader;

/**
 * Simple DOOM style navigation of a 3D scene using Java 3D. The scene
 * description is loaded from a GIF file where different colors denote objects
 * in the 3D scene. The example includes: simple (manual) collision detection,
 * texture animation, keyboard navigation.
 */
public class KeyNavigateTest extends Java3dApplet implements CollisionDetector {
    private static int m_kWidth = 300;

    private static int m_kHeight = 300;

    private static final String m_szMapName = new String("small_map.gif");

    private float FLOOR_WIDTH = 256;

    private float FLOOR_LENGTH = 256;

    private final int m_ColorWall = new Color(0, 0, 0).getRGB();

    private final int m_ColorGuard = new Color(255, 0, 0).getRGB();

    private final int m_ColorLight = new Color(255, 255, 0).getRGB();

    private final int m_ColorBookcase = new Color(0, 255, 0).getRGB();

    private final int m_ColorWater = new Color(0, 0, 255).getRGB();

    private Vector3d m_MapSquareSize = null;

    private Appearance m_WallAppearance = null;

    private Appearance m_GuardAppearance = null;

    private Appearance m_BookcaseAppearance = null;

    private Appearance m_WaterAppearance = null;

    private BufferedImage m_MapImage = null;

    private final double m_kFloorLevel = -20;

    private final double m_kCeilingHeight = 50;

    private final double m_kCeilingLevel = m_kFloorLevel + m_kCeilingHeight;

    private Vector3d m_Translation = new Vector3d();

    public KeyNavigateTest() {
        initJava3d();
    }

    protected double getScale() {
        return 0.05;
    }

    protected int getCanvas3dWidth(Canvas3D c3d) {
        return m_kWidth - 10;
    }

    protected int getCanvas3dHeight(Canvas3D c3d) {
        return m_kHeight - 10;
    }

    // edit the positions of the clipping
    // planes so we don't clip on the front
    // plane prematurely
    protected double getBackClipDistance() {
        return 20.0;
    }

    protected double getFrontClipDistance() {
        return 0.05;
    }

    // we create 2 TransformGroups above the ViewPlatform:
    // the first merely applies a scale, while the second is
    // manipulated using the KeyBehavior
    public TransformGroup[] getViewTransformGroupArray() {
        TransformGroup[] tgArray = new TransformGroup[2];
        tgArray[0] = new TransformGroup();
        tgArray[1] = new TransformGroup();

        Transform3D t3d = new Transform3D();
        t3d.setScale(getScale());
        t3d.invert();
        tgArray[0].setTransform(t3d);

        // ensure the Behavior can access the TG
        tgArray[1].setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

        // create the KeyBehavior and attach
        KeyCollisionBehavior keyBehavior = new KeyCollisionBehavior(tgArray[1], this);
        keyBehavior.setSchedulingBounds(getApplicationBounds());
        keyBehavior.setMovementRate(0.7);
        tgArray[1].addChild(keyBehavior);

        return tgArray;
    }

    protected BranchGroup createSceneBranchGroup() {
        BranchGroup objRoot = super.createSceneBranchGroup();

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

        createMap(objTrans);
        createFloor(objTrans);
        createCeiling(objTrans);

        objRoot.addChild(objTrans);

        return objRoot;
    }

    public Group createFloor(Group g) {
        System.out.println("Creating floor");

        Group floorGroup = new Group();
        Land floorTile = null;

        // use a shared Appearance so we only store 1 copy of the texture
        Appearance app = new Appearance();
        g.addChild(floorGroup);

        final double kNumTiles = 6;

        for (double x = -FLOOR_WIDTH + FLOOR_WIDTH / (2 * kNumTiles); x < FLOOR_WIDTH; x = x
                + FLOOR_WIDTH / kNumTiles) {
            for (double z = -FLOOR_LENGTH + FLOOR_LENGTH / (2 * kNumTiles); z < FLOOR_LENGTH; z = z
                    + FLOOR_LENGTH / kNumTiles) {
                floorTile = new Land(this, g, ComplexObject.GEOMETRY | ComplexObject.TEXTURE);
                floorTile.createObject(app, new Vector3d(x, m_kFloorLevel, z),
                        new Vector3d(FLOOR_WIDTH / (2 * kNumTiles), 1, FLOOR_LENGTH / (2 * kNumTiles)), "floor.gif",
                        null, null);
            }
        }

        return floorGroup;
    }

    public Group createCeiling(Group g) {
        System.out.println("Creating ceiling");

        Group ceilingGroup = new Group();
        Land ceilingTile = null;

        // because we are technically viewing the ceiling from below
        // we want to switch the normals using a PolygonAttributes.
        Appearance app = new Appearance();
        app.setPolygonAttributes(
                new PolygonAttributes(PolygonAttributes.POLYGON_FILL, PolygonAttributes.CULL_NONE, 0, false));

        g.addChild(ceilingGroup);

        final double kNumTiles = 6;

        for (double x = -FLOOR_WIDTH + FLOOR_WIDTH / (2 * kNumTiles); x < FLOOR_WIDTH; x = x
                + FLOOR_WIDTH / kNumTiles) {
            for (double z = -FLOOR_LENGTH + FLOOR_LENGTH / (2 * kNumTiles); z < FLOOR_LENGTH; z = z
                    + FLOOR_LENGTH / kNumTiles) {
                ceilingTile = new Land(this, g, ComplexObject.GEOMETRY | ComplexObject.TEXTURE);
                ceilingTile.createObject(app, new Vector3d(x, m_kCeilingLevel, z),
                        new Vector3d(FLOOR_WIDTH / (2 * kNumTiles), 1, FLOOR_LENGTH / (2 * kNumTiles)),
                        "ceiling.gif", null, null);
            }
        }

        return ceilingGroup;
    }

    Point3d convertToWorldCoordinate(int nPixelX, int nPixelY) {
        Point3d point3d = new Point3d();
        Vector3d squareSize = getMapSquareSize();

        // range from 0 to 1
        point3d.x = nPixelX * squareSize.x;
        point3d.x -= FLOOR_WIDTH;

        point3d.z = nPixelY * squareSize.z;
        point3d.z -= FLOOR_LENGTH;

        point3d.y = 0;

        return point3d;
    }

    Point3d convertToWorldCoordinatesPixelCenter(int nPixelX, int nPixelY) {
        Point3d point3d = convertToWorldCoordinate(nPixelX, nPixelY);
        Vector3d squareSize = getMapSquareSize();

        point3d.x += squareSize.x / 2;
        point3d.z += squareSize.z / 2;

        return point3d;
    }

    Point2d convertToMapCoordinate(Vector3d worldCoord) {
        Point2d point2d = new Point2d();

        Vector3d squareSize = getMapSquareSize();

        point2d.x = (worldCoord.x + FLOOR_WIDTH) / squareSize.x;
        point2d.y = (worldCoord.z + FLOOR_LENGTH) / squareSize.z;

        return point2d;
    }

    public boolean isCollision(Transform3D t3d, boolean bViewSide) {
        // get the translation
        t3d.get(m_Translation);

        // we need to scale up by the scale that was
        // applied to the root TG on the view side of the scenegraph
        if (bViewSide != false)
            m_Translation.scale(1.0 / getScale());

        Vector3d mapSquareSize = getMapSquareSize();

        // first check that we are still inside the "world"
        if (m_Translation.x < -FLOOR_WIDTH + mapSquareSize.x || m_Translation.x > FLOOR_WIDTH - mapSquareSize.x
                || m_Translation.y < -FLOOR_LENGTH + mapSquareSize.y
                || m_Translation.y > FLOOR_LENGTH - mapSquareSize.y)
            return true;

        if (bViewSide != false)
            // then do a pixel based look up using the map
            return isCollision(m_Translation);

        return false;
    }

    // returns true if the given x,z location in the world corresponds to a wall
    // section
    protected boolean isCollision(Vector3d worldCoord) {
        Point2d point = convertToMapCoordinate(worldCoord);
        int nImageWidth = m_MapImage.getWidth();
        int nImageHeight = m_MapImage.getHeight();

        // outside of image
        if (point.x < 0 || point.x >= nImageWidth || point.y < 0 || point.y >= nImageHeight)
            return true;

        int color = m_MapImage.getRGB((int) point.x, (int) point.y);

        // we can't walk through walls or bookcases
        return (color == m_ColorWall || color == m_ColorBookcase);
    }

    public Group createMap(Group g) {
        System.out.println("Creating map items");

        Group mapGroup = new Group();
        g.addChild(mapGroup);

        Texture tex = new TextureLoader(m_szMapName, this).getTexture();
        m_MapImage = ((ImageComponent2D) tex.getImage(0)).getImage();

        float imageWidth = m_MapImage.getWidth();
        float imageHeight = m_MapImage.getHeight();

        FLOOR_WIDTH = imageWidth * 8;
        FLOOR_LENGTH = imageHeight * 8;

        for (int nPixelX = 1; nPixelX < imageWidth - 1; nPixelX++) {
            for (int nPixelY = 1; nPixelY < imageWidth - 1; nPixelY++)
                createMapItem(mapGroup, nPixelX, nPixelY);

            float percentDone = 100 * (float) nPixelX / (float) (imageWidth - 2);
            System.out.println("   " + (int) (percentDone) + "%");
        }

        createExternalWall(mapGroup);

        return mapGroup;
    }

    void createMapItem(Group mapGroup, int nPixelX, int nPixelY) {
        int color = m_MapImage.getRGB((int) nPixelX, (int) nPixelY);

        if (color == m_ColorWall)
            createWall(mapGroup, nPixelX, nPixelY);

        else if (color == m_ColorGuard)
            createGuard(mapGroup, nPixelX, nPixelY);

        else if (color == m_ColorLight)
            createLight(mapGroup, nPixelX, nPixelY);

        else if (color == m_ColorBookcase)
            createBookcase(mapGroup, nPixelX, nPixelY);

        else if (color == m_ColorWater)
            createWater(mapGroup, nPixelX, nPixelY);
    }

    Vector3d getMapSquareSize() {
        if (m_MapSquareSize == null) {
            double imageWidth = m_MapImage.getWidth();
            double imageHeight = m_MapImage.getHeight();
            m_MapSquareSize = new Vector3d(2 * FLOOR_WIDTH / imageWidth, 0, 2 * FLOOR_LENGTH / imageHeight);
        }

        return m_MapSquareSize;
    }

    void createWall(Group mapGroup, int nPixelX, int nPixelY) {
        Point3d point = convertToWorldCoordinatesPixelCenter(nPixelX, nPixelY);

        if (m_WallAppearance == null)
            m_WallAppearance = new Appearance();

        Vector3d squareSize = getMapSquareSize();

        Cuboid wall = new Cuboid(this, mapGroup, ComplexObject.GEOMETRY | ComplexObject.TEXTURE);
        wall.createObject(m_WallAppearance, new Vector3d(point.x, m_kFloorLevel, point.z),
                new Vector3d(squareSize.x / 2, m_kCeilingHeight / 2, squareSize.z / 2), "wall.gif", null, null);
    }

    void createExternalWall(Group mapGroup) {
        Vector3d squareSize = getMapSquareSize();

        Appearance app = new Appearance();
        app.setColoringAttributes(
                new ColoringAttributes(new Color3f(132f / 255f, 0, 66f / 255f), ColoringAttributes.FASTEST));

        int imageWidth = m_MapImage.getWidth();
        int imageHeight = m_MapImage.getHeight();

        Point3d topLeft = convertToWorldCoordinatesPixelCenter(0, 0);
        Point3d bottomRight = convertToWorldCoordinatesPixelCenter(imageWidth - 1, imageHeight - 1);

        // top
        Cuboid wall = new Cuboid(this, mapGroup, ComplexObject.GEOMETRY);
        wall.createObject(app, new Vector3d(0, m_kFloorLevel, topLeft.z),
                new Vector3d(squareSize.x * imageWidth / 2, m_kCeilingHeight / 2, squareSize.z / 2), null, null,
                null);

        // bottom
        wall = new Cuboid(this, mapGroup, ComplexObject.GEOMETRY);
        wall.createObject(app, new Vector3d(0, m_kFloorLevel, bottomRight.z),
                new Vector3d(squareSize.x * imageWidth / 2, m_kCeilingHeight / 2, squareSize.z / 2), null, null,
                null);

        // left
        wall = new Cuboid(this, mapGroup, ComplexObject.GEOMETRY);
        wall.createObject(app, new Vector3d(topLeft.x, m_kFloorLevel, 0),
                new Vector3d(squareSize.x / 2, m_kCeilingHeight / 2, squareSize.z * imageHeight / 2), null, null,
                null);

        // right
        wall = new Cuboid(this, mapGroup, ComplexObject.GEOMETRY);
        wall.createObject(app, new Vector3d(bottomRight.x, m_kFloorLevel, 0),
                new Vector3d(squareSize.x / 2, m_kCeilingHeight / 2, squareSize.z * imageHeight / 2), null, null,
                null);
    }

    void createGuard(Group mapGroup, int nPixelX, int nPixelY) {
        Point3d point = convertToWorldCoordinatesPixelCenter(nPixelX, nPixelY);

        if (m_GuardAppearance == null)
            m_GuardAppearance = new Appearance();

        Vector3d squareSize = getMapSquareSize();

        Guard guard = new Guard(this, mapGroup, ComplexObject.GEOMETRY, this);
        guard.createObject(m_GuardAppearance, new Vector3d(point.x, (m_kFloorLevel + m_kCeilingLevel) / 2, point.z),
                new Vector3d(1, 1, 1), null, null, null);
    }

    void createLight(Group mapGroup, int nPixelX, int nPixelY) {
        Point3d point = convertToWorldCoordinatesPixelCenter(nPixelX, nPixelY);

        // we do not share an Appearance for lights
        // or they all animate in synch...
        final double lightHeight = m_kCeilingHeight / 1.5;

        Light light = new Light(this, mapGroup, ComplexObject.GEOMETRY | ComplexObject.TEXTURE);
        light.createObject(new Appearance(), new Vector3d(point.x, m_kFloorLevel + lightHeight / 2, point.z),
                new Vector3d(5, lightHeight, 5), "light.gif", null, null);
    }

    void createWater(Group mapGroup, int nPixelX, int nPixelY) {
        Point3d point = convertToWorldCoordinatesPixelCenter(nPixelX, nPixelY);

        if (m_WaterAppearance == null) {
            m_WaterAppearance = new Appearance();
            m_WaterAppearance.setPolygonAttributes(
                    new PolygonAttributes(PolygonAttributes.POLYGON_FILL, PolygonAttributes.CULL_NONE, 0, false));
            m_WaterAppearance
                    .setTransparencyAttributes(new TransparencyAttributes(TransparencyAttributes.BLENDED, 1.0f));
            m_WaterAppearance.setTextureAttributes(new TextureAttributes(TextureAttributes.REPLACE,
                    new Transform3D(), new Color4f(0, 0, 0, 1), TextureAttributes.FASTEST));

        }

        Land water = new Land(this, mapGroup, ComplexObject.GEOMETRY | ComplexObject.TEXTURE);
        water.createObject(m_WaterAppearance, new Vector3d(point.x, m_kFloorLevel + 0.1, point.z),
                new Vector3d(40, 1, 40), "water.gif", null, null);
    }

    void createBookcase(Group mapGroup, int nPixelX, int nPixelY) {
        Point3d point = convertToWorldCoordinatesPixelCenter(nPixelX, nPixelY);

        if (m_BookcaseAppearance == null)
            m_BookcaseAppearance = new Appearance();

        Vector3d squareSize = getMapSquareSize();

        Cuboid bookcase = new Cuboid(this, mapGroup, ComplexObject.GEOMETRY | ComplexObject.TEXTURE);
        bookcase.createObject(m_BookcaseAppearance, new Vector3d(point.x, m_kFloorLevel, point.z),
                new Vector3d(squareSize.x / 2, m_kCeilingHeight / 2.7, squareSize.z / 2), "bookcase.gif", null,
                null);

    }

    public static void main(String[] args) {
        KeyNavigateTest keyTest = new KeyNavigateTest();
        keyTest.saveCommandLineArguments(args);

        new MainFrame(keyTest, m_kWidth, m_kHeight);
    }
}

/*******************************************************************************
 * Copyright (C) 2001 Daniel Selman
 * 
 * First distributed with the book "Java 3D Programming" by Daniel Selman and
 * published by Manning Publications. http://manning.com/selman
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, version 2.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * The license can be found on the WWW at: http://www.fsf.org/copyleft/gpl.html
 * 
 * Or by writing to: Free Software Foundation, Inc., 59 Temple Place - Suite
 * 330, Boston, MA 02111-1307, USA.
 * 
 * Authors can be contacted at: Daniel Selman: daniel@selman.org
 * 
 * If you make changes you think others would like, please contact one of the
 * authors or someone at the www.j3d.org web site.
 ******************************************************************************/

//*****************************************************************************
/**
 * Java3dApplet
 * 
 * Base class for defining a Java 3D applet. Contains some useful methods for
 * defining views and scenegraphs etc.
 * 
 * @author Daniel Selman
 * @version 1.0
 */
//*****************************************************************************

abstract class Java3dApplet extends Applet {
    public static int m_kWidth = 300;

    public static int m_kHeight = 300;

    protected String[] m_szCommandLineArray = null;

    protected VirtualUniverse m_Universe = null;

    protected BranchGroup m_SceneBranchGroup = null;

    protected Bounds m_ApplicationBounds = null;

    //   protected com.tornadolabs.j3dtree.Java3dTree m_Java3dTree = null;

    public Java3dApplet() {
    }

    public boolean isApplet() {
        try {
            System.getProperty("user.dir");
            System.out.println("Running as Application.");
            return false;
        } catch (Exception e) {
        }

        System.out.println("Running as Applet.");
        return true;
    }

    public URL getWorkingDirectory() throws java.net.MalformedURLException {
        URL url = null;

        try {
            File file = new File(System.getProperty("user.dir"));
            System.out.println("Running as Application:");
            System.out.println("   " + file.toURL());
            return file.toURL();
        } catch (Exception e) {
        }

        System.out.println("Running as Applet:");
        System.out.println("   " + getCodeBase());

        return getCodeBase();
    }

    public VirtualUniverse getVirtualUniverse() {
        return m_Universe;
    }

    //public com.tornadolabs.j3dtree.Java3dTree getJ3dTree() {
    //return m_Java3dTree;
    //   }

    public Locale getFirstLocale() {
        java.util.Enumeration e = m_Universe.getAllLocales();

        if (e.hasMoreElements() != false)
            return (Locale) e.nextElement();

        return null;
    }

    protected Bounds getApplicationBounds() {
        if (m_ApplicationBounds == null)
            m_ApplicationBounds = createApplicationBounds();

        return m_ApplicationBounds;
    }

    protected Bounds createApplicationBounds() {
        m_ApplicationBounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 100.0);
        return m_ApplicationBounds;
    }

    protected Background createBackground() {
        Background back = new Background(new Color3f(0.9f, 0.9f, 0.9f));
        back.setApplicationBounds(createApplicationBounds());
        return back;
    }

    public void initJava3d() {
        //   m_Java3dTree = new com.tornadolabs.j3dtree.Java3dTree();
        m_Universe = createVirtualUniverse();

        Locale locale = createLocale(m_Universe);

        BranchGroup sceneBranchGroup = createSceneBranchGroup();

        ViewPlatform vp = createViewPlatform();
        BranchGroup viewBranchGroup = createViewBranchGroup(getViewTransformGroupArray(), vp);

        createView(vp);

        Background background = createBackground();

        if (background != null)
            sceneBranchGroup.addChild(background);

        //      m_Java3dTree.recursiveApplyCapability(sceneBranchGroup);
        //   m_Java3dTree.recursiveApplyCapability(viewBranchGroup);

        locale.addBranchGraph(sceneBranchGroup);
        addViewBranchGroup(locale, viewBranchGroup);

        onDoneInit();
    }

    protected void onDoneInit() {
        //   m_Java3dTree.updateNodes(m_Universe);
    }

    protected double getScale() {
        return 1.0;
    }

    public TransformGroup[] getViewTransformGroupArray() {
        TransformGroup[] tgArray = new TransformGroup[1];
        tgArray[0] = new TransformGroup();

        // move the camera BACK a little...
        // note that we have to invert the matrix as
        // we are moving the viewer
        Transform3D t3d = new Transform3D();
        t3d.setScale(getScale());
        t3d.setTranslation(new Vector3d(0.0, 0.0, -20.0));
        t3d.invert();
        tgArray[0].setTransform(t3d);

        return tgArray;
    }

    protected void addViewBranchGroup(Locale locale, BranchGroup bg) {
        locale.addBranchGraph(bg);
    }

    protected Locale createLocale(VirtualUniverse u) {
        return new Locale(u);
    }

    protected BranchGroup createSceneBranchGroup() {
        m_SceneBranchGroup = new BranchGroup();
        return m_SceneBranchGroup;
    }

    protected View createView(ViewPlatform vp) {
        View view = new View();

        PhysicalBody pb = createPhysicalBody();
        PhysicalEnvironment pe = createPhysicalEnvironment();

        AudioDevice audioDevice = createAudioDevice(pe);

        if (audioDevice != null) {
            pe.setAudioDevice(audioDevice);
            audioDevice.initialize();
        }

        view.setPhysicalEnvironment(pe);
        view.setPhysicalBody(pb);

        if (vp != null)
            view.attachViewPlatform(vp);

        view.setBackClipDistance(getBackClipDistance());
        view.setFrontClipDistance(getFrontClipDistance());

        Canvas3D c3d = createCanvas3D();
        view.addCanvas3D(c3d);
        addCanvas3D(c3d);

        return view;
    }

    protected PhysicalBody createPhysicalBody() {
        return new PhysicalBody();
    }

    protected AudioDevice createAudioDevice(PhysicalEnvironment pe) {
        JavaSoundMixer javaSoundMixer = new JavaSoundMixer(pe);

        if (javaSoundMixer == null)
            System.out.println("create of audiodevice failed");

        return javaSoundMixer;
    }

    protected PhysicalEnvironment createPhysicalEnvironment() {
        return new PhysicalEnvironment();
    }

    protected float getViewPlatformActivationRadius() {
        return 100;
    }

    protected ViewPlatform createViewPlatform() {
        ViewPlatform vp = new ViewPlatform();
        vp.setViewAttachPolicy(View.RELATIVE_TO_FIELD_OF_VIEW);
        vp.setActivationRadius(getViewPlatformActivationRadius());

        return vp;
    }

    protected Canvas3D createCanvas3D() {
        GraphicsConfigTemplate3D gc3D = new GraphicsConfigTemplate3D();
        gc3D.setSceneAntialiasing(GraphicsConfigTemplate.PREFERRED);
        GraphicsDevice gd[] = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();

        Canvas3D c3d = new Canvas3D(gd[0].getBestConfiguration(gc3D));
        c3d.setSize(getCanvas3dWidth(c3d), getCanvas3dHeight(c3d));

        return c3d;
    }

    protected int getCanvas3dWidth(Canvas3D c3d) {
        return m_kWidth;
    }

    protected int getCanvas3dHeight(Canvas3D c3d) {
        return m_kHeight;
    }

    protected double getBackClipDistance() {
        return 100.0;
    }

    protected double getFrontClipDistance() {
        return 1.0;
    }

    protected BranchGroup createViewBranchGroup(TransformGroup[] tgArray, ViewPlatform vp) {
        BranchGroup vpBranchGroup = new BranchGroup();

        if (tgArray != null && tgArray.length > 0) {
            Group parentGroup = vpBranchGroup;
            TransformGroup curTg = null;

            for (int n = 0; n < tgArray.length; n++) {
                curTg = tgArray[n];
                parentGroup.addChild(curTg);
                parentGroup = curTg;
            }

            tgArray[tgArray.length - 1].addChild(vp);
        } else
            vpBranchGroup.addChild(vp);

        return vpBranchGroup;
    }

    protected void addCanvas3D(Canvas3D c3d) {
        setLayout(new BorderLayout());
        add(c3d, BorderLayout.CENTER);
        doLayout();
    }

    protected VirtualUniverse createVirtualUniverse() {
        return new VirtualUniverse();
    }

    protected void saveCommandLineArguments(String[] szArgs) {
        m_szCommandLineArray = szArgs;
    }

    protected String[] getCommandLineArguments() {
        return m_szCommandLineArray;
    }
}

class KeyCollisionBehavior extends KeyBehavior {
    private CollisionChecker m_CollisionChecker = null;

    public KeyCollisionBehavior(TransformGroup tg, CollisionDetector collisionDetector) {
        super(tg);

        m_CollisionChecker = new CollisionChecker(tg, collisionDetector, true);
    }

    protected void updateTransform() {
        if (m_CollisionChecker.isCollision(transform3D) == false)
            transformGroup.setTransform(transform3D);
    }

    // dissallow rotation up or down
    protected void altMove(int keycode) {
    }

    // dissallow moving up or down
    protected void controlMove(int keycode) {
    }
}

class Guard extends ComplexObject {
    private CollisionDetector m_CollisionDetector = null;

    public Guard(Component comp, Group g, int nFlags, CollisionDetector detector) {
        super(comp, g, nFlags);

        m_CollisionDetector = detector;
    }

    protected Group createGeometryGroup(Appearance app, Vector3d position, Vector3d scale, String szTextureFile,
            String szSoundFile) {
        TransformGroup tg = new TransformGroup();
        tg.addChild(new Cone(5, 30));

        attachBehavior(new RandomWalkBehavior(getBehaviorTransformGroup(), m_CollisionDetector));

        return tg;
    }
}

interface CollisionDetector {
    public boolean isCollision(Transform3D t3d, boolean bViewSide);
}

class CollisionChecker {
    CollisionDetector m_Detector = null;

    Transform3D m_ToWorld = null;

    Transform3D m_Transform3D = null;

    Node m_Node = null;

    boolean m_bViewSide = false;

    public CollisionChecker(Node node, CollisionDetector detector, boolean bViewSide) {
        m_Detector = detector;
        m_Node = node;
        m_bViewSide = bViewSide;
    }

    public boolean isCollision(Transform3D t3d) {
        return m_Detector.isCollision(t3d, m_bViewSide);
    }
}

class Light extends ComplexObject {
    private TextureAttributes m_TextureAttributes = null;

    public Light(Component comp, Group g, int nFlags) {
        super(comp, g, nFlags);
    }

    protected Group createGeometryGroup(Appearance app, Vector3d position, Vector3d scale, String szTextureFile,
            String szSoundFile) {
        Group g = new Group();

        app.setPolygonAttributes(
                new PolygonAttributes(PolygonAttributes.POLYGON_FILL, PolygonAttributes.CULL_NONE, 0, false));
        app.setTransparencyAttributes(new TransparencyAttributes(TransparencyAttributes.BLENDED, 1.0f));

        m_TextureAttributes = new TextureAttributes(TextureAttributes.REPLACE, new Transform3D(),
                new Color4f(0, 0, 0, 1), TextureAttributes.FASTEST);
        app.setTextureAttributes(m_TextureAttributes);

        if ((m_nFlags & ComplexObject.TEXTURE) == ComplexObject.TEXTURE)
            setTexture(app, szTextureFile);

        Cone cone = new Cone(1, 1, Primitive.GENERATE_TEXTURE_COORDS, app);

        g.addChild(cone);

        attachBehavior(new TextureAnimationBehavior(m_TextureAttributes));

        return g;
    }
}

class TextureAnimationBehavior extends Behavior {
    // the wake up condition for the behavior
    protected WakeupCondition m_WakeupCondition = null;

    protected Transform3D m_Transform3D = null;

    protected TextureAttributes m_TextureAttributes = null;

    protected double rotY = 0;

    public TextureAnimationBehavior(TextureAttributes texAttribs) {
        m_TextureAttributes = texAttribs;
        m_Transform3D = new Transform3D();
        m_TextureAttributes.setCapability(TextureAttributes.ALLOW_TRANSFORM_WRITE);

        // create the WakeupCriterion for the behavior
        WakeupCriterion criterionArray[] = new WakeupCriterion[1];
        criterionArray[0] = new WakeupOnElapsedTime(300);

        // save the WakeupCriterion for the behavior
        m_WakeupCondition = new WakeupOr(criterionArray);
    }

    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();

            if (wakeUp instanceof WakeupOnElapsedTime) {
                rotY += Utils.getRandomNumber(0.01, 0.01);
                m_Transform3D.rotY(rotY);
                m_TextureAttributes.setTextureTransform(m_Transform3D);
            }
        }

        // assign the next WakeUpCondition, so we are notified again
        wakeupOn(m_WakeupCondition);
    }
}

// this class implements a simple behavior that
// calculates and prints the size of an object
// based on the vertices in its GeometryArray

class RandomWalkBehavior extends Behavior {
    // the wake up condition for the behavior
    protected WakeupCondition m_WakeupCondition = null;

    protected TransformGroup m_TransformGroup = null;

    protected Transform3D m_Transform3D = null;

    protected Vector3d TargetVector3d = null;

    protected Vector3d CurrentVector3d = null;

    private final double m_MovementX = 2;

    private final double m_MovementY = 0;

    private final double m_MovementZ = 2;

    private int m_nFrameCount = 0;

    private CollisionChecker m_CollisionChecker = null;

    public RandomWalkBehavior(TransformGroup tg, CollisionDetector detector) {
        m_TransformGroup = tg;

        m_CollisionChecker = new CollisionChecker(tg, detector, false);

        m_Transform3D = new Transform3D();

        TargetVector3d = new Vector3d();
        CurrentVector3d = new Vector3d();

        // create the WakeupCriterion for the behavior
        WakeupCriterion criterionArray[] = new WakeupCriterion[1];
        criterionArray[0] = new WakeupOnElapsedTime(100);

        // save the WakeupCriterion for the behavior
        m_WakeupCondition = new WakeupOr(criterionArray);
    }

    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();

            if (wakeUp instanceof WakeupOnElapsedTime) {
                if (m_nFrameCount % 100 == 0) {
                    // generate a random direction for movement
                    TargetVector3d.x = m_MovementX * Utils.getRandomNumber(0, 1);
                    TargetVector3d.y = m_MovementY * Utils.getRandomNumber(0, 1);
                    TargetVector3d.z = m_MovementZ * Utils.getRandomNumber(0, 1);
                }

                CurrentVector3d.x += TargetVector3d.x * Utils.getRandomNumber(1, 0.1);
                CurrentVector3d.y += TargetVector3d.y * Utils.getRandomNumber(1, 0.1);
                CurrentVector3d.z += TargetVector3d.z * Utils.getRandomNumber(1, 0.1);

                m_Transform3D.setTranslation(CurrentVector3d);

                if (m_CollisionChecker.isCollision(m_Transform3D) == false)
                    m_TransformGroup.setTransform(m_Transform3D);

                m_nFrameCount++;
            }
        }

        // assign the next WakeUpCondition, so we are notified again
        wakeupOn(m_WakeupCondition);
    }
}

abstract class ComplexObject extends BranchGroup {
    protected Group m_ParentGroup = null;

    protected int m_nFlags = 0;

    protected BackgroundSound m_CollideSound = null;

    protected Component m_Component = null;

    protected TransformGroup m_TransformGroup = null;

    protected TransformGroup m_BehaviorTransformGroup = null;

    public static final int SOUND = 0x001;

    public static final int GEOMETRY = 0x002;

    public static final int TEXTURE = 0x004;

    public static final int COLLISION = 0x008;

    public static final int COLLISION_SOUND = 0x010;

    public ComplexObject(Component comp, Group group, int nFlags) {
        m_ParentGroup = group;
        m_nFlags = nFlags;
        m_Component = comp;
    }

    public Bounds getGeometryBounds() {
        return new BoundingSphere(new Point3d(0, 0, 0), 100);
    }

    private MediaContainer loadSoundFile(String szFile) {
        try {
            File file = new File(System.getProperty("user.dir"));
            URL url = file.toURL();

            URL soundUrl = new URL(url, szFile);
            return new MediaContainer(soundUrl);
        } catch (Exception e) {
            System.err.println("Error could not load sound file: " + e);
            System.exit(-1);
        }

        return null;
    }

    protected void setTexture(Appearance app, String szFile) {
        Texture tex = new TextureLoader(szFile, m_Component).getTexture();
        app.setTexture(tex);
    }

    abstract protected Group createGeometryGroup(Appearance app, Vector3d position, Vector3d scale,
            String szTextureFile, String szSoundFile);

    protected Group loadGeometryGroup(String szModel, Appearance app) throws java.io.FileNotFoundException {
        // load the object file
        Scene scene = null;
        Shape3D shape = null;

        // read in the geometry information from the data file
        ObjectFile objFileloader = new ObjectFile(ObjectFile.RESIZE);

        scene = objFileloader.load(szModel);

        // retrieve the Shape3D object from the scene
        BranchGroup branchGroup = scene.getSceneGroup();
        shape = (Shape3D) branchGroup.getChild(0);
        shape.setAppearance(app);

        return branchGroup;
    }

    protected int getSoundLoop(boolean bCollide) {
        return 1;
    }

    protected float getSoundPriority(boolean bCollide) {
        return 1.0f;
    }

    protected float getSoundInitialGain(boolean bCollide) {
        return 1.0f;
    }

    protected boolean getSoundInitialEnable(boolean bCollide) {
        return true;
    }

    protected boolean getSoundContinuousEnable(boolean bCollide) {
        return false;
    }

    protected Bounds getSoundSchedulingBounds(boolean bCollide) {
        return new BoundingSphere(new Point3d(0, 0, 0), 1.0);
    }

    protected boolean getSoundReleaseEnable(boolean bCollide) {
        return true;
    }

    protected Point2f[] getSoundDistanceGain(boolean bCollide) {
        return null;
    }

    protected void setSoundAttributes(Sound sound, boolean bCollide) {
        sound.setCapability(Sound.ALLOW_ENABLE_WRITE);
        sound.setCapability(Sound.ALLOW_ENABLE_READ);

        sound.setSchedulingBounds(getSoundSchedulingBounds(bCollide));
        sound.setEnable(getSoundInitialEnable(bCollide));
        sound.setLoop(getSoundLoop(bCollide));
        sound.setPriority(getSoundPriority(bCollide));
        sound.setInitialGain(getSoundInitialGain(bCollide));

        sound.setContinuousEnable(getSoundContinuousEnable(bCollide));
        sound.setReleaseEnable(bCollide);

        if (sound instanceof PointSound) {
            PointSound pointSound = (PointSound) sound;
            pointSound.setInitialGain(getSoundInitialGain(bCollide));

            Point2f[] gainArray = getSoundDistanceGain(bCollide);

            if (gainArray != null)
                pointSound.setDistanceGain(gainArray);
        }
    }

    public Group createObject(Appearance app, Vector3d position, Vector3d scale, String szTextureFile,
            String szSoundFile, String szCollisionSound) {
        m_TransformGroup = new TransformGroup();
        Transform3D t3d = new Transform3D();

        t3d.setScale(scale);
        t3d.setTranslation(position);

        m_TransformGroup.setTransform(t3d);

        m_BehaviorTransformGroup = new TransformGroup();

        if ((m_nFlags & GEOMETRY) == GEOMETRY)
            m_BehaviorTransformGroup
                    .addChild(createGeometryGroup(app, position, scale, szTextureFile, szSoundFile));

        if ((m_nFlags & SOUND) == SOUND) {
            MediaContainer media = loadSoundFile(szSoundFile);
            PointSound pointSound = new PointSound(media, getSoundInitialGain(false), 0, 0, 0);
            setSoundAttributes(pointSound, false);
            m_BehaviorTransformGroup.addChild(pointSound);
        }

        if ((m_nFlags & COLLISION) == COLLISION) {
            m_BehaviorTransformGroup.setCapability(Node.ENABLE_COLLISION_REPORTING);
            m_BehaviorTransformGroup.setCollidable(true);
            m_BehaviorTransformGroup.setCollisionBounds(getGeometryBounds());

            if ((m_nFlags & COLLISION_SOUND) == COLLISION_SOUND) {
                MediaContainer collideMedia = loadSoundFile(szCollisionSound);

                m_CollideSound = new BackgroundSound(collideMedia, 1);
                setSoundAttributes(m_CollideSound, true);
                m_TransformGroup.addChild(m_CollideSound);
            }

            CollisionBehavior collision = new CollisionBehavior(m_BehaviorTransformGroup, this);
            collision.setSchedulingBounds(getGeometryBounds());

            m_BehaviorTransformGroup.addChild(collision);
        }

        m_TransformGroup.addChild(m_BehaviorTransformGroup);
        m_ParentGroup.addChild(m_TransformGroup);

        return m_BehaviorTransformGroup;
    }

    public void onCollide(boolean bCollide) {
        System.out.println("Collide: " + bCollide);

        if (m_CollideSound != null && bCollide == true)
            m_CollideSound.setEnable(true);
    }

    public void attachBehavior(Behavior beh) {
        m_BehaviorTransformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        beh.setSchedulingBounds(getGeometryBounds());
        m_BehaviorTransformGroup.addChild(beh);
    }

    public TransformGroup getBehaviorTransformGroup() {
        return m_BehaviorTransformGroup;
    }

    public void attachSplinePathInterpolator(Alpha alpha, Transform3D axis, URL urlKeyframes) {
        // read a spline path definition file and
        // add a Spline Path Interpolator to the TransformGroup for the object.

        m_BehaviorTransformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

        RotPosScaleTCBSplinePathInterpolator splineInterpolator = Utils.createSplinePathInterpolator(alpha,
                m_BehaviorTransformGroup, axis, urlKeyframes);

        if (splineInterpolator != null) {
            splineInterpolator.setSchedulingBounds(getGeometryBounds());
            m_BehaviorTransformGroup.addChild(splineInterpolator);
        } else {
            System.out.println("attachSplinePathInterpolator failed for: " + urlKeyframes);
        }
    }
}

class KeyBehavior extends Behavior {
    protected static final double FAST_SPEED = 20.0;

    protected static final double NORMAL_SPEED = 1.0;

    protected static final double SLOW_SPEED = 0.5;

    protected TransformGroup transformGroup;

    protected Transform3D transform3D;

    protected WakeupCondition keyCriterion;

    private final static double TWO_PI = (2.0 * Math.PI);

    private double rotateXAmount = Math.PI / 16.0;

    private double rotateYAmount = Math.PI / 16.0;

    private double rotateZAmount = Math.PI / 16.0;

    private double moveRate = 5;

    private double speed = NORMAL_SPEED;

    private final double kMoveForwardScale = 1.1;

    private final double kMoveBackwardScale = 0.9;

    private int forwardKey = KeyEvent.VK_UP;

    private int backKey = KeyEvent.VK_DOWN;

    private int leftKey = KeyEvent.VK_LEFT;

    private int rightKey = KeyEvent.VK_RIGHT;

    public KeyBehavior(TransformGroup tg) {
        super();

        transformGroup = 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) {
                    processKeyEvent((KeyEvent) event[i]);
                }
            }
        }

        wakeupOn(keyCriterion);
    }

    protected void processKeyEvent(KeyEvent event) {
        int keycode = event.getKeyCode();

        if (event.isShiftDown())
            speed = FAST_SPEED;
        else
            speed = NORMAL_SPEED;

        if (event.isAltDown())
            altMove(keycode);
        else if (event.isControlDown())
            controlMove(keycode);
        else
            standardMove(keycode);
    }

    //moves forward backward or rotates left right
    private void standardMove(int keycode) {
        if (keycode == forwardKey)
            moveForward();
        else if (keycode == backKey)
            moveBackward();
        else if (keycode == leftKey)
            rotLeft();
        else if (keycode == rightKey)
            rotRight();
    }

    //moves left right, rotate up down
    protected void altMove(int keycode) {
        if (keycode == forwardKey)
            rotUp();
        else if (keycode == backKey)
            rotDown();
        else if (keycode == leftKey)
            rotLeft();
        else if (keycode == rightKey)
            rotRight();
        else if (keycode == leftKey)
            moveLeft();
        else if (keycode == rightKey)
            moveRight();
    }

    //move up down, rot left right
    protected void controlMove(int keycode) {
        if (keycode == forwardKey)
            moveUp();
        else if (keycode == backKey)
            moveDown();
        else if (keycode == leftKey)
            rollLeft();
        else if (keycode == rightKey)
            rollRight();
    }

    private void moveForward() {
        doMove(new Vector3d(0.0, 0.0, kMoveForwardScale * speed));
    }

    private void moveBackward() {
        doMove(new Vector3d(0.0, 0.0, -kMoveBackwardScale * speed));
    }

    private void moveLeft() {
        doMove(new Vector3d(-getMovementRate(), 0.0, 0.0));
    }

    private void moveRight() {
        doMove(new Vector3d(getMovementRate(), 0.0, 0.0));
    }

    private void moveUp() {
        doMove(new Vector3d(0.0, getMovementRate(), 0.0));
    }

    private void moveDown() {
        doMove(new Vector3d(0.0, -getMovementRate(), 0.0));
    }

    protected void rotRight() {
        doRotateY(getRotateRightAmount());
    }

    protected void rotUp() {
        doRotateX(getRotateUpAmount());
    }

    protected void rotLeft() {
        doRotateY(getRotateLeftAmount());
    }

    protected void rotDown() {
        doRotateX(getRotateDownAmount());
    }

    protected void rollLeft() {
        doRotateZ(getRollLeftAmount());
    }

    protected void rollRight() {
        doRotateZ(getRollRightAmount());
    }

    protected void updateTransform() {
        transformGroup.setTransform(transform3D);
    }

    protected void doRotateY(double radians) {
        transformGroup.getTransform(transform3D);
        Transform3D toMove = new Transform3D();
        toMove.rotY(radians);
        transform3D.mul(toMove);
        updateTransform();
    }

    protected void doRotateX(double radians) {
        transformGroup.getTransform(transform3D);
        Transform3D toMove = new Transform3D();
        toMove.rotX(radians);
        transform3D.mul(toMove);
        updateTransform();
    }

    protected void doRotateZ(double radians) {
        transformGroup.getTransform(transform3D);
        Transform3D toMove = new Transform3D();
        toMove.rotZ(radians);
        transform3D.mul(toMove);
        updateTransform();
    }

    protected void doMove(Vector3d theMove) {
        transformGroup.getTransform(transform3D);
        Transform3D toMove = new Transform3D();
        toMove.setTranslation(theMove);
        transform3D.mul(toMove);
        updateTransform();
    }

    protected double getMovementRate() {
        return moveRate * speed;
    }

    protected double getRollLeftAmount() {
        return rotateZAmount * speed;
    }

    protected double getRollRightAmount() {
        return -rotateZAmount * speed;
    }

    protected double getRotateUpAmount() {
        return rotateYAmount * speed;
    }

    protected double getRotateDownAmount() {
        return -rotateYAmount * speed;
    }

    protected double getRotateLeftAmount() {
        return rotateYAmount * speed;
    }

    protected double getRotateRightAmount() {
        return -rotateYAmount * speed;
    }

    public void setRotateXAmount(double radians) {
        rotateXAmount = radians;
    }

    public void setRotateYAmount(double radians) {
        rotateYAmount = radians;
    }

    public void setRotateZAmount(double radians) {
        rotateZAmount = radians;
    }

    public void setMovementRate(double meters) {
        moveRate = meters; // Travel rate in meters/frame
    }

    public void setForwardKey(int key) {
        forwardKey = key;
    }

    public void setBackKey(int key) {
        backKey = key;
    }

    public void setLeftKey(int key) {
        leftKey = key;
    }
}

//creates a 2x2x2 cuboid with its base at y=0

class Cuboid extends ComplexObject {
    public Cuboid(Component comp, Group g, int nFlags) {
        super(comp, g, nFlags);
    }

    protected Group createGeometryGroup(Appearance app, Vector3d position, Vector3d scale, String szTextureFile,
            String szSoundFile) {
        int nFlags = GeometryArray.COORDINATES | GeometryArray.NORMALS;

        if ((m_nFlags & TEXTURE) == TEXTURE)
            nFlags |= GeometryArray.TEXTURE_COORDINATE_2;

        QuadArray quadArray = new QuadArray(24, nFlags);

        quadArray.setCoordinates(0, verts, 0, 24);

        for (int n = 0; n < 24; n++)
            quadArray.setNormal(n, normals[n / 4]);

        if ((m_nFlags & TEXTURE) == TEXTURE) {
            quadArray.setTextureCoordinates(0, 0, tcoords, 0, 24);
            setTexture(app, szTextureFile);
        }

        Shape3D shape = new Shape3D(quadArray, app);

        BranchGroup bg = new BranchGroup();
        bg.addChild(shape);
        return bg;
    }

    private static final float[] verts = {
            // front face
            1.0f, 0.0f, 1.0f, 1.0f, 2.0f, 1.0f, -1.0f, 2.0f, 1.0f, -1.0f, 0.0f, 1.0f,
            // back face
            -1.0f, 0.0f, -1.0f, -1.0f, 2.0f, -1.0f, 1.0f, 2.0f, -1.0f, 1.0f, 0.0f, -1.0f,
            // right face
            1.0f, 0.0f, -1.0f, 1.0f, 2.0f, -1.0f, 1.0f, 2.0f, 1.0f, 1.0f, 0.0f, 1.0f,
            // left face
            -1.0f, 0.0f, 1.0f, -1.0f, 2.0f, 1.0f, -1.0f, 2.0f, -1.0f, -1.0f, 0.0f, -1.0f,
            // top face
            1.0f, 2.0f, 1.0f, 1.0f, 2.0f, -1.0f, -1.0f, 2.0f, -1.0f, -1.0f, 2.0f, 1.0f,
            // bottom face
            -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, };

    private static final float[] tcoords = {
            // front
            1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,
            // back
            1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,
            //right
            1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,
            // left
            1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,
            // top
            1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,
            // bottom
            0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f };

    private static final Vector3f[] normals = { new Vector3f(0.0f, 0.0f, 1.0f), // front
            // face
            new Vector3f(0.0f, 0.0f, -1.0f), // back face
            new Vector3f(1.0f, 0.0f, 0.0f), // right face
            new Vector3f(-1.0f, 0.0f, 0.0f), // left face
            new Vector3f(0.0f, 1.0f, 0.0f), // top face
            new Vector3f(0.0f, -1.0f, 0.0f), // bottom face
    };
}

//*****************************************************************************
/**
 * Utils
 * 
 * @author Daniel Selman
 * @version 1.0
 */
//*****************************************************************************

class Utils {
    // convert an angular rotation about an axis to a Quaternion
    static Quat4f createQuaternionFromAxisAndAngle(Vector3d axis, double angle) {
        double sin_a = Math.sin(angle / 2);
        double cos_a = Math.cos(angle / 2);

        // use a vector so we can call normalize
        Vector4f q = new Vector4f();

        q.x = (float) (axis.x * sin_a);
        q.y = (float) (axis.y * sin_a);
        q.z = (float) (axis.z * sin_a);
        q.w = (float) cos_a;

        // It is necessary to normalise the quaternion
        // in case any values are very close to zero.
        q.normalize();

        // convert to a Quat4f and return
        return new Quat4f(q);
    }

    // convert three rotations about the Euler axes to a Quaternion
    static Quat4f createQuaternionFromEuler(double angleX, double angleY, double angleZ) {
        // simply call createQuaternionFromAxisAndAngle
        // for each axis and multiply the results
        Quat4f qx = createQuaternionFromAxisAndAngle(new Vector3d(1, 0, 0), angleX);
        Quat4f qy = createQuaternionFromAxisAndAngle(new Vector3d(0, 1, 0), angleY);
        Quat4f qz = createQuaternionFromAxisAndAngle(new Vector3d(0, 0, 1), angleZ);

        // qx = qx * qy
        qx.mul(qy);

        // qx = qx * qz
        qx.mul(qz);

        return qx;
    }

    static public double getRandomNumber(double basis, double random) {
        return basis + ((float) Math.random() * random * 2f) - (random);
    }

    static public double getRandomNumber(double basis, double random, double scale) {
        double value = basis + ((float) Math.random() * random * 2f) - (random);
        return value * scale;
    }

    static public StringBuffer readFile(URL urlFile) {
        /*   allocate a temporary buffer to store the input file*/
        StringBuffer szBufferData = new StringBuffer();
        Vector keyFramesVector = new Vector();

        try {
            InputStream inputStream = urlFile.openStream();

            int nChar = 0;

            // read the entire file into the StringBuffer
            while (true) {
                nChar = inputStream.read();

                /* if we have not hit the end of file
                 add the character to the StringBuffer
                */
                if (nChar != -1)
                    szBufferData.append((char) nChar);
                else
                    // EOF
                    break;
            }

            inputStream.close();
        } catch (Exception e) {
            System.err.println(e.toString());
            return null;
        }

        return szBufferData;
    }

    static public RotPosScaleTCBSplinePathInterpolator createSplinePathInterpolator(Alpha alpha, TransformGroup tg,
            Transform3D axis, URL urlKeyframes) {
        TCBKeyFrame[] keyFrames = readKeyFrames(urlKeyframes);

        if (keyFrames != null)
            return new RotPosScaleTCBSplinePathInterpolator(alpha, tg, axis, keyFrames);

        return null;
    }

    static public TCBKeyFrame[] readKeyFrames(URL urlKeyframes) {
        StringBuffer szBufferData = readFile(urlKeyframes);

        if (szBufferData == null)
            return null;

        Vector keyFramesVector = new Vector();

        // create a tokenizer to tokenize the input file at whitespace
        java.util.StringTokenizer tokenizer = new java.util.StringTokenizer(szBufferData.toString());

        // each keyframe is defined as follows
        // - knot (0 >= k <= 1)
        // - position (x,y,z)
        // - rotation (rx,ry,rz)
        // - scale (x,y,z)

        // - tension (-1 >= t <= 1)
        // - continuity (-1 >= c <= 1)
        // - bias (-1 >= b <= 1)
        // - linear (int - 0 or 1)

        while (true) {
            try {
                float knot = Float.parseFloat(tokenizer.nextToken());

                float posX = Float.parseFloat(tokenizer.nextToken());
                float posY = Float.parseFloat(tokenizer.nextToken());
                float posZ = Float.parseFloat(tokenizer.nextToken());

                float rotX = Float.parseFloat(tokenizer.nextToken());
                float rotY = Float.parseFloat(tokenizer.nextToken());
                float rotZ = Float.parseFloat(tokenizer.nextToken());

                float scaleX = Float.parseFloat(tokenizer.nextToken());
                float scaleY = Float.parseFloat(tokenizer.nextToken());
                float scaleZ = Float.parseFloat(tokenizer.nextToken());

                float tension = Float.parseFloat(tokenizer.nextToken());
                float continuity = Float.parseFloat(tokenizer.nextToken());
                float bias = Float.parseFloat(tokenizer.nextToken());

                int linear = Integer.parseInt(tokenizer.nextToken());

                TCBKeyFrame keyframe = new TCBKeyFrame(knot, linear, new Point3f(posX, posY, posZ),
                        createQuaternionFromEuler(rotX, rotY, rotZ), new Point3f(scaleX, scaleY, scaleZ), tension,
                        continuity, bias);

                keyFramesVector.add(keyframe);
            } catch (Exception e) {
                break;
            }
        }

        // create the return structure and populate
        TCBKeyFrame[] keysReturn = new TCBKeyFrame[keyFramesVector.size()];

        for (int n = 0; n < keysReturn.length; n++)
            keysReturn[n] = (TCBKeyFrame) keyFramesVector.get(n);

        // return the array
        return keysReturn;
    }
}

class Land extends ComplexObject {
    public static final float WIDTH = 1.0f;

    public static final float LENGTH = 1.0f;

    public static final float HEIGHT = 0.0f;

    public Land(Component comp, Group g, int nFlags) {
        super(comp, g, nFlags);
    }

    protected Group createGeometryGroup(Appearance app, Vector3d position, Vector3d scale, String szTextureFile,
            String szSoundFile) {
        int nFlags = GeometryArray.COORDINATES | GeometryArray.NORMALS;

        if ((m_nFlags & TEXTURE) == TEXTURE)
            nFlags |= GeometryArray.TEXTURE_COORDINATE_2;

        QuadArray quadArray = new QuadArray(4, nFlags);

        float[] coordArray = { -WIDTH, HEIGHT, LENGTH, WIDTH, HEIGHT, LENGTH, WIDTH, HEIGHT, -LENGTH, -WIDTH,
                HEIGHT, -LENGTH };

        quadArray.setCoordinates(0, coordArray, 0, coordArray.length / 3);

        for (int n = 0; n < coordArray.length / 3; n++)
            quadArray.setNormal(n, new Vector3f(0, 1, 0));

        if ((m_nFlags & TEXTURE) == TEXTURE) {
            float[] texArray = { 0, 0, 1, 0, 1, 1, 0, 1 };

            quadArray.setTextureCoordinates(0, 0, texArray, 0, coordArray.length / 3);
            setTexture(app, szTextureFile);
        }

        BranchGroup bg = new BranchGroup();
        Shape3D shape = new Shape3D(quadArray, app);
        bg.addChild(shape);

        return bg;
    }
}

/**
 * This class is a simple behavior that invokes the KeyNavigator to modify the
 * view platform transform.
 */

class CollisionBehavior extends Behavior {
    private WakeupOnCollisionEntry wakeupOne = null;

    private WakeupOnCollisionExit wakeupTwo = null;

    private WakeupCriterion[] wakeupArray = new WakeupCriterion[2];

    private WakeupCondition wakeupCondition = null;

    private ComplexObject m_Owner = null;

    public CollisionBehavior(Node node, ComplexObject owner) {
        wakeupOne = new WakeupOnCollisionEntry(node, WakeupOnCollisionEntry.USE_GEOMETRY);
        wakeupTwo = new WakeupOnCollisionExit(node, WakeupOnCollisionExit.USE_GEOMETRY);

        wakeupArray[0] = wakeupOne;
        wakeupArray[1] = wakeupTwo;

        wakeupCondition = new WakeupOr(wakeupArray);

        m_Owner = owner;
    }

    /**
     * Override Behavior's initialize method to setup 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) {
        WakeupCriterion genericEvt;

        while (criteria.hasMoreElements()) {
            genericEvt = (WakeupCriterion) criteria.nextElement();

            if (genericEvt instanceof WakeupOnCollisionEntry) {
                m_Owner.onCollide(true);
            } else if (genericEvt instanceof WakeupOnCollisionExit) {
                m_Owner.onCollide(false);
            }
        }

        // Set wakeup criteria for next time
        wakeupOn(wakeupCondition);
    }
}