MouseNavigateTest.java Source code

Java tutorial

Introduction

Here is the source code for MouseNavigateTest.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.BorderLayout;
import java.awt.GraphicsConfigTemplate;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Label;
import java.awt.Panel;
import java.awt.TextField;
import java.awt.event.MouseEvent;
import java.io.File;
import java.net.URL;
import java.util.Enumeration;

import javax.media.j3d.Appearance;
import javax.media.j3d.AudioDevice;
import javax.media.j3d.Background;
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.GraphicsConfigTemplate3D;
import javax.media.j3d.Group;
import javax.media.j3d.Locale;
import javax.media.j3d.PhysicalBody;
import javax.media.j3d.PhysicalEnvironment;
import javax.media.j3d.PolygonAttributes;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.View;
import javax.media.j3d.ViewPlatform;
import javax.media.j3d.VirtualUniverse;
import javax.media.j3d.WakeupCriterion;
import javax.media.j3d.WakeupOnAWTEvent;
import javax.media.j3d.WakeupOr;
import javax.vecmath.Color3f;
import javax.vecmath.Matrix3d;
import javax.vecmath.Matrix4d;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

import com.sun.j3d.audioengines.javasound.JavaSoundMixer;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.geometry.ColorCube;

/**
 * This example illustrates the mouse behaviors defined in the
 * org.selman.java3d.book package. These improved mouse behaviors: - handle
 * TransformGroups above the target TG properly <br>- can be applied to any
 * object, not just TGs <br>- support interface reporting to give easy feedback
 * on all manipulations <br>- better motion speed control <br>- range
 * validation to clamp scales and translation between limits <br>
 */
public class MouseNavigateTest extends Java3dApplet
        implements ScaleChangeListener, RotationChangeListener, TranslationChangeListener {
    private static int m_kWidth = 300;

    private static int m_kHeight = 400;

    // create some UI to provide feedback on all mouse
    // manipulations
    Label m_RotationLabel = null;

    TextField m_RotationFieldX = null;

    TextField m_RotationFieldY = null;

    TextField m_RotationFieldZ = null;

    Label m_TranslationLabel = null;

    TextField m_TranslationFieldX = null;

    TextField m_TranslationFieldY = null;

    TextField m_TranslationFieldZ = null;

    Label m_ScaleLabel = null;

    TextField m_ScaleFieldZ = null;

    TextField m_ScaleFieldY = null;

    TextField m_ScaleFieldX = null;

    public MouseNavigateTest() {
        initJava3d();
    }

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

        Panel controlPanel = new Panel();

        // add the UI to the frame
        m_RotationLabel = new Label("Rotation: ");
        m_RotationFieldX = new TextField("0.00");
        m_RotationFieldY = new TextField("0.00");
        m_RotationFieldZ = new TextField("0.00");
        controlPanel.add(m_RotationLabel);
        controlPanel.add(m_RotationFieldX);
        controlPanel.add(m_RotationFieldY);
        controlPanel.add(m_RotationFieldZ);

        m_TranslationLabel = new Label("Translation: ");
        m_TranslationFieldX = new TextField("0.00");
        m_TranslationFieldY = new TextField("0.00");
        m_TranslationFieldZ = new TextField("0.00");
        controlPanel.add(m_TranslationLabel);
        controlPanel.add(m_TranslationFieldX);
        controlPanel.add(m_TranslationFieldY);
        controlPanel.add(m_TranslationFieldZ);

        m_ScaleLabel = new Label("Scale: ");
        m_ScaleFieldX = new TextField("0.00");
        m_ScaleFieldY = new TextField("0.00");
        m_ScaleFieldZ = new TextField("0.00");
        controlPanel.add(m_ScaleLabel);
        controlPanel.add(m_ScaleFieldX);
        controlPanel.add(m_ScaleFieldY);
        controlPanel.add(m_ScaleFieldZ);

        add(controlPanel, BorderLayout.SOUTH);

        doLayout();
    }

    protected double getScale() {
        return 1.0;
    }

    // do nothing for these notifications from the mouse behaviors
    public void onStartDrag(Object target) {
    }

    public void onEndDrag(Object target) {
    }

    public void onApplyTransform(Object target) {
    }

    public void onAdjustTransform(Object target, int xpos, int ypos) {
    }

    // called by TornadoMouseRotate
    // yes, those really are Euler angles for the objects rotation
    public void onRotate(Object target, Point3d point3d) {
        m_RotationFieldX.setText(String.valueOf((int) java.lang.Math.toDegrees(point3d.x)));
        m_RotationFieldY.setText(String.valueOf((int) java.lang.Math.toDegrees(point3d.y)));
        m_RotationFieldZ.setText(String.valueOf((int) java.lang.Math.toDegrees(point3d.z)));
    }

    // called by TornadoMouseScale
    public void onScale(Object target, Vector3d scale) {
        m_ScaleFieldX.setText(String.valueOf(scale.x));
        m_ScaleFieldY.setText(String.valueOf(scale.y));
        m_ScaleFieldZ.setText(String.valueOf(scale.z));
    }

    // called by TornadoMouseTranslate
    public void onTranslate(Object target, Vector3d vTranslation) {
        m_TranslationFieldX.setText(String.valueOf(vTranslation.x));
        m_TranslationFieldY.setText(String.valueOf(vTranslation.y));
        m_TranslationFieldZ.setText(String.valueOf(vTranslation.z));
    }

    // we want a black background
    protected Background createBackground() {
        return null;
    }

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

        // note that we are creating a TG *above* the TG
        // the is being controlled by the mouse behaviors.
        // The SUN mouse translate behavior would fail in this
        // instance as all movement would be in the X-Y plane
        // irrespective of any TG above the object.
        // The TornadoMouseTranslate behavior always moves an object
        // parrallel to the image plane
        TransformGroup objTrans1 = new TransformGroup();
        Transform3D t3d = new Transform3D();
        objTrans1.getTransform(t3d);
        t3d.setEuler(new Vector3d(0.9, 0.8, 0.3));
        objTrans1.setTransform(t3d);

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

        // create the mouse scale behavior and set limits
        TornadoMouseScale mouseScale = new TornadoMouseScale(5, 0.1f);
        mouseScale.setMinScale(new Point3d(0.5, 0.5, 0.5));
        mouseScale.setMaxScale(new Point3d(2, 2, 2));
        mouseScale.setObject(objTrans);
        mouseScale.setChangeListener(this);
        mouseScale.setSchedulingBounds(getApplicationBounds());
        objTrans.addChild(mouseScale);

        // create the mouse rotate behavior
        TornadoMouseRotate mouseRotate = new TornadoMouseRotate(0.001, 0.001);
        mouseRotate.setInvert(true);
        mouseRotate.setObject(objTrans);
        mouseRotate.setChangeListener(this);
        mouseRotate.setSchedulingBounds(getApplicationBounds());
        objTrans.addChild(mouseRotate);

        // create the mouse translate behavior and set limits
        TornadoMouseTranslate mouseTrans = new TornadoMouseTranslate(0.005f);
        mouseTrans.setObject(objTrans);
        mouseTrans.setChangeListener(this);
        mouseTrans.setMinTranslate(new Point3d(-4, -4, -4));
        mouseTrans.setMaxTranslate(new Point3d(4, 4, 4));
        mouseTrans.setSchedulingBounds(getApplicationBounds());
        objTrans.addChild(mouseTrans);

        objTrans.addChild(new ColorCube(0.5));

        // create some axis for the world to show it has been rotated
        ColorCube axis = new ColorCube(5.0);
        Appearance app = new Appearance();
        app.setPolygonAttributes(
                new PolygonAttributes(PolygonAttributes.POLYGON_LINE, PolygonAttributes.CULL_NONE, 0));
        axis.setAppearance(app);
        objTrans1.addChild(axis);

        objTrans1.addChild(objTrans);
        objRoot.addChild(objTrans1);

        return objRoot;
    }

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

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

//*****************************************************************************
/**
 * TornadoMouseRotate
 * 
 * Custom mouse rotation behaviour
 * 
 * @author Daniel Selman
 * @version 1.0
 */
//*****************************************************************************

class TornadoMouseRotate extends TornadoMouseBehavior {
    protected double m_FactorX = 0.001;

    protected double m_FactorY = 0.001;

    protected Transform3D m_TransformX = null;

    protected Transform3D m_TransformY = null;

    protected boolean m_bInvert = false;

    //*****************************************************************************
    /**
     * @param xf
     *            the x rotation scale factor
     * @param yf
     *            the y rotation scale factor
     */
    //*****************************************************************************
    public TornadoMouseRotate(double xf, double yf) {
        m_FactorX = xf;
        m_FactorY = yf;

        m_TransformX = new Transform3D();
        m_TransformY = new Transform3D();

        m_bInvert = false;
    }

    protected boolean isStartBehaviorEvent(java.awt.event.MouseEvent evt) {
        int nId = evt.getID();
        return ((nId == MouseEvent.MOUSE_DRAGGED) && (evt.isAltDown() == false) && (evt.isMetaDown() == false));
    }

    //*****************************************************************************
    /**
     * @param bInvert
     *            true to invert the Y axis
     */
    //*****************************************************************************
    public void setInvert(boolean bInvert) {
        m_bInvert = bInvert;
    }

    // this behavior is relative to the *screen*
    // the current rotation of the object etc. is ignored
    protected boolean isRelativeToObjectCoordinates() {
        return false;
    }

    protected void applyVectorToObject(Vector3f vector) {
        TransformGroup tg = getTransformGroup();

        if (tg != null) {
            tg.getTransform(m_Transform3D);

            double x_angle = vector.y * m_FactorX;
            double y_angle = vector.x * m_FactorY;

            m_TransformX.rotX(x_angle);
            m_TransformY.rotY(y_angle);

            Matrix4d mat = new Matrix4d();

            // Remember old matrix
            m_Transform3D.get(mat);

            // Translate to origin
            m_Transform3D.setTranslation(new Vector3d(0.0, 0.0, 0.0));

            if (m_bInvert != false) {
                m_Transform3D.mul(m_Transform3D, m_TransformX);
                m_Transform3D.mul(m_Transform3D, m_TransformY);
            } else {
                m_Transform3D.mul(m_TransformX, m_Transform3D);
                m_Transform3D.mul(m_TransformY, m_Transform3D);
            }

            // Set old translation back
            Vector3d translation = new Vector3d(mat.m03, mat.m13, mat.m23);
            m_Transform3D.setTranslation(translation);

            // save the new Transform3D
            applyTransform();

            if (m_Listener != null) {
                Point3d rotate = Euler.getEulerRotation(m_Transform3D);
                ((RotationChangeListener) m_Listener).onRotate(m_Object, rotate);
            }
        }
    }
} // TornadoMouseRotate

class Euler {
    public static int EulOrdXYZs() {
        return EulOrd(X, EulParEven, EulRepNo, EulFrmS);
    }

    public static int EulOrdXYXs() {
        return EulOrd(X, EulParEven, EulRepYes, EulFrmS);
    }

    public static int EulOrdXZYs() {
        return EulOrd(X, EulParOdd, EulRepNo, EulFrmS);
    }

    public static int EulOrdXZXs() {
        return EulOrd(X, EulParOdd, EulRepYes, EulFrmS);
    }

    public static int EulOrdYZXs() {
        return EulOrd(Y, EulParEven, EulRepNo, EulFrmS);
    }

    public static int EulOrdYZYs() {
        return EulOrd(Y, EulParEven, EulRepYes, EulFrmS);
    }

    public static int EulOrdYXZs() {
        return EulOrd(Y, EulParOdd, EulRepNo, EulFrmS);
    }

    public static int EulOrdYXYs() {
        return EulOrd(Y, EulParOdd, EulRepYes, EulFrmS);
    }

    public static int EulOrdZXYs() {
        return EulOrd(Z, EulParEven, EulRepNo, EulFrmS);
    }

    public static int EulOrdZXZs() {
        return EulOrd(Z, EulParEven, EulRepYes, EulFrmS);
    }

    public static int EulOrdZYXs() {
        return EulOrd(Z, EulParOdd, EulRepNo, EulFrmS);
    }

    public static int EulOrdZYZs() {
        return EulOrd(Z, EulParOdd, EulRepYes, EulFrmS);
    }

    /* Rotating axes */
    public static int EulOrdZYXr() {
        return EulOrd(X, EulParEven, EulRepNo, EulFrmR);
    }

    public static int EulOrdXYXr() {
        return EulOrd(X, EulParEven, EulRepYes, EulFrmR);
    }

    public static int EulOrdYZXr() {
        return EulOrd(X, EulParOdd, EulRepNo, EulFrmR);
    }

    public static int EulOrdXZXr() {
        return EulOrd(X, EulParOdd, EulRepYes, EulFrmR);
    }

    public static int EulOrdXZYr() {
        return EulOrd(Y, EulParEven, EulRepNo, EulFrmR);
    }

    public static int EulOrdYZYr() {
        return EulOrd(Y, EulParEven, EulRepYes, EulFrmR);
    }

    public static int EulOrdZXYr() {
        return EulOrd(Y, EulParOdd, EulRepNo, EulFrmR);
    }

    public static int EulOrdYXYr() {
        return EulOrd(Y, EulParOdd, EulRepYes, EulFrmR);
    }

    public static int EulOrdYXZr() {
        return EulOrd(Z, EulParEven, EulRepNo, EulFrmR);
    }

    public static int EulOrdZXZr() {
        return EulOrd(Z, EulParEven, EulRepYes, EulFrmR);
    }

    public static int EulOrdXYZr() {
        return EulOrd(Z, EulParOdd, EulRepNo, EulFrmR);
    }

    public static int EulOrdZYZr() {
        return EulOrd(Z, EulParOdd, EulRepYes, EulFrmR);
    }

    public static int EulFrm(int ord) {
        // DCS, was unsigned
        return ((ord) & 1);
    }

    public static int EulRep(int ord) {
        // DCS, was unsigned
        return (((ord) >> 1) & 1);
    }

    public static int EulPar(int ord) {
        return (((ord) >> 2) & 1);
    }

    public static int EulAxI(int ord) {
        // DCS, was unsigned
        return ((int) (EulSafe((((ord) >> 3) & 3))));
    }

    public static int EulAxJ(int ord) {
        int i = 0;

        if (EulPar(ord) == EulParOdd)
            i = 1;

        return ((int) (EulNext(EulAxI(ord) + i)));
    }

    public static int EulAxK(int ord) {
        int i = 0;

        if (EulPar(ord) != EulParOdd)
            i = 1;

        return ((int) (EulNext(EulAxI(ord) + i)));
    }

    public static int EulAxH(int ord) {
        if (EulRep(ord) == EulRepNo)
            return EulAxK(ord);

        return EulAxI(ord);
    }

    public static int EulOrd(int i, int p, int r, int f) {
        return (((((((i) << 1) + (p)) << 1) + (r)) << 1) + (f));
    }

    // enum
    static final int X = 0;

    static final int Y = 1;

    static final int Z = 2;

    static final int W = 3;

    static final int EulRepNo = 0;

    static final int EulRepYes = 1;

    static final int EulParEven = 0;

    static final int EulParOdd = 1;

    static final int EulFrmS = 0;

    static final int EulFrmR = 1;

    static final float FLT_EPSILON = 1.192092896e-07F;

    static EulGetOrdInfo EulGetOrd(int ord) {
        EulGetOrdInfo info = new EulGetOrdInfo();

        // note, used to be unsigned!
        int o = ord;
        info.f = o & 1;
        o >>= 1;
        info.s = o & 1;
        o >>= 1;
        info.n = o & 1;
        o >>= 1;
        info.i = EulSafe(o & 3);
        info.j = EulNext(info.i + info.n);
        info.k = EulNext(info.i + 1 - info.n);

        if (info.s != 0)
            info.h = info.k;
        else
            info.h = info.i;

        return info;
    }

    static int EulSafe(int val) {
        int[] valArray = { 0, 1, 2, 0 };
        return valArray[val];
    }

    static int EulNext(int val) {
        int[] valArray = { 1, 2, 0, 1 };
        return valArray[val];
    }

    // float HMatrix[4][4];

    /* Convert matrix to Euler angles (in radians). */
    public static EulerAngles Eul_FromMatrix(float[][] M, int order) {
        EulerAngles ea = new EulerAngles();

        EulGetOrdInfo info = EulGetOrd(order);

        int i = info.i;
        int j = info.j;
        int k = info.k;
        int h = info.h;
        int n = info.n;
        int s = info.s;
        int f = info.f;

        if (s == EulRepYes) {
            double sy = Math.sqrt(M[i][j] * M[i][j] + M[i][k] * M[i][k]);
            if (sy > 16 * FLT_EPSILON) {
                ea.x = (float) Math.atan2(M[i][j], M[i][k]);
                ea.y = (float) Math.atan2(sy, M[i][i]);
                ea.z = (float) Math.atan2(M[j][i], -M[k][i]);
            } else {
                ea.x = (float) Math.atan2(-M[j][k], M[j][j]);
                ea.y = (float) Math.atan2(sy, M[i][i]);
                ea.z = 0;
            }
        } else {
            double cy = Math.sqrt(M[i][i] * M[i][i] + M[j][i] * M[j][i]);
            if (cy > 16 * FLT_EPSILON) {
                ea.x = (float) Math.atan2(M[k][j], M[k][k]);
                ea.y = (float) Math.atan2(-M[k][i], cy);
                ea.z = (float) Math.atan2(M[j][i], M[i][i]);
            } else {
                ea.x = (float) Math.atan2(-M[j][k], M[j][j]);
                ea.y = (float) Math.atan2(-M[k][i], cy);
                ea.z = 0;
            }
        }
        if (n == EulParOdd) {
            ea.x = -ea.x;
            ea.y = -ea.y;
            ea.z = -ea.z;
        }
        if (f == EulFrmR) {
            float t = ea.x;
            ea.x = ea.z;
            ea.z = t;
        }
        ea.w = order;
        return (ea);
    }

    /* Convert quaternion to Euler angles (in radians). */
    public static EulerAngles Eul_FromQuat(Quat q, int order) {
        float[][] M = new float[4][4];
        double Nq = q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w;
        double s = (Nq > 0.0) ? (2.0 / Nq) : 0.0;
        double xs = q.x * s, ys = q.y * s, zs = q.z * s;
        double wx = q.w * xs, wy = q.w * ys, wz = q.w * zs;
        double xx = q.x * xs, xy = q.x * ys, xz = q.x * zs;
        double yy = q.y * ys, yz = q.y * zs, zz = q.z * zs;
        M[X][X] = (float) (1.0 - (yy + zz));
        M[X][Y] = (float) (xy - wz);
        M[X][Z] = (float) (xz + wy);
        M[Y][X] = (float) (xy + wz);
        M[Y][Y] = (float) (1.0 - (xx + zz));
        M[Y][Z] = (float) (yz - wx);
        M[Z][X] = (float) (xz - wy);
        M[Z][Y] = (float) (yz + wx);
        M[Z][Z] = (float) (1.0 - (xx + yy));
        M[W][X] = M[W][Y] = M[W][Z] = M[X][W] = M[Y][W] = M[Z][W] = 0.0f;
        M[W][W] = 1.0f;
        return (Eul_FromMatrix(M, order));
    }

    public static Point3d getEulerRotation(Transform3D t3d) {
        Point3d Rotation = new Point3d();

        Matrix3d m1 = new Matrix3d();
        t3d.get(m1);

        // extract the rotation angles from the upper 3x3 rotation
        // component of the 4x4 transformation matrix
        Rotation.y = -java.lang.Math.asin(m1.getElement(2, 0));
        double c = java.lang.Math.cos(Rotation.y);
        double tRx, tRy, tRz;

        if (java.lang.Math.abs(Rotation.y) > 0.00001) {
            tRx = m1.getElement(2, 2) / c;
            tRy = -m1.getElement(2, 1) / c;

            Rotation.x = java.lang.Math.atan2(tRy, tRx);

            tRx = m1.getElement(0, 0) / c;
            tRy = -m1.getElement(1, 0) / c;

            Rotation.z = java.lang.Math.atan2(tRy, tRx);
        } else {
            Rotation.x = 0.0;

            tRx = m1.getElement(1, 1);
            tRy = m1.getElement(0, 1);

            Rotation.z = java.lang.Math.atan2(tRy, tRx);
        }

        Rotation.x = -Rotation.x;
        Rotation.z = -Rotation.z;

        // now try to ensure that the values are positive by adding 2PI if
        // necessary...
        if (Rotation.x < 0.0)
            Rotation.x += 2 * java.lang.Math.PI;

        if (Rotation.y < 0.0)
            Rotation.y += 2 * java.lang.Math.PI;

        if (Rotation.z < 0.0)
            Rotation.z += 2 * java.lang.Math.PI;

        return Rotation;
    }
}

class EulerAngles extends Quat {
    public EulerAngles() {
    }
}

class Quat {
    public float x;
    public float y;
    public float z;
    public float w;

    public Quat() {
    }
}

class EulGetOrdInfo {
    public int i;

    public int j;

    public int k;

    public int h;

    public int n;

    public int s;

    public int f;

    EulGetOrdInfo() {
    }
}

//*****************************************************************************
/**
 * Base class for the Tornado Mouse Behaviors (Rotate, Translate, Scale).
 * 
 * @author Daniel Selman
 * @version 1.0
 */
//*****************************************************************************

abstract class TornadoMouseBehavior extends Behavior {
    // private data
    protected Object m_Object = null;

    protected Point3f m_NewPos = null;

    protected Point3f m_OldPos = null;

    protected Vector3f m_TranslationVector = null;

    protected Transform3D m_Translation = null;

    protected boolean m_bDragging = false;

    protected WakeupOr m_MouseCriterion = null;

    protected int m_nLastY = 0;

    protected Transform3D m_Transform3D = null;

    protected TornadoChangeListener m_Listener = null;

    public TornadoMouseBehavior() {
        m_Object = null;
        m_NewPos = new Point3f();
        m_OldPos = new Point3f();

        m_Translation = new Transform3D();
        m_TranslationVector = new Vector3f();
        m_bDragging = false;

        m_Transform3D = new Transform3D();
    }

    //*****************************************************************************
    /**
     * Register a listener for the behavior.
     * 
     * @param listener
     *            the listener to add or null to remove the listener
     */
    //*****************************************************************************
    public void setChangeListener(TornadoChangeListener listener) {
        m_Listener = listener;
    }

    //*****************************************************************************
    /**
     * Apply a delta vector (in the object's local coordinates) to the object.
     */
    //*****************************************************************************
    protected abstract void applyVectorToObject(Vector3f v);

    //*****************************************************************************
    /**
     * @return true is this the mouse event that starts the tracking behaviour
     */
    //*****************************************************************************
    protected abstract boolean isStartBehaviorEvent(java.awt.event.MouseEvent evt);

    //*****************************************************************************
    /**
     * Dispatches mouse events as appropriate. Should not need to overide this
     * method.
     */
    //*****************************************************************************
    protected void processMouseEvent(java.awt.event.MouseEvent evt) {
        if (m_Object != null) {
            if (isStartBehaviorEvent(evt) != false)
                adjustTransform(evt.getX(), evt.getY());

            else if (isStopBehaviorEvent(evt) != false)
                onEndDrag();
        }
    }

    //*****************************************************************************
    /**
     * @return true if this is the event that stops drag tracking behviour the
     *         default uses MOUSE_RELEASED.
     */
    //*****************************************************************************
    protected boolean isStopBehaviorEvent(java.awt.event.MouseEvent evt) {
        int nId = evt.getID();
        return (m_bDragging != false && nId == MouseEvent.MOUSE_RELEASED || nId == MouseEvent.MOUSE_EXITED);
    }

    //*****************************************************************************
    /**
     * @return true if this behaviours change vector is relative to the starting
     *         mouse click. The default is a behaviour that generates delta
     *         change vectors as the user moves the mouse.
     */
    //*****************************************************************************
    protected boolean isRelativeToStartDrag() {
        return false;
    }

    //*****************************************************************************
    /**
     * @return true if the mouse coordinates should be converted to local object
     *         coordinates before being processed by applyVectorToObject
     */
    //*****************************************************************************
    protected boolean isRelativeToObjectCoordinates() {
        return true;
    }

    //*****************************************************************************
    /**
     * Allows custom start drag processing. Default does nothing.
     */
    //*****************************************************************************
    protected void onStartDrag() {
        if (m_Listener != null)
            m_Listener.onStartDrag(m_Object);
    }

    //*****************************************************************************
    /**
     * Allows custom end drag processing. ** Call this base class! **
     */
    //*****************************************************************************
    protected void onEndDrag() {
        m_bDragging = false;

        if (m_Listener != null)
            m_Listener.onEndDrag(m_Object);
    }

    //*****************************************************************************
    /**
     * Gets the Transform3D to convert from the Objects coordinate system to the
     * world coordinate system.
     * 
     * @param t3d
     *            the Transform3D to populate
     */
    //*****************************************************************************
    protected void getObjectLocalToVworld(Transform3D t3d) {
        if (getTransformGroup() != null)
            getTransformGroup().getLocalToVworld(t3d);
    }

    //*****************************************************************************
    /**
     * Gets the Transform3D to convert from the Image plate coordinate system to
     * the world coordinate system.
     * 
     * @param t3d
     *            the Transform3D to populate
     */
    //*****************************************************************************
    protected void getImagePlateToVworld(Transform3D t3d) {
        getView().getCanvas3D(0).getImagePlateToVworld(t3d);
    }

    //*****************************************************************************
    /**
     * @return the TransformGroup if a TG Object is associated with the behavior
     *         or null otherwise.
     */
    //*****************************************************************************
    protected TransformGroup getTransformGroup() {
        if (m_Object instanceof TransformGroup)
            return (TransformGroup) m_Object;

        return null;
    }

    //*****************************************************************************
    /**
     * Saves the behaviors Transform3D into its TransformGroup (if present).
     * Catches any exceptions (bad transform) that might be thrown.
     */
    //*****************************************************************************
    protected void applyTransform() {
        TransformGroup tg = getTransformGroup();

        if (tg != null) {
            try {
                // save the new Transform3D
                tg.setTransform(m_Transform3D);

                if (m_Listener != null)
                    m_Listener.onApplyTransform(m_Object);
            } catch (Exception e) {
                System.err.println(e.toString());
            }
        }
    }

    //*****************************************************************************
    /**
     * Transforms the x,y mouse coordinates to coordinates relative to the
     * object. Calculates a "delta vector" in object coordinates and calls
     * ApplyVectorToObject().
     * 
     * Thanks to: A.R. van Ballegooy. Simon McMullen [simonmc@mincom.com]
     */
    //*****************************************************************************
    protected void adjustTransform(int xpos, int ypos) {
        if (m_Listener != null)
            m_Listener.onAdjustTransform(m_Object, xpos, ypos);

        if (m_bDragging == false) {
            // initialise the starting position
            m_OldPos.x = xpos;
            m_OldPos.y = ypos;
            m_OldPos.z = 0.0f;
            m_nLastY = ypos;

            onStartDrag();
        }

        m_bDragging = true;

        // save the current position and invert the tracking in the Y direction
        // (positive upwards)
        m_NewPos.x = xpos;
        m_NewPos.y = m_nLastY + (m_nLastY - ypos);
        m_NewPos.z = 0.0f;
        m_nLastY = ypos;

        // transform points to Virtual World Coordinates
        getImagePlateToVworld(m_Translation);

        if (isRelativeToStartDrag() == false)
            m_Translation.transform(m_OldPos);

        m_Translation.transform(m_NewPos);

        // transform coordinates to Object Space Coordinates
        // Make sure capability ALLOW_LOCAL_TO_VWORLD_READ is set....

        if (isRelativeToObjectCoordinates() != false) {
            getObjectLocalToVworld(m_Translation);
            m_Translation.transpose();

            // transform points to local coordinate system
            if (isRelativeToStartDrag() == false)
                m_Translation.transform(m_OldPos);

            m_Translation.transform(m_NewPos);
        }

        // Calculate change and scale
        m_TranslationVector.sub(m_NewPos, m_OldPos);

        applyVectorToObject(m_TranslationVector);

        if (isRelativeToStartDrag() == false) {
            // store the new positions
            m_OldPos.x = xpos;
            m_OldPos.y = ypos;
            m_OldPos.z = 0.0f;
        }
    }

    //*****************************************************************************
    /**
     * Dispatches events based on the behaviours criteria
     */
    //*****************************************************************************
    public void processStimulus(Enumeration criteria) {
        WakeupCriterion wakeup;
        AWTEvent[] event;
        int id;
        int dx, dy;

        if (m_Object != null) {
            while (criteria.hasMoreElements()) {
                wakeup = (WakeupCriterion) criteria.nextElement();

                if (wakeup instanceof WakeupOnAWTEvent) {
                    event = ((WakeupOnAWTEvent) wakeup).getAWTEvent();

                    for (int i = 0; i < event.length; i++) {
                        processMouseEvent((MouseEvent) event[i]);
                    }
                }
            }
        }

        // tell the behaviour when to wake up again...
        wakeupOn(m_MouseCriterion);
    }

    //*****************************************************************************
    /**
     * Registers which AWT events are of interest to the behaviour
     */
    //*****************************************************************************
    public void initialize() {
        WakeupCriterion[] mouseEvents = new WakeupCriterion[3];

        mouseEvents[0] = new WakeupOnAWTEvent(MouseEvent.MOUSE_DRAGGED);
        mouseEvents[1] = new WakeupOnAWTEvent(MouseEvent.MOUSE_PRESSED);
        mouseEvents[2] = new WakeupOnAWTEvent(MouseEvent.MOUSE_RELEASED);

        m_MouseCriterion = new WakeupOr(mouseEvents);
        wakeupOn(m_MouseCriterion);
    }

    //*****************************************************************************
    /**
     * void setObject( Object obj )
     * 
     * @param obj
     *            the Objectto manipulate. A null object disables the behaviour.
     */
    //*****************************************************************************
    public void setObject(Object obj) {
        m_Object = obj;
    }

} // TornadoMouseBehavior

//*****************************************************************************
/**
 * TornadoMouseScale
 * 
 * Custon scaling behaviour
 * 
 * @author Daniel Selman
 * @version 1.0
 */
//*****************************************************************************

class TornadoMouseScale extends TornadoMouseBehavior {
    // private data
    protected float m_Delta = 0;

    protected float m_Threshold = 0;

    protected Point3d m_MinScale;

    protected Point3d m_MaxScale;

    //*****************************************************************************
    /**
     * @param threshold
     *            the amount the mouse must be moved before an object is moved
     * @param delta
     *            the step size to use for object scaling bigger = faster
     *            scaling.
     * 
     * Default minimum scale: 0.1,0.1,0.1 Default maximum scale: 5,5,5
     */
    //*****************************************************************************
    public TornadoMouseScale(float threshold, float delta) {
        m_Delta = delta;
        m_Threshold = threshold;

        m_MinScale = new Point3d(0.1, 0.1, 0.1);
        m_MaxScale = new Point3d(5, 5, 5);
    }

    //*****************************************************************************
    /**
     * @param minScale
     *            the minimum x,y,z scale
     */
    //*****************************************************************************
    public void setMinScale(Point3d minScale) {
        m_MinScale = minScale;
    }

    //*****************************************************************************
    /**
     * @param maxScale
     *            the maximum x,y,z scale
     */
    //*****************************************************************************
    public void setMaxScale(Point3d maxScale) {
        m_MaxScale = maxScale;
    }

    // this behavior is relative to the *screen*
    // the current rotation of the object etc. is ignored
    protected boolean isRelativeToObjectCoordinates() {
        return true;
    }

    protected boolean isStartBehaviorEvent(java.awt.event.MouseEvent evt) {
        int nId = evt.getID();
        return ((nId == MouseEvent.MOUSE_DRAGGED) && (evt.isAltDown() != false) && (evt.isMetaDown() == false));
    }

    protected void applyVectorToObject(Vector3f vector) {
        TransformGroup tg = getTransformGroup();

        if (tg != null) {
            tg.getTransform(m_Transform3D);

            Vector3d vScale = new Vector3d();
            m_Transform3D.getScale(vScale);

            Vector3f delta = new Vector3f();

            if (vector.x > m_Threshold)
                delta.x = m_Delta;
            else if (vector.x < -m_Threshold)
                delta.x = -m_Delta;

            if (vector.y > m_Threshold)
                delta.y = m_Delta;
            else if (vector.y < -m_Threshold)
                delta.y = -m_Delta;

            if (vector.z > m_Threshold)
                delta.z = m_Delta;
            else if (vector.z < -m_Threshold)
                delta.z = -m_Delta;

            Vector3d objectScale = new Vector3d(vScale.x + delta.x, vScale.y + delta.y, vScale.z + delta.z);

            if (objectScale.x >= m_MinScale.x && objectScale.y >= m_MinScale.y && objectScale.z >= m_MinScale.z) {
                if (objectScale.x <= m_MaxScale.x && objectScale.y <= m_MaxScale.y
                        && objectScale.z <= m_MaxScale.z) {
                    m_Transform3D.setScale(objectScale);

                    // save the new Transform3D
                    applyTransform();

                    if (m_Listener != null)
                        ((ScaleChangeListener) m_Listener).onScale(m_Object, objectScale);
                }
            }
        }
    }
} // TornadoMouseScale

//*****************************************************************************
/**
 * TornadoMouseTranslate
 * 
 * Custom translation behavior.
 * 
 * @author Daniel Selman
 * @version 1.0
 */
//*****************************************************************************

class TornadoMouseTranslate extends TornadoMouseBehavior {
    // private data
    private float m_Scale = 1;

    protected Point3d m_MinTranslate = null;

    protected Point3d m_MaxTranslate = null;

    // protected data

    // public data

    //*****************************************************************************
    /**
     * @param scale
     *            the translation scale factor (bigger = faster)
     * 
     * Default minimum translation: -10,-10,-10 Default maximum translation:
     * 10,10,10
     */
    //*****************************************************************************
    public TornadoMouseTranslate(float scale) {
        m_Scale = scale;

        m_MinTranslate = new Point3d(-10, -10, -10);
        m_MaxTranslate = new Point3d(10, 10, 10);
    }

    //*****************************************************************************
    /**
     * @param minTrans
     *            the minimum x,y,z translation
     */
    //*****************************************************************************
    public void setMinTranslate(Point3d minTrans) {
        m_MinTranslate = minTrans;
    }

    //*****************************************************************************
    /**
     * @param maxTrans
     *            the maximum x,y,z translation
     */
    //*****************************************************************************
    public void setMaxTranslate(Point3d maxTrans) {
        m_MaxTranslate = maxTrans;
    }

    protected boolean isStartBehaviorEvent(java.awt.event.MouseEvent evt) {
        int nId = evt.getID();
        return ((nId == MouseEvent.MOUSE_DRAGGED) && (evt.isAltDown() == false) && (evt.isMetaDown() != false));
    }

    protected void applyVectorToObject(Vector3f vector) {
        TransformGroup tg = getTransformGroup();

        if (tg != null) {
            // scale the mouse movements so the objects roughly tracks with the
            // mouse
            vector.scale(m_Scale);

            Vector3d vTranslation = new Vector3d();
            tg.getTransform(m_Transform3D);
            m_Transform3D.get(vTranslation);

            vTranslation.x += vector.x;
            vTranslation.y += vector.y;
            vTranslation.z += vector.z;

            if (vTranslation.x >= m_MinTranslate.x && vTranslation.y >= m_MinTranslate.y
                    && vTranslation.z >= m_MinTranslate.z) {
                if (vTranslation.x <= m_MaxTranslate.x && vTranslation.y <= m_MaxTranslate.y
                        && vTranslation.z <= m_MaxTranslate.z) {
                    m_Transform3D.setTranslation(vTranslation);
                    applyTransform();

                    if (m_Listener != null)
                        ((TranslationChangeListener) m_Listener).onTranslate(m_Object, vTranslation);
                }
            }
        }
    }
} // TornadoMouseTranslate

//*****************************************************************************
/**
 * Interface to listen for changes in translation from the TornadoMouseScale
 * class.
 * 
 * @author Daniel Selman
 * @version 1.0
 */
//*****************************************************************************

abstract interface ScaleChangeListener extends TornadoChangeListener {
    //*****************************************************************************
    /**
     * Callback to notify of new scale being applied.
     * 
     * @param target
     *            the Object being manipulated
     * @param scale
     *            the new scale being applied
     */
    //*****************************************************************************
    public void onScale(Object target, Vector3d scale);
}

//*****************************************************************************
/**
 * Interface to listen for changes in translation from the TornadoMouseTranslate
 * class.
 * 
 * @author Daniel Selman
 * @version 1.0
 */
//*****************************************************************************

abstract interface TranslationChangeListener extends TornadoChangeListener {
    //*****************************************************************************
    /**
     * Callback to notify of new translationg being applied.
     * 
     * @param target
     *            the Object being manipulated
     * @param vTranslation
     *            the new translation being applied
     */
    //*****************************************************************************
    public void onTranslate(Object target, Vector3d vTranslation);
}

//*****************************************************************************
/**
 * Interface to listen for changes in rotation from the TornadoMouseRotate
 * class.
 * 
 * @author Daniel Selman
 * @version 1.0
 */
//*****************************************************************************

abstract interface RotationChangeListener extends TornadoChangeListener {
    //*****************************************************************************
    /**
     * Callback to notify of new translationg being applied.
     * 
     * @param target
     *            the Object being manipulated
     * @param point3d
     *            the new rotation (Euler, radians) applied
     */
    //*****************************************************************************
    public void onRotate(Object target, Point3d point3d);
}

//*****************************************************************************
/**
 * Interface to listen for changes affected by in the TornadoMouseBehaviors.
 * 
 * @author Daniel Selman
 * @version 1.0
 */
//*****************************************************************************

abstract interface TornadoChangeListener {
    //*****************************************************************************
    /**
     * Callback to notify of a start drag event.
     * 
     * @param target
     *            the Object being manipulated
     */
    //*****************************************************************************
    public void onStartDrag(Object target);

    //*****************************************************************************
    /**
     * Callback to notify of an end drag event.
     * 
     * @param target
     *            the Object being manipulated
     */
    //*****************************************************************************
    public void onEndDrag(Object target);

    //*****************************************************************************
    /**
     * Notification that the Transform is being updated
     * 
     * @param target
     *            the Object being manipulated
     */
    //*****************************************************************************
    public void onApplyTransform(Object target);

    //*****************************************************************************
    /**
     * Notification that a new Transform is being calculated
     * 
     * @param target
     *            the Object being manipulated
     * @param xpos
     *            the mouse x position
     * @param ypos
     *            the mouse y position
     */
    //*****************************************************************************
    public void onAdjustTransform(Object target, int xpos, int ypos);
}

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