AvatarTest.java Source code

Java tutorial

Introduction

Here is the source code for AvatarTest.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.
    
 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.
 **************************************************************/

import java.applet.Applet;
import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.event.KeyEvent;
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.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.GeometryArray;
import javax.media.j3d.Group;
import javax.media.j3d.MediaContainer;
import javax.media.j3d.Node;
import javax.media.j3d.PhysicalEnvironment;
import javax.media.j3d.PointSound;
import javax.media.j3d.PositionInterpolator;
import javax.media.j3d.QuadArray;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Sound;
import javax.media.j3d.Texture;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
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.WakeupOr;
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.behaviors.interpolators.RotPosScaleTCBSplinePathInterpolator;
import com.sun.j3d.utils.behaviors.interpolators.TCBKeyFrame;
import com.sun.j3d.utils.geometry.Box;
import com.sun.j3d.utils.geometry.Primitive;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.image.TextureLoader;
import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.universe.ViewerAvatar;

/**
 * Displays a simple driving type game scene, using texture mapped cubes. It
 * assigns an Avatar to the viewer and incorporates simple sounds and collision
 * detection/notification/
 * <p>
 * This example does not use the Java3dApplet base class but is based on a
 * SimpleUniverse construction instead. that way we can illustrate the
 * setPlatformGeometry call.
 */
public class AvatarTest extends Applet {

    public BranchGroup createSceneGraph() {
        BranchGroup bg = new BranchGroup();

        TransformGroup tgRoot = addBehaviors(bg);

        createBuildings(tgRoot);
        createRoad(tgRoot);
        createLand(tgRoot);
        createCars(tgRoot);
        createBackground(bg);

        return bg;
    }

    public void createBackground(Group bg) {
        // add the sky backdrop
        Background back = new Background();
        back.setApplicationBounds(getBoundingSphere());
        bg.addChild(back);

        BranchGroup bgGeometry = new BranchGroup();

        // create an appearance and assign the texture image
        Appearance app = new Appearance();
        Texture tex = new TextureLoader("back.jpg", this).getTexture();
        app.setTexture(tex);

        Sphere sphere = new Sphere(1.0f, Primitive.GENERATE_TEXTURE_COORDS | Primitive.GENERATE_NORMALS_INWARD,
                app);

        bgGeometry.addChild(sphere);
        back.setGeometry(bgGeometry);
    }

    public Group createLand(Group g) {
        Land land = new Land(this, g, ComplexObject.GEOMETRY | ComplexObject.TEXTURE);
        return land.createObject(new Appearance(), new Vector3d(0, 0, 0), new Vector3d(1, 1, 1), "land.jpg", null,
                null);
    }

    public Group createRoad(Group g) {
        Road road = new Road(this, g, ComplexObject.GEOMETRY | ComplexObject.TEXTURE);
        return road.createObject(new Appearance(), new Vector3d(0, 0, 0), new Vector3d(1, 1, 1), "road.jpg", null,
                null);
    }

    private float getRandomNumber(float basis, float random) {
        return basis + ((float) Math.random() * random * 2) - (random);
    }

    public Group createBuildings(Group g) {
        BranchGroup bg = new BranchGroup();

        for (int n = (int) Road.ROAD_LENGTH; n < 0; n = n + 10) {
            Building building = new Building(this, bg,
                    ComplexObject.GEOMETRY | ComplexObject.TEXTURE | ComplexObject.COLLISION);

            building.createObject(new Appearance(), new Vector3d(getRandomNumber(-4.0f, 0.25f),
                    getRandomNumber(1.0f, 0.5f), getRandomNumber(n, 0.5f)), new Vector3d(1, 1, 1), "house.jpg",
                    null, null);

            building = new Building(this, bg,
                    ComplexObject.GEOMETRY | ComplexObject.TEXTURE | ComplexObject.COLLISION);

            building.createObject(new Appearance(), new Vector3d(getRandomNumber(4.0f, 0.25f),
                    getRandomNumber(1.0f, 0.5f), getRandomNumber(n, 0.5f)), new Vector3d(1, 1, 1), "house.jpg",
                    null, null);

        }

        g.addChild(bg);

        return bg;
    }

    public Group createCars(Group g) {
        BranchGroup bg = new BranchGroup();

        for (int n = (int) Road.ROAD_LENGTH; n < 0; n = n + 10) {
            Car car = new Car(this, bg, ComplexObject.GEOMETRY | ComplexObject.TEXTURE | ComplexObject.SOUND);

            car.createObject(new Appearance(),
                    new Vector3d(getRandomNumber(0.0f, 2.0f), Car.CAR_HEIGHT / 2.0f, getRandomNumber(n, 5.0f)),
                    new Vector3d(1, 1, 1), "car0.jpg", "car.wav", "collide.wav");
        }

        g.addChild(bg);
        return bg;
    }

    public TransformGroup addBehaviors(Group bgRoot) {
        // Create the transform group node and initialize it to the
        // identity. Enable the TRANSFORM_WRITE capability so that
        // our behavior code can modify it at runtime. Add it to the
        // root of the subgraph.
        TransformGroup objTrans = new TransformGroup();
        objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

        Transform3D zAxis = new Transform3D();
        zAxis.rotY(Math.toRadians(90.0));

        Alpha zoomAlpha = new Alpha(-1, Alpha.INCREASING_ENABLE, 0, 0, 20000, 0, 0, 0, 0, 0);

        PositionInterpolator posInt = new PositionInterpolator(zoomAlpha, objTrans, zAxis, 0, -160);

        posInt.setSchedulingBounds(getBoundingSphere());
        objTrans.addChild(posInt);

        bgRoot.addChild(objTrans);

        return objTrans;
    }

    BoundingSphere getBoundingSphere() {
        return new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 400.0);
    }

    ViewerAvatar createAvatar() {
        ViewerAvatar va = new ViewerAvatar();
        TransformGroup tg = new TransformGroup();

        Car car = new Car(this, tg, ComplexObject.GEOMETRY | ComplexObject.TEXTURE | ComplexObject.COLLISION
                | ComplexObject.COLLISION_SOUND);

        car.createObject(new Appearance(), new Vector3d(0, -0.3, -0.3), new Vector3d(0.3, 0.3, 1), "platform.jpg",
                null, "collide.wav");

        tg.addChild(car);
        va.addChild(tg);

        return va;
    }

    public static void main(String[] args) {
        AvatarTest avatarTest = new AvatarTest();

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

        PhysicalEnvironment physicalEnv = u.getViewer().getPhysicalEnvironment();

        TransformGroup tg = u.getViewer().getViewingPlatform().getViewPlatformTransform();

        Transform3D t3d = new Transform3D();
        t3d.set(new Vector3f(0, 0.5f, 0));
        tg.setTransform(t3d);

        CarSteering keys = new CarSteering(tg);
        keys.setSchedulingBounds(avatarTest.getBoundingSphere());

        u.getViewer().setAvatar(avatarTest.createAvatar());

        if (physicalEnv != null) {
            JavaSoundMixer javaSoundMixer = new JavaSoundMixer(physicalEnv);

            if (javaSoundMixer == null)
                System.out.println("Unable to create AudioDevice.");

            javaSoundMixer.initialize();
        }

        // Add everthing to the scene graph - it will now be displayed.
        BranchGroup scene = avatarTest.createSceneGraph();
        scene.addChild(keys);
        //      Java3dTree j3dTree = new Java3dTree();

        //   j3dTree.recursiveApplyCapability(scene);

        u.addBranchGraph(scene);

        //j3dTree.updateNodes(u);

        u.getViewingPlatform().getViewPlatform().setActivationRadius(2);
    }
}

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

class Building extends ComplexObject {
    private final float BUILDING_WIDTH = 1.0f;

    private final float BUILDING_LENGTH = 1.0f;

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

    private float getRandomNumber(float basis, float random) {
        return basis + ((float) Math.random() * random * 2) - (random);
    }

    protected Group createGeometryGroup(Appearance app, Vector3d position, Vector3d scale, String szTextureFile,
            String szSoundFile) {
        int nPrimFlags = 0;

        if ((m_nFlags & ComplexObject.TEXTURE) == ComplexObject.TEXTURE) {
            nPrimFlags |= Primitive.GENERATE_TEXTURE_COORDS;
            setTexture(app, szTextureFile);
        }

        return new Box(getRandomNumber(BUILDING_WIDTH, 0.25f), (float) position.y,
                getRandomNumber(BUILDING_LENGTH, 0.15f), nPrimFlags, app);
    }
}

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

class Land extends ComplexObject {
    private final float LAND_WIDTH = 100.0f;

    private final float LAND_HEIGHT = 0.0f;

    private final float LAND_LENGTH = -200.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) {
        QuadArray quadArray = new QuadArray(4, GeometryArray.COORDINATES | GeometryArray.TEXTURE_COORDINATE_2);

        float[] coordArray = { -LAND_WIDTH, LAND_HEIGHT, 0, LAND_WIDTH, LAND_HEIGHT, 0, LAND_WIDTH, LAND_HEIGHT,
                LAND_LENGTH, -LAND_WIDTH, LAND_HEIGHT, LAND_LENGTH };

        float[] texArray = { 0, 0, 1, 0, 1, 1, 0, 1 };

        quadArray.setCoordinates(0, coordArray, 0, 4);

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

        Shape3D sh = new Shape3D(quadArray, app);

        BranchGroup bg = new BranchGroup();
        bg.addChild(sh);
        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_BOUNDS);
        wakeupTwo = new WakeupOnCollisionExit(node, WakeupOnCollisionExit.USE_BOUNDS);

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

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

class CarSteering extends Behavior {
    private WakeupOnAWTEvent wakeupOne = null;

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

    private WakeupCondition wakeupCondition = null;

    private final float TRANSLATE_LEFT = -0.05f;

    private final float TRANSLATE_RIGHT = 0.05f;

    TransformGroup m_TransformGroup = null;

    public CarSteering(TransformGroup tg) {
        m_TransformGroup = tg;

        try {
            m_TransformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
            m_TransformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        } catch (Exception e) {
        }

        wakeupOne = new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED);
        wakeupArray[0] = wakeupOne;
        wakeupCondition = new WakeupOr(wakeupArray);
    }

    /**
     * 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) {
        WakeupOnAWTEvent ev;
        WakeupCriterion genericEvt;
        AWTEvent[] events;

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

            if (genericEvt instanceof WakeupOnAWTEvent) {
                ev = (WakeupOnAWTEvent) genericEvt;
                events = ev.getAWTEvent();
                processAWTEvent(events);
            }
        }

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

    /**
     * Process a keyboard event
     */
    private void processAWTEvent(AWTEvent[] events) {
        for (int n = 0; n < events.length; n++) {
            if (events[n] instanceof KeyEvent) {
                KeyEvent eventKey = (KeyEvent) events[n];

                if (eventKey.getID() == KeyEvent.KEY_PRESSED) {
                    int keyCode = eventKey.getKeyCode();
                    int keyChar = eventKey.getKeyChar();

                    Vector3f translate = new Vector3f();

                    Transform3D t3d = new Transform3D();
                    m_TransformGroup.getTransform(t3d);
                    t3d.get(translate);

                    switch (keyCode) {
                    case KeyEvent.VK_LEFT:
                        translate.x += TRANSLATE_LEFT;
                        break;

                    case KeyEvent.VK_RIGHT:
                        translate.x += TRANSLATE_RIGHT;
                        break;
                    }

                    // System.out.println( "Steering: " + translate.x );
                    translate.y = 0.5f;

                    t3d.setTranslation(translate);
                    m_TransformGroup.setTransform(t3d);
                }
            }
        }
    }
}

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

class Car extends ComplexObject {
    public static final float CAR_WIDTH = 0.2f;

    public static final float CAR_HEIGHT = 0.2f;

    public static final float CAR_LENGTH = 0.6f;

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

    private float getRandomNumber(float basis, float random) {
        return basis + ((float) Math.random() * random * 2) - (random);
    }

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

    protected Group createGeometryGroup(Appearance app, Vector3d position, Vector3d scale, String szTextureFile,
            String szSoundFile) {
        int nPrimFlags = 0;

        if ((m_nFlags & ComplexObject.TEXTURE) == ComplexObject.TEXTURE) {
            nPrimFlags |= Primitive.GENERATE_TEXTURE_COORDS;
            setTexture(app, szTextureFile);
        }

        return new Box(CAR_WIDTH, (float) position.y, getRandomNumber(CAR_LENGTH, 0.01f), nPrimFlags, app);
    }
}

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

class Road extends ComplexObject {
    public static final float ROAD_WIDTH = 3.0f;

    public static final float ROAD_HEIGHT = 0.01f;

    public static final float ROAD_LENGTH = -200.0f;

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

    protected Group createGeometryGroup(Appearance app, Vector3d position, Vector3d scale, String szTextureFile,
            String szSoundFile) {
        // creates a segment of road 200 x 2

        QuadArray quadArray = new QuadArray(4, GeometryArray.COORDINATES | GeometryArray.TEXTURE_COORDINATE_2);

        float[] coordArray = { -ROAD_WIDTH, ROAD_HEIGHT, 0, ROAD_WIDTH, ROAD_HEIGHT, 0, ROAD_WIDTH, ROAD_HEIGHT,
                ROAD_LENGTH, -ROAD_WIDTH, ROAD_HEIGHT, ROAD_LENGTH };

        float[] texArray = { 0, 0, 1, 0, 1, 1, 0, 1 };

        quadArray.setCoordinates(0, coordArray, 0, 4);

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

        Shape3D sh = new Shape3D(quadArray, app);

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

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

//*****************************************************************************
/**
 * 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;
    }
}