/*
 *      @(#)Vrml97Viewer.java 1.26 98/09/24 12:36:47
 *
 * Copyright (c) 1996-1998 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software; and ii) Licensee does not utilize the software in a manner
 * which is disparaging to Sun.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 *
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 * the design, construction, operation or maintenance of any nuclear
 * facility. Licensee represents and warrants that it will not use or
 * redistribute the Software for such purposes.
 *
 * @Author: Doug Gehringer
 * @Author: Rick Goldberg
 */


// A Frame which creates a Canvas3D and a VrmlLoader and passes a URL to 
// the loader

// TODO: make into an applet

import java.io.File;
import java.io.IOException;
import java.awt.*;
import java.awt.event.*;
import java.util.Enumeration;
import java.net.URL;

import javax.media.j3d.*;
import javax.vecmath.*;
import com.sun.j3d.utils.behaviors.mouse.*;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.loaders.vrml97.VrmlLoader;
import com.sun.j3d.loaders.vrml97.VrmlScene;
import com.sun.j3d.loaders.vrml97.BaseNode;
import com.sun.j3d.loaders.vrml97.node.Viewpoint;
// import com.sun.j3d.utils.dev_tools.TreePrinter;


public class Vrml97Viewer extends Frame implements ActionListener, 
	ItemListener, MotionNotifierInterface {

    Canvas3D 	canvas;
    VirtualUniverse     universe;
    Locale              locale;
    View                view;
    String   		urlString;
    VrmlLoader		loader;
    Viewpoint[]		fileViewpoints = null;
    TransformGroup[]	fileVpWorldTrans;
    TransformGroup[]	fileVpOrientTrans;
    TransformGroup[]	fileVpTrans;
    ViewPlatform[]	fileVp;
    BranchGroup		objRoot;
    BoundingSphere	objBounds = null;
    BranchGroup		vpRoot;
    BranchGroup		browserGroup;
    DirectionalLight	headLight;
    AmbientLight	ambLight;
    ViewPlatform	vp ;
    TransformGroup 	vpTrans, prevVpTrans;
    TransformGroup	vpWorldTrans, vpOrientTrans;
    TransformGroup	objVpObjectTrans;
    TransformGroup	objVpWorldTrans[] = new TransformGroup[NUM_OBJ_VIEWS];
    TransformGroup	objVpOrientTrans[] = new TransformGroup[NUM_OBJ_VIEWS];
    TransformGroup	objVpTrans[] = new TransformGroup[NUM_OBJ_VIEWS];
    ViewPlatform	objVp[] = new ViewPlatform[NUM_OBJ_VIEWS];
    BoundingSphere	browserBounds;
    float		vpFieldOfView, vpFrontClip, vpBackClip;
    float		objVpFieldOfView, objVpFrontClip;
    float		objVpBackClip;
    int			viewMode = EXAMINE;
    TextField 		gotoUrl;
    Panel 		panel;
    Label 		label;
    FileDialog 		fd;
    String 		filename;
    String 		pathname;
    MenuBar 		mb;
    Menu 		m;
    Menu		fileVpMenu;
    Menu		objVpMenu;
    MenuItem 		mi;
    CheckboxMenuItem 	hlCheck;
    File 		file;
    WindowListener 	wl;
    int			initVp = OBJ_VIEW_PLUS_Z;
    String		fileVpAction = "setFileVp ";
    String		objVpAction = "setObjVp ";
    String		modeAction = "setMode ";
    String		resetViewpoint = "Reset Viewpoint";
    Transform3D		identity = new Transform3D();
    NumFormat		numFormat = new NumFormat();
    boolean		fileLoaded = false;
    boolean		debug;
    boolean		timing;
    boolean		loadOnly;
    int			numTris;
    boolean		startupTiming;
    int			startupCount;
    int			startupFrames = 5;
    int			numFrames = 0;
    long		postTime;
    long 		baseTime = System.currentTimeMillis();
//    TreePrinter		tp = new TreePrinter();

    static final int	AMB_LIGHT = 0;
    static final int	DIR_LIGHT = 1;
    static final int	BEHAVIOR  = 2;
    static final int	NUM_SLOTS = 3;

    static final int	EXAMINE = 1;
    static final int	FLY = 2;

    static final int	FILE_VIEW = -1;
    static final int    OBJ_VIEW_PLUS_X = 0;
    static final int    OBJ_VIEW_PLUS_Y = 1;
    static final int    OBJ_VIEW_PLUS_Z = 2;
    static final int    NUM_OBJ_VIEWS = 3;

    Vrml97Viewer(String initURL) {
	debug = Boolean.getBoolean("debug");
	timing = Boolean.getBoolean("timing");
	loadOnly = Boolean.getBoolean("loadOnly");
	urlString = initURL;

	if (!urlString.endsWith(".wrl") ) {
		pathname = urlString;
		urlString +="interp.wrl";
	} else {
		int lastSlash = urlString.lastIndexOf('/');
		if (lastSlash > 0) {
		    pathname =  urlString.substring(0, lastSlash+1); 
		} 
	}
	//System.out.println("pathname = " + pathname);

	// menus
	mb = new MenuBar();
	m = new Menu("File");
	mi = new MenuItem("Open");
	mi.addActionListener(this);
	m.add(mi);
	mi = new MenuItem("Exit");
	mi.addActionListener(this);
	m.add(mi);
	mb.add(m);

	m = new Menu("View");
	fileVpMenu = new Menu("File Viewpoint");
	m.add(fileVpMenu);
	objVpMenu = new Menu("Object Viewpoint");
	mi = new MenuItem("Object from +X");
	mi.addActionListener(this);
	mi.setActionCommand(objVpAction + OBJ_VIEW_PLUS_X);
	//objVpMenu.add(mi);
	mi = new MenuItem("Object from +Y");
	mi.addActionListener(this);
	mi.setActionCommand(objVpAction + OBJ_VIEW_PLUS_Y);
	//objVpMenu.add(mi);
	mi = new MenuItem("Object from +Z");
	mi.addActionListener(this);
	mi.setActionCommand(objVpAction + OBJ_VIEW_PLUS_Z);
	objVpMenu.add(mi);
	m.add(objVpMenu);
	mi = new MenuItem(resetViewpoint);
	mi.addActionListener(this);
	m.add(mi);
	mi = new MenuItem("WorldInfo");
	mi.addActionListener(this);
	m.add(mi);
	mb.add(m);

	m = new Menu("Mode");
	mi = new MenuItem("Examine");
	mi.addActionListener(this);
	mi.setActionCommand(modeAction + EXAMINE);
	m.add(mi);
	mi = new MenuItem("Fly");
	mi.setActionCommand(modeAction + FLY);
	mi.addActionListener(this);
	m.add(mi);
	mb.add(m);

	m = new Menu("Options");
	hlCheck = new CheckboxMenuItem("Headlight", true);
	hlCheck.addItemListener(this);
	m.add(hlCheck);
	mb.add(m);

	m = new Menu("Info");
	mi = new MenuItem("About");
	mi.addActionListener(this);
	m.add(mi);
	mb.add(m);

	fd = new FileDialog(this,"Visit WRL",FileDialog.LOAD);
	fd.setDirectory("."+File.separator+"parts");
	setMenuBar(mb);

	loader = new VrmlLoader();

    }

    public void actionPerformed(ActionEvent evt) {
	if(evt.getSource().equals(gotoUrl)){
	    urlString = gotoUrl.getText();
	    gotoUrl(urlString);
	} else if (evt.getSource() instanceof MenuItem) {
	    String arg = evt.getActionCommand();
	    if (arg.equals("Open")) {
		fd.show();
		gotoUrl.setText(pathname+fd.getFile());
		gotoUrl(pathname+fd.getFile());
	    } else if (arg.equals("Exit")) {
		if (timing) {
		    outputTiming();
		}
		System.exit(0);
	    } else if (arg.startsWith(fileVpAction)) {
		String indexString = arg.substring(fileVpAction.length());
		int index = Integer.valueOf(indexString).intValue();
		setFileViewpoint(index);
	    } else if (arg.startsWith(objVpAction)) {
		String indexString = arg.substring(objVpAction.length());
		int index = Integer.valueOf(indexString).intValue();
		setObjViewpoint(index);
	    } else if (arg.equals(resetViewpoint)) {
		resetViewpoint();
	    } else if (arg.startsWith(modeAction)) {
		String indexString = arg.substring(modeAction.length());
		int index = Integer.valueOf(indexString).intValue();
		setMode(index);
	    } else {
		System.out.println("Unknown action: " + arg);
	    }
	}
    }

    public void itemStateChanged(ItemEvent evt) {
	if (evt.getItem().equals("Headlight")) {
	    setHeadlight(hlCheck.getState());
	} else {
	    System.out.println("Unknown item changed: " + evt);
	}
    }

    // This is the normal UniverseBuilder code, except that the ViewPlatform
    // will only be used if VRML doesn't define one
    void setupJ3D(Canvas3D canvas) {

	// Establish an unnamed virtual universe, with a single hi-res Locale
        universe = new VirtualUniverse();
        locale = new Locale(universe);

        // Create a PhysicalBody and Physical Environment object
        PhysicalBody body = new PhysicalBody();
        PhysicalEnvironment environment = new PhysicalEnvironment();

        // Create a View and attach the Canvas3D and the physical
        // body and environment to the view.
        view = new View();
        view.addCanvas3D(canvas);
        view.setPhysicalBody(body);
        view.setPhysicalEnvironment(environment);

        // Create a branch group node for the default view platform
        vpRoot = new BranchGroup();
	vpRoot.setCapability(BranchGroup.ALLOW_CHILDREN_READ);

	// This is the stuff that the browser needs to have in the same
	// place as the view platform.  It will get added to the view platform
	// group when a view is selected
	browserGroup = new BranchGroup();
	browserGroup.setCapability(BranchGroup.ALLOW_DETACH);
	browserGroup.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
	browserGroup.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
	setSize(browserGroup, NUM_SLOTS);
	BoundingSphere lightBounds = 
                new BoundingSphere(new Point3d(), Double.MAX_VALUE);
	ambLight = new AmbientLight(true, new Color3f(0.2f, 0.2f, 0.2f));
	ambLight.setInfluencingBounds(lightBounds);
	ambLight.setCapability(Light.ALLOW_STATE_WRITE);
	browserGroup.setChild(ambLight, AMB_LIGHT);
	headLight = new DirectionalLight();
	headLight.setColor(new Color3f(0.8f, 0.8f, 0.8f));
	headLight.setCapability(Light.ALLOW_STATE_WRITE);
	headLight.setInfluencingBounds(lightBounds);
	browserGroup.setChild(headLight, DIR_LIGHT);


        // initialize the default view tree.  These are predefined views
	// which display the whole object along each of the axes.
	// The Object transform is used to scale and translate the object so 
	// it's BoundingSphere is at the origin with radius=1 in the View 
	// space.  The World transform is used to tranform the "world" 
	// relative to the view (examine mode). The Orient tranform is used 
	// to set the initial placement of the view (set by the VRML fields 
	// for VRML Viewpoints and initialzed here for obj views) and the
	// VpTrans is the transform from the initial view platform orientation
	// made by the browser
	objVpFieldOfView = .785398f;
	objVpFrontClip = 0.01f;
	objVpBackClip = 30.0f;
	double eyeDist = 1.1 / Math.tan(objVpFieldOfView/ 2.0);
	Point3d origin = new Point3d();
	Point3d offset = new Point3d();
	Vector3d up = new Vector3d();
	Transform3D eyeTrans = new Transform3D();

        objVpObjectTrans =  new TransformGroup();
        objVpObjectTrans.setCapability(vpTrans.ALLOW_TRANSFORM_READ);
        objVpObjectTrans.setCapability(vpTrans.ALLOW_TRANSFORM_WRITE);
        objVpObjectTrans.setCapability(Group.ALLOW_CHILDREN_READ);
        objVpObjectTrans.setCapability(Group.ALLOW_CHILDREN_WRITE);
        objVpObjectTrans.setCapability(Group.ALLOW_CHILDREN_EXTEND);
	for (int i = 0; i < NUM_OBJ_VIEWS; i++) {
	    objVpWorldTrans[i] =  new TransformGroup();
	    objVpWorldTrans[i].setCapability(vpTrans.ALLOW_TRANSFORM_READ);
	    objVpWorldTrans[i].setCapability(vpTrans.ALLOW_TRANSFORM_WRITE);
	    objVpWorldTrans[i].setCapability(Group.ALLOW_CHILDREN_READ);
	    objVpWorldTrans[i].setCapability(Group.ALLOW_CHILDREN_WRITE);
	    objVpWorldTrans[i].setCapability(Group.ALLOW_CHILDREN_EXTEND);
	    objVpObjectTrans.addChild(objVpWorldTrans[i]);
	    objVpOrientTrans[i] =  new TransformGroup();
	    objVpOrientTrans[i].setCapability(vpTrans.ALLOW_TRANSFORM_READ);
	    objVpOrientTrans[i].setCapability(vpTrans.ALLOW_TRANSFORM_WRITE);
	    objVpOrientTrans[i].setCapability(Group.ALLOW_CHILDREN_READ);
	    objVpOrientTrans[i].setCapability(Group.ALLOW_CHILDREN_WRITE);
	    objVpOrientTrans[i].setCapability(Group.ALLOW_CHILDREN_EXTEND);
	    // set orient trans to view sphere of radius 1 at origin
	    switch (i) { 
	      case OBJ_VIEW_PLUS_X:
		offset.x = eyeDist;
		offset.y = 0.0;
		offset.z = 0.0;
		up.x = 0.0;
		up.y = 0.0;
		up.z = 1.0;
		eyeTrans.lookAt(offset, origin, up);
		//System.out.println("+X eye xform = \n" + eyeTrans);
		break;
	      case OBJ_VIEW_PLUS_Y:
		offset.x = 0.0;
		offset.y = eyeDist;
		offset.z = 0.0;
		up.x = 0.0;
		up.y = 0.0;
		up.z = 1.0;
		eyeTrans.lookAt(offset, origin, up);
		// System.out.println("+Y eye xform = \n" + eyeTrans);
		break;
	      case OBJ_VIEW_PLUS_Z:
		offset.x = 0.0;
		offset.y = 0.0;
		offset.z = eyeDist;
		up.x = 0.0;
		up.y = 1.0;
		up.z = 0.0;
		eyeTrans.lookAt(offset, origin, up);
		// System.out.println("+Z eye xform = \n" + eyeTrans);
		break;
	    }
	    eyeTrans.invert(); // need to invert because on view side of tree
	    objVpOrientTrans[i].setTransform(eyeTrans);
	    objVpWorldTrans[i].addChild(objVpOrientTrans[i]);
	    objVpTrans[i] = new TransformGroup();
	    objVpTrans[i].setCapability(vpTrans.ALLOW_TRANSFORM_READ);
	    objVpTrans[i].setCapability(vpTrans.ALLOW_TRANSFORM_WRITE);
	    objVpTrans[i].setCapability(Group.ALLOW_CHILDREN_READ);
	    objVpTrans[i].setCapability(Group.ALLOW_CHILDREN_WRITE);
	    objVpTrans[i].setCapability(Group.ALLOW_CHILDREN_EXTEND);
	    objVpOrientTrans[i].addChild(objVpTrans[i]);
	    objVp[i] = new ViewPlatform();
	    objVpTrans[i].addChild(objVp[i]);
	}

	browserBounds = new BoundingSphere(new Point3d(), Double.MAX_VALUE);

	// currently don't have a file version for these, so they will always
	// be the obj versions
	vpFieldOfView = objVpFieldOfView;
	vpFrontClip = objVpFrontClip;
	vpBackClip = objVpBackClip;

	updateBehavior();

        vpRoot.addChild(objVpObjectTrans);
	objVpTrans[0].addChild(browserGroup);
	prevVpTrans = objVpTrans[0];

	// setup the initial obj viewpoints and use one
	setupObjViewpoints();
	setObjViewpoint(0);


        // Attach the vpRoot to the universe, via the Locale.
        locale.addBranchGraph(vpRoot);

	objRoot = new BranchGroup();
	objRoot.setCapability(BranchGroup.ALLOW_BOUNDS_READ);
	objRoot.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
	objRoot.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
	objRoot.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
	objRoot.setCapability(BranchGroup.ALLOW_DETACH);

    }

    private void setSize(javax.media.j3d.Group g, int size) {
        for (int i = 0; i < size; i++) {
            g.addChild(new NullNode());
        }
    }

    void setupCanvas() {
	if (timing) {
	    canvas = new TimingCanvas3D(null, this);
	} else {
	    canvas = new Canvas3D(null);
	}
	add("Center",canvas);
	panel = new Panel();
	panel.setLayout(new FlowLayout(FlowLayout.LEFT));
	gotoUrl = new GotoWRLTextField(this,urlString,60);	
	label = new Label("WRL:");
	panel.add(label);
	panel.add(gotoUrl);
	add("North",panel);
	canvas.setCursor(new Cursor(Cursor.WAIT_CURSOR));
	setupJ3D(canvas);
	show();
    }

    void removeOldURL() {
	objRoot.detach();
	int numToRemove = objRoot.numChildren();
	for (int i = 0; i < numToRemove; i++) {
	    objRoot.removeChild(0);
	}
	fileVpMenu.removeAll();
	fileViewpoints = null;
	objBounds = null;
    }

    void resetViewpoint() {
	vpWorldTrans.setTransform(identity);
	vpTrans.setTransform(identity);
    }

    private void removeBgFromPrev() {
	if (debug) System.out.println("removeBgFromPrev()");
	int numChildren = prevVpTrans.numChildren();
	boolean found = false;
	for (int i = numChildren-1; i >= 0; i--) { 
	    Node child = prevVpTrans.getChild(i);
	    if (child.equals(browserGroup)) {
		if (debug) System.out.println("removeBgFromPrev():" +
			" removing child " + i + " from group " + prevVpTrans);
		prevVpTrans.removeChild(i);
		found = true;
	    }
	}
	if (!found) {
	    System.out.println("Can't find browserGroup on previous vpTrans");
	}
    }


    private void updateView() {
	if (debug) System.out.println("updateView()");
	resetViewpoint();
	removeBgFromPrev();
	vpTrans.addChild(browserGroup);
	if (debug) System.out.println("updateView(): updating view");
	view.setFieldOfView(vpFieldOfView);
	view.setFrontClipDistance(vpFrontClip);
	view.setBackClipDistance(vpBackClip);
	view.attachViewPlatform(vp);
	updateBehavior();

	prevVpTrans = vpTrans;
    }

    void setFileViewpoint(int index) {
	if (debug) System.out.println("setFileViewpoint(" + index + ")");
	Viewpoint viewpoint = fileViewpoints[index];
	vpWorldTrans = fileVpWorldTrans[index];
	vpOrientTrans = fileVpOrientTrans[index];
	vpTrans = fileVpTrans[index];
	vp = fileVp[index];

	updateView();
    }

    void setObjViewpoint(int index) {
	if (debug) System.out.println("setObjViewpoint(" + index + ")");
	vp = objVp[index];
	vpTrans = objVpTrans[index];
	vpWorldTrans = objVpWorldTrans[index];
	vpOrientTrans = objVpOrientTrans[index];

	updateView();
    }

    void setupObjViewpoints() {
	if (debug) System.out.println("setupObjViewpoints()");

	Vector3d offset = new Vector3d();
	Transform3D objTrans = new Transform3D();

	if (objBounds == null) {
	    objVpObjectTrans.setTransform(identity);
	} else {

	    // Scale the obj trans so that the bounding sphere has radius 1
	    // in view space
	    double radius = objBounds.getRadius();

	    // Set the Obj trans so the bounding sphere is at the origin
	    Point3d center = new Point3d();
	    objBounds.getCenter(center);
	    offset.x = center.x;
	    offset.y = center.y;
	    offset.z = center.z;
	    if (debug) {
	    	System.out.println("Default view center:" + center +
			" distance " + radius);
	    }
	    objTrans.set(radius, offset);
	    objVpObjectTrans.setTransform(objTrans);

	}


    }

    void setupFileViewpoints() {
	if (fileViewpoints != null) {
	    MenuItem mi;
	    String desc;
	    fileVpWorldTrans = new TransformGroup[fileViewpoints.length];
	    fileVpOrientTrans = new TransformGroup[fileViewpoints.length];
	    fileVpTrans = new TransformGroup[fileViewpoints.length];
	    fileVp = new ViewPlatform[fileViewpoints.length];
	    for (int i = 0; i < fileViewpoints.length; i++) {
		// insert a transform above and below the Viewpoint's 
		// TransformGroup
		TransformGroup viewOri = fileViewpoints[i].getTransformGroup();
		viewOri.setCapability(BranchGroup.ALLOW_CHILDREN_READ);

		Group parent = (Group)viewOri.getParent();
		TransformGroup viewWorld = new TransformGroup();
		viewWorld.setCapability(viewWorld.ALLOW_TRANSFORM_READ);
		viewWorld.setCapability(viewWorld.ALLOW_TRANSFORM_WRITE);

		// Replace the TG by a new TG which will hold the ViewTG
		int numChildren = parent.numChildren();
		boolean found = false;
		for (int j = 0; (j < numChildren) && (!found); j++) {
		    if (parent.getChild(j) == viewOri) {
			found = true;
			parent.setChild(viewWorld, j);
		    }
		}
		if (!found) {
		    System.err.println("Internal error, can't find viewOri");
		}
		viewWorld.addChild(viewOri);

		TransformGroup viewBrowser = new TransformGroup();
		viewBrowser.setCapability(viewBrowser.ALLOW_TRANSFORM_READ);
		viewBrowser.setCapability(viewBrowser.ALLOW_TRANSFORM_WRITE);
		viewBrowser.setCapability(viewBrowser.ALLOW_CHILDREN_READ);
		viewBrowser.setCapability(viewBrowser.ALLOW_CHILDREN_WRITE);
		viewBrowser.setCapability(viewBrowser.ALLOW_CHILDREN_EXTEND);
		viewOri.setChild(viewBrowser, 0);
		viewBrowser.addChild(fileViewpoints[i].getViewPlatform());

		fileVpWorldTrans[i] = viewWorld;
		fileVpOrientTrans[i] = viewOri;
		fileVpTrans[i] = viewBrowser;
		fileVp[i] = fileViewpoints[i].getViewPlatform();

		if (((desc = fileViewpoints[i].getDescription()) == null) ||
							    desc.equals("")) {
		    desc = "Viewpoint " + i;
		}
		mi = new MenuItem(desc);
		mi.addActionListener(this);
		mi.setActionCommand(fileVpAction + i);
		fileVpMenu.add(mi);
	    }
	}
    }

    void setHeadlight(boolean state) {
	if (debug) System.out.println("setHeadlight(" + state + ")");
	ambLight.setEnable(state);
	headLight.setEnable(state);
    }

    private void updateBehavior() {
	BranchGroup bg = new BranchGroup();
	bg.setCapability(BranchGroup.ALLOW_DETACH);
        bg.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
	KeyBehavior kb = new KeyBehavior(this);
	kb.setSchedulingBounds(browserBounds);
	bg.addChild(kb);
	switch (viewMode) {
	  case EXAMINE:
	    MouseRotate mr = new MouseRotate(MouseRotate.INVERT_INPUT);
	    mr.setTransformGroup(vpWorldTrans);
	    mr.setSchedulingBounds(browserBounds);
	    bg.addChild(mr);
	    MouseTranslate mt = new MouseTranslate(MouseTranslate.INVERT_INPUT);
	    mt.setTransformGroup(vpWorldTrans);
	    mt.setSchedulingBounds(browserBounds);
	    bg.addChild(mt);
	    MouseZoom mz = new MouseZoom(MouseZoom.INVERT_INPUT);
	    mz.setTransformGroup(vpWorldTrans);
	    mz.setSchedulingBounds(browserBounds);
	    bg.addChild(mz);
	    break;
	  case FLY:
	    FlightBehavior fb = new FlightBehavior(0.0, 0.0, 0.0,
		0.0, 0.0, 0.0, vpTrans, canvas);
	    bg.addChild(fb);
	    break;
	}
	browserGroup.setChild(bg, BEHAVIOR);
    }

    void setMode(int mode) {
	viewMode = mode;
	updateBehavior();
    }

    synchronized void gotoUrl(String u) {
	try {
	    Node 	j3dNode;
	    VrmlScene 	scene;  // local var so VRML stuff will get gc'd
	    canvas.setCursor(new Cursor(Cursor.WAIT_CURSOR));
	    removeOldURL();
	    fileLoaded = false;
	    long startFree = 0;
            scene = (VrmlScene)loader.load(new URL(u));
	    if (debug || timing || loadOnly) {
		System.gc();
		System.out.println("After parse, freeMemory = " + 
		    numFormat.format(
			Runtime.getRuntime().freeMemory()/(1024*1024.0),2) +  
		    " totalMemory = " + 
		    numFormat.format(
			Runtime.getRuntime().totalMemory()/(1024*1024.0),2));
	    }
	    if (loadOnly) {
		System.exit(0);
	    }
	    BranchGroup sceneGroup = scene.getSceneGroup();
/*
	    if (debug) {
	       System.out.println("Adding subtree:");
	       tp.print(sceneGroup);
	       System.out.println("");
	    }
*/

	    // setting up file viewpoints modifies nodes in the Scene graph,
	    // so we need to do this before we compile/add
	    fileViewpoints = scene.getViewpoints(); 
	    if (fileViewpoints.length > 0) {
		setupFileViewpoints();
	    } 

	    // compile the branchgraph to optimize it
	    sceneGroup.compile();

	    // add the brachgraph to our object tree
	    objRoot.addChild(sceneGroup);

	    locale.addBranchGraph(objRoot);
	    objBounds = (BoundingSphere)objRoot.getBounds();
	    if (debug) {
		System.out.println("Object Bounds = " + objBounds);
		TransparencyAttributes sphereTransp = 
			new TransparencyAttributes(
				TransparencyAttributes.FASTEST, 0.95f);
		Appearance sphereApp = new Appearance();
		sphereApp.setTransparencyAttributes(sphereTransp);
		Sphere sphere = new Sphere((float)objBounds.getRadius(), 
				sphereApp);
		TransformGroup sphereTG = new TransformGroup();
		Point3d center = new Point3d();
		objBounds.getCenter(center);
		Vector3d sphereOffset = new Vector3d(center);
		Transform3D sphereXform = new Transform3D();
		sphereXform.setTranslation(sphereOffset);
		sphereTG.setTransform(sphereXform);
		sphereTG.addChild(sphere);
		BranchGroup sphereBG = new BranchGroup();
		sphereBG.addChild(sphereTG);
		objRoot.addChild(sphereBG);
	    }
	    if (timing) {
		startupTiming = true;
		startupCount = 0;
		postTime = System.currentTimeMillis();
	    }
	    fileLoaded = true;
	    setupObjViewpoints();

	    if ((fileViewpoints.length > 0) && (initVp == FILE_VIEW)) {
		setFileViewpoint(0);
	    } else {
		setObjViewpoint(OBJ_VIEW_PLUS_Z);
	    }

	    numTris = scene.getNumTris();
	    if (debug || timing) {
		System.out.println("Scene contains " + numTris + " triangles");
	    }
	    String desc = scene.getDescription();
	    if ((desc == null) || desc.equals("")) {
		desc = u.substring(u.lastIndexOf('/')+1);
	    }
	    setTitle("Vrml97 Viewer : " + desc);
        } catch (java.io.IOException e) {
            System.err.println("IO exception reading URL");
        } catch (com.sun.j3d.loaders.vrml97.InvalidVRMLSyntaxException e) {
            System.err.println("VRML parse error");
	    e.printStackTrace(System.err);
	}

	canvas.setCursor(new Cursor(Cursor.HAND_CURSOR));
    }

    void outputTiming() {
	// TODO: remove TimingCanvas3D and switch these to use view instead of
	// tc when these methods are implemented
	TimingCanvas3D tc = (TimingCanvas3D) canvas;
	long now = System.currentTimeMillis();
	long[] startTimes = new long[10];
	tc.getFrameStartTimes(startTimes);
	long lastFrameDuration = tc.getLastFrameDuration();
	int numFrames = (int)tc.getFrameNumber();

	if (numFrames > 10) numFrames = 10;
	double elapsed = (now - startTimes[numFrames-1]) / 1000.0;
	System.out.println("Last " + numFrames + " frames rendered in " +
		numFormat.format(elapsed, 1) + " seconds, " +
		numFormat.format(numFrames / elapsed, 2) + " frames/sec");
	System.out.println(numTris + " triangles/frame ");
	System.out.println("Overall rate: " +
		numFormat.format((numTris * numFrames) / (1000.0 * elapsed),0) +
		"K triangles/sec");
	System.out.println("Last frame rendered in " + 
		numFormat.format(lastFrameDuration / 1000.0, 3) + " seconds " +
		numFormat.format(1.0 * numTris / lastFrameDuration, 0) + 
		"K triangles/sec ");
    }

    public void postRender() {
	if (startupTiming) {
	    if ((startupCount++) == startupFrames) {
		TimingCanvas3D tc = (TimingCanvas3D) canvas;
		long[] startTimes = new long[startupFrames+1];
		long[] durations = new long[startupFrames+1];
		tc.getFrameStartTimes(startTimes);
		tc.getFrameDurations(durations);
		int first = startupFrames;
		System.out.println("Post to first start: " + 
		    numFormat.format((startTimes[first] - postTime)/1000.0,2));
		for (int i = 0; i < startupFrames; i++) {
		    double overall = 
			(startTimes[first-i-1] - startTimes[first-i]) / 1000.0;
		    double render = durations[first - i] / 1000.0;
		    double other = overall - render;
		    System.out.println("Frame " + i + 
			" overall time " + numFormat.format(overall, 3) + 
			" render time "  + numFormat.format(render,  3) +
			" other time "   + numFormat.format(other,   3));
		}
		startupTiming = false;
	    }
	}
	if (fileLoaded && ((numFrames++ % 10) == 0)) {
	    outputTiming();
	}
    }

    // MotionNotifierInteface
    public void viewMoved( Matrix4d position ) {}
    public void velocityChanged(double velocity) {}
    public void buttonPressed(int buttonId) {};

    public void processEvent(AWTEvent e) {
	if(e.getID() == WindowEvent.WINDOW_CLOSING){
	    dispose();
	    System.exit(0);
	}
    }

    public void setGotoString(String s) {
	gotoUrl.setText(s);
	validate();
    }
	

    public static void main(String[] args) {
    
	String urlString = null;
	if (args.length == 0) {
	    urlString = "file:" +
		System.getProperties().getProperty("user.dir") +
		"/parts/simple.wrl";
	} else {
	    if (args.length != 1) {
		System.out.println("Usage: Vrml97Viewer [<URL>]");
		System.exit(0);
	    } else {
		urlString = args[0];
	    }
	}

	Vrml97Viewer v97v = new Vrml97Viewer(urlString);
	v97v.setSize(780, 780);
	v97v.setupCanvas();
	v97v.gotoUrl(urlString);
    }

}

