/*
 *      @(#)Evagation.java 1.31 99/03/01 14:33:18
 *
 * 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.
 */

package com.sun.j3d.loaders.vrml97.impl;

import javax.media.j3d.*;
import java.util.Enumeration;
import java.awt.event.*;
import java.awt.Point;
import java.awt.AWTEvent.*;
import java.awt.AWTEvent;
import java.awt.Frame;
import java.awt.Dimension;
import java.applet.Applet;
import java.util.*;
import java.awt.Event;
import java.awt.Point;
import java.awt.Cursor;
import java.net.*;
import javax.vecmath.*;
import vrml.InvalidVRMLSyntaxException;


/**
 * Evagation \Ev`a*ga*tion\, n  A wandering about, excursion, a roving.
 * (Source: Webster's Revised Unbriged Dictionary (1913))
 *
 * This is the event controller for the VRML runtime.  It dispatches events
 * from Java3D to the VRML Sensors and handles the walk/fly/examine Viewpoint
 * animation.
 */
public class Evagation extends Behavior {

    TransformGroup viewTrans = new TransformGroup();
    Browser browser;
    PickRay pickRay;
    WakeupOnCollisionEntry collisionEnter;
    WakeupOnCollisionExit collisionExit;
    WakeupCriterion[] criterion = new WakeupCriterion[7];
    Matrix3d rot = new Matrix3d();
    Matrix3d lastFreeRot = new Matrix3d();
    Matrix3d tmpDirection = new Matrix3d();
    Matrix3d zComp = new Matrix3d();
    Vector3d newDirection = new Vector3d();
    Transform3D positionTrans = new Transform3D();
    Matrix3d rollRot = new Matrix3d();
    Matrix3d pitchRot = new Matrix3d();
    Matrix3d yawRot = new Matrix3d();
    double rollAngle=0.0;
    double pitchAngle=0.0;
    double yawAngle=0.0;
    double velocity=0.0;
    double rollAngleDelta=0.0;
    double pitchAngleDelta=0.0;
    double yawAngleDelta=0.0;
    double directionX; // non destroyed direction vector components
    double directionY;
    double directionZ;
    double acceleration;
    static final double SPEED = .005;
    double speed = SPEED;
    double angularSpeed = SPEED/420.0;
    Transform3D currentView;   // Orientation and Translation
    Vector3d currentPosn;      // Translation
    Vector3d lastFreePosn = new Vector3d();
    Point mousePosn;
    Point3d eyePosn = new Point3d();
    double eyeZoffset = 0.0;
    double startX=0.0;
    double startY=0.0;
    double startZ=0.0;
    double lastT = 0.0;
    double deltaT = 0.0;
    static double MAXANG = Math.PI*2.0;
    int last_x, last_y;
    int first_x, first_y;
    int drag_dx, drag_dy;
    int eid;
    boolean initialized;
    // bug: first time causes null ptr exception
    boolean firstTime = true; // for resetWakeups
    boolean collided = false;
    // store the current dragsensor, so mouse
    // motion means something even if the mouse isn't over
    // the sensor until mouse released.
    String navMode;
    SphereSensor sceneExaminer=null;
    DragSensor curDragSensor;
    boolean dragIsSensor = false;

    // pick sphere stuff
    Transform3D tfm = new Transform3D();
    Transform3D tfmO = new Transform3D();
    javax.media.j3d.Appearance pickAppear;
    javax.media.j3d.TransparencyAttributes pickTransat;
    RGroup pickSphereHandle;
    TransformGroup pickSphereLocator = new TransformGroup();

    // Reusable vars
    Point3d p1 = new Point3d();
    Point3d p2 = new Point3d();

    public Evagation(Browser initBrowser) {
	browser = initBrowser;
	initialized = false;
	collided = false;
	currentView = new Transform3D();
	currentView.setIdentity();
	pickRay = new PickRay();
    }

    public void initialize() {
      	initialize(new BoundingSphere(new Point3d(),1000.0),
		   new Vector3d(startX,startY,startZ));

    }

    public void initialize( BoundingSphere bounds, Vector3d currentPosn ) {

        Transform3D initTransform = new Transform3D();
	setSchedulingBounds(bounds);
        this.currentPosn = currentPosn;

        currentView.setTranslation( currentPosn );

        rollAngle=0.0;
        pitchAngle=0.0;
        rollAngleDelta=0.0;
        pitchAngleDelta=0.0;
        yawAngleDelta=0.0;
        yawAngleDelta=0.0;
        velocity=0;

	initialized = true;
	resetWakeups();
    }

    public void setViewGroup(TransformGroup newViewTrans) {
	viewTrans = newViewTrans;
	
	// getBounds only works with Morph and Shape now
	//Bounds b = viewTrans.getBounds();
	Bounds b = new BoundingSphere();
	if ( browser.curNavInfo == null ) {
	    browser.curNavInfo = new NavigationInfo(browser.loader);
	} else {
	    double sizex = browser.curNavInfo.avatarSize.mfloat[0]/2.0f;
	    double sizey;
	    if (browser.curNavInfo.avatarSize.mfloat.length > 1) {
		sizey = browser.curNavInfo.avatarSize.mfloat[1]/2.0f;
	    } else {
		sizey = browser.curNavInfo.avatarSize.mfloat[0]/2.0f;
	    }
	    double sizez;
	    if (browser.curNavInfo.avatarSize.mfloat.length > 2) {
	        sizez = browser.curNavInfo.avatarSize.mfloat[2]/2.0f;
	    } else {
	        sizez = browser.curNavInfo.avatarSize.mfloat[0]/2.0f;
	    }
	    b = new BoundingBox(new Point3d(-sizex,-sizey,-sizez), 
		new Point3d(sizex,sizey,sizez));
	    navMode = browser.curNavInfo.type.strings[0];
	    if (!navMode.equals("EXAMINE") && !navMode.equals("WALK")) {
		navMode = "WALK";
	    }
	} //else b = new BoundingSphere();
	speed = browser.curNavInfo.speed.value * SPEED;
	viewTrans.setCollisionBounds(b);
	viewTrans.setBounds(b);
	viewTrans.getTransform(currentView);
	Vector3d vc = new Vector3d();
	currentView.get(vc);
	startX = vc.x;
	startY = vc.y;
	startZ = vc.z;

        System.out.println("navMode: " + navMode);
	if ( sceneExaminer != null && navMode.equals("WALK")) {
	    try {
		browser.deleteRoute(sceneExaminer,"rotation",
			browser.curSceneT,"rotation");
	    } catch (Exception e) {e.printStackTrace();}
	    browser.curViewpoint.updateViewTrans();
	}		
	if ( navMode.equals("EXAMINE")) {
	    // bug: it can sometimes happen that in examine mode with
	    // no viewpoint specified that the sceneExaminer is null when
	    // it actually wasn't expected 
	    if ( browser.sceneExaminer == null ) {
		browser.sceneExaminer = 
			new SphereSensor(browser.loader);
	        try {
	            browser.sceneExaminer.updateParent(browser.curSceneT.impl);
		} catch (NullPointerException npe) {;}
	    }
	    sceneExaminer = browser.sceneExaminer;
	    try {
	        browser.addRoute(sceneExaminer,"rotation",
			browser.curSceneT,"rotation");
	    } catch (Exception e) {e.printStackTrace();} // will null first time
	}
	//resetWakeups();
    }

    void resetWakeups() {
	//if ( !firstTime ) {
	    SceneGraphPath viewPath = 
		new SceneGraphPath(browser.locale,viewTrans);
	    //collisionEnter = new WakeupOnCollisionEntry(viewPath);
	    //collisionExit = new WakeupOnCollisionExit(viewPath);
	    //criterion[0] = collisionEnter;  
	    //criterion[1] = collisionExit;
	    criterion = new WakeupCriterion[7];
	    criterion[0] = new WakeupOnAWTEvent(MouseEvent.MOUSE_MOVED) ;
	    criterion[1] = new WakeupOnAWTEvent(MouseEvent.MOUSE_PRESSED);
	    criterion[2] = new WakeupOnAWTEvent(MouseEvent.MOUSE_DRAGGED);
	    criterion[3] = new WakeupOnAWTEvent(MouseEvent.MOUSE_CLICKED);
	    criterion[4] = new WakeupOnAWTEvent(MouseEvent.MOUSE_RELEASED);
	    criterion[5] = new WakeupOnAWTEvent(KeyEvent.KEY_RELEASED);
	    criterion[6] = new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED);

	    wakeupOn( new WakeupOr( criterion ));
	//} else { firstTime = false; }
	firstTime=false;
    }


    public void processStimulus( Enumeration critter ) {
	WakeupCriterion wakeup;
	while ( critter.hasMoreElements() ) {
	    wakeup = (WakeupCriterion)critter.nextElement();
	    //if ( wakeup instanceof WakeupOnCollisionEntry ) {
		//collided = true;
	        //currentView.setRotationScale( lastFreeRot );
	        //currentView.setTranslation( lastFreePosn );
	        //resetWakeups();
		//System.out.println("collided");
	    //} else if ( wakeup instanceof WakeupOnCollisionExit ) {
		//collided = false;
		//System.out.println("exit");
	        //resetWakeups();
	    //} else if ( wakeup instanceof WakeupOnAWTEvent ) {
	  	AWTEvent[] awts = ((WakeupOnAWTEvent)wakeup).getAWTEvent();
		for (int i = 0; i<awts.length; i++) {
			processEvent(awts[i]);
		}
	    //}
			
	    //resetWakeups();
	}
	resetWakeups();
    }
	    

    void processEvent(AWTEvent evt) {
	if (!initialized) {
	    System.out.println("Event before behavior is initialized");
	    return;
	}
	if (evt instanceof KeyEvent) {
		processKeyEvent( (KeyEvent) evt );
	} else if (evt instanceof MouseEvent) {
		processMouseEvent( (MouseEvent) evt );
	} else if (evt instanceof FocusEvent)
		processFocusEvent( (FocusEvent)evt);
    }


    public void processMouseEvent( MouseEvent evt ) {

	eid=evt.getID();
	boolean mmb = ((evt.getModifiers() & MouseEvent.BUTTON2_MASK)>0);
	switch(eid) {
	 case MouseEvent.MOUSE_DRAGGED:
	   mousePosn = evt.getPoint();

	   drag_dx = mousePosn.x - first_x;
	   drag_dy = mousePosn.y - first_y;
	   last_x = mousePosn.x;
	   last_y = mousePosn.y;

	   // this must be here, and really want to do this in vworld coords
	   if ( drag_dx * drag_dx + drag_dy * drag_dy > 100 )
	       mouseDragged(evt.getWhen(), drag_dx, drag_dy, first_x, first_y, mmb);

	   if (dragIsSensor)
		velocity = 0;
	   else {
	       pitchAngleDelta = -drag_dx *angularSpeed;
	       if (evt.isShiftDown())
		    yawAngleDelta = -drag_dy*angularSpeed;
	       else 
		    velocity = (drag_dy)*speed;
	   }

	   break;
	 case MouseEvent.MOUSE_CLICKED:
	   mousePosn = evt.getPoint();	
	   first_x=last_x=mousePosn.x;
	   first_y=last_y=mousePosn.y;
	   mouseClicked(evt.getWhen(),last_x,last_y,mmb);
	   break;
	 case MouseEvent.MOUSE_PRESSED:
	   mousePosn = evt.getPoint();	
	   first_x=mousePosn.x;
	   first_y=mousePosn.y;
	   break;
	 case MouseEvent.MOUSE_RELEASED:
	   clearDragState();
	   curDragSensor = null;
	   dragIsSensor = false;
	   break;
	 default:
	   break;	
	}
	update();

   }
   private void processKeyEvent( KeyEvent evt ) {
	int key;
	double ROT_ANGLE=0.03;
	double VELOCITY=10.0;
	key = evt.getKeyCode();
        if(evt.getID() == KeyEvent.KEY_RELEASED ) {
	    velocity = 0.0;
	    rollAngleDelta = 0.0;
	    pitchAngleDelta = 0.0;
	    yawAngleDelta = 0.0;
        }

	// todo: make a switch statement instead
	if(evt.getID() == KeyEvent.KEY_PRESSED) {
	  if (key == KeyEvent.VK_UP) {
		velocity = -VELOCITY;
	  } else if ( key == KeyEvent.VK_DOWN) {
		velocity = VELOCITY;
	  } else if ( key == KeyEvent.VK_LEFT) {
		pitchAngleDelta += ROT_ANGLE;
	  } else if ( key == KeyEvent.VK_RIGHT) {
		pitchAngleDelta -= ROT_ANGLE;
	  } else if ( key == KeyEvent.VK_S ) {
		resetViewpoint();
	  } else if (key==KeyEvent.VK_C ) {
		pitchAngle=0.0;
		pitchAngleDelta=0.0;
		rollAngle=0.0;
		rollAngleDelta=0.0;
		velocity=0.0;
	  } else if (key==KeyEvent.VK_D ) {
		browser.treePrinter.print(browser.curScene);
	  } else if (key==KeyEvent.VK_SLASH) {
                browser.outputTiming();
          } else if (key==KeyEvent.VK_ESCAPE) {
                browser.outputTiming();
                browser.shutDown();
          } else if (key==KeyEvent.VK_PAGE_UP) {
		yawAngleDelta = 2.0*SPEED;
	  } else if (key==KeyEvent.VK_PAGE_DOWN) {
		yawAngleDelta = -2.0*SPEED;
	  }
	}
	calcTransform();
	viewTrans.setTransform( currentView  );

   }
   private void processFocusEvent(FocusEvent evt) {
	if ( evt.getID() == FocusEvent.FOCUS_GAINED ) {
		//System.out.println("XAutoRepeatOn(display) ... please ");
	} else if ( evt.getID() == FocusEvent.FOCUS_LOST ) {	
		//System.out.println("XAutoRepeatOff(display) ... please ");
	}
   }

   void forceUpDown() {
        curDragSensor = null;
        clearDragState();
        first_x=last_x=mousePosn.x;
        first_y=last_y=mousePosn.y;
	mouseDragged(Time.getNow(), drag_dx, drag_dy, first_x, first_y, false);

   } 

   void clearDragState() {
	drag_dx=drag_dy=0;
	first_x=first_y=-1;
	rollAngleDelta = pitchAngleDelta = yawAngleDelta = 0.0;
	velocity = 0.0;
	if(curDragSensor != null) {
	    curDragSensor.offset();
	}    
   }

   private void calcTransform() {
	//if (!collided) {
	    positionTrans.set( velocity*deltaT );
	    zComp.setIdentity();
	    zComp.setRow(2, 0,0,1 );
    
	    rot.setIdentity();
	    rot.mul( rollRot );
	    rot.mul( pitchRot );
	    rot.mul( yawRot );

	    tmpDirection.mul( rot, zComp );
	    newDirection.x = directionX = tmpDirection.m02;
	    newDirection.y = directionY = tmpDirection.m12;
	    newDirection.z = directionZ = tmpDirection.m22;
	
	    positionTrans.transform( newDirection );
	    currentPosn.add( newDirection );

	    rollAngle=Math.IEEEremainder(rollAngle+rollAngleDelta,Math.PI*2.0);
	    pitchAngle=Math.IEEEremainder(pitchAngle+pitchAngleDelta,
		Math.PI*2.0);
	    yawAngle=Math.IEEEremainder(yawAngle+yawAngleDelta,Math.PI*2.0);

	    rollRot.rotZ(rollAngle );
	    pitchRot.rotY(pitchAngle );
	    yawRot.rotX(yawAngle );

	    currentView.setRotationScale( rot );
	    currentView.setTranslation( currentPosn );
	//} else {
	    //currentView.setRotationScale( lastFreeRot );
	    //currentView.setTranslation( lastFreePosn );
	//}
    }

    public void simTick(double now) {
	// it is not specified when the event acutally happens, so
	// that is why collided gets checked all the time
	if(!collided) {
	    deltaT = now-lastT;
	    lastFreeRot.set(rot);
	    lastFreePosn.set(currentPosn);
 	} else {
	    if(browser.debug)System.out.println("collided");
	    deltaT = 0.0; // this effectively freezes the viewplatfrom
	    // upon collision. it is not entirely the desired effect
	    //deltaT = now-lastT;
	} 
	lastT = now;	
	update();	
	// This is an experimental drag-n-hold feature. Uncomment to enable
	// the SphereSensor SFBool autoSpin from within a file
	//if ( curDragSensor != null )
	    //curDragSensor.simTick(now);
    }

    void update() {
	if(initialized) {
	    calcTransform();
	    // any new collision info should happen after this call
	    if ((currentView.getType() & Transform3D.CONGRUENT) != 0)
	       viewTrans.setTransform( currentView );
	}
    }

    public void resetViewpoint() {
	rollAngle=0.0;
	pitchAngle=0.0;
	yawAngle=0.0;
	rollAngleDelta=0.0;
	pitchAngleDelta=0.0;
	yawAngleDelta=0.0;
	velocity=0;
	currentPosn = new Vector3d( startX,startY,startZ );
    }

    private PickRay getPickRay(int xpos, int ypos) {
	Transform3D motion=new Transform3D();
	Point3d mousePosn = new Point3d();
	Vector3d mouseVec=new Vector3d();

	browser.canvas.getCenterEyeInImagePlate(eyePosn);
	browser.canvas.getPixelLocationInImagePlate(xpos,ypos,mousePosn);
	browser.canvas.getImagePlateToVworld(motion);

	//if(navMode.equals("EXAMINE")) eyeZoffset = eyePosn.z;
	//else eyeZoffset=0.0;
	eyeZoffset = eyePosn.z;

	motion.transform(eyePosn);
	motion.transform(mousePosn);
	mouseVec.sub(mousePosn, eyePosn);
	mouseVec.normalize();


	pickRay.set(eyePosn, mouseVec);


	return pickRay;
    }

    private void pickEcho(javax.media.j3d.Node step, SceneGraphPath path) {
	try {
	    javax.media.j3d.Bounds b = step.getBounds();
	    if( b instanceof javax.media.j3d.BoundingSphere ) {
		Point3d p = new Point3d();
		((BoundingSphere)b).getCenter(p);
		try {
		   step.getLocalToVworld(this.tfm);
		} catch (IllegalSharingException e) { 
		   try {
		      step.getLocalToVworld(path, this.tfm);
		   } catch ( Exception f ) { /*f.printStackTrace();*/; }
		}
		this.tfmO.set(new Vector3d(p));
		this.tfm.mul(this.tfmO);
		com.sun.j3d.utils.geometry.Sphere sp = 
		      new com.sun.j3d.utils.geometry.Sphere(
			(float)((BoundingSphere)b).getRadius());
		attachPickSphere(sp);
	    } else {
		if (browser.debug) {
		    System.out.println("node bounds are not " +
			"BoundingSphere");
		}
	    }
	// node might not have capability to read bounds
	} catch (Exception ed) { 
	    System.out.println("Exception trying to echo pick");
	    ed.printStackTrace();
	}
    }


    void mouseClicked(double when, int x, int y, boolean mmb) {

        if (browser.debug) {
	    System.out.println("Evagation.mouseClicked() called");
	}
	// all touch events should have the same timestamp.  This will
        // prevent multiple events on the same sensor from the same pick
        double touchTime = Time.getNow();
        browser.beginRoute(touchTime);

	SceneGraphPath[] stuffPicked = 
		browser.curScene.pickAllSorted(getPickRay(x,y));
	boolean ts_done=false;

	if ( stuffPicked != null ) {
	    if (browser.debug) {
		System.out.println("stuffPicked.length="+ stuffPicked.length);
	    }
	    for(int i = 0; i < stuffPicked.length; i++) {
		if (browser.debug) {
		    System.out.println("stuffPicked[" + i + "] is "+ 
				stuffPicked[i]);
		}
		VrmlSensor vs = (VrmlSensor)null;
		Vector sv = (Vector)null;
		javax.media.j3d.Node step = null;
		if (browser.debug) {
		    System.out.println("stuffPicked[" + i + "].nodeCount() = " +
			 stuffPicked[i].nodeCount());
		}
		for(int j = 0; j<stuffPicked[i].nodeCount(); j++) {
		    step = stuffPicked[i].getNode(j);
		    if (browser.debug || browser.pickEcho) {
			pickEcho(step, stuffPicked[i]);
		    }
		    Object o = step.getUserData();
		    if (browser.debug && j==0) {
			System.out.println(i+" "+j+"Picked node is " + 
				step + " user data is " + o);
			System.out.println(stuffPicked[i].getObject());
		    }
		    if( o instanceof Vector && !((Vector)o).isEmpty()) {
			// save the bottom most to preserve nesting
			// note, the userData should be a vector
			// of sensors for this group. 
			sv = (Vector)o;
		    // rare case
		    } else if ( o instanceof SphereSensor ) {
			sv = new Vector();
			sv.addElement(o);
			step.setUserData(sv);
		    }
		}
		    

		if (sv != null) {
		    Enumeration e = sv.elements();
		    while(e.hasMoreElements()) {
		        vs = (VrmlSensor)(e.nextElement());
		        if (vs instanceof TouchSensor && !ts_done) {
			    TouchSensor ts = (TouchSensor) vs;
			    if(ts.enabled.value == true) {
				if (browser.debug) {
				    System.out.println("Calling setValue on " +
                                       "touchTime for sensor " +
                                        ts.toStringId());
				}
			        ts.touchTime.setValue(touchTime);
                                ts_done = true;
			    }
		        } else if (vs instanceof DragSensor && !mmb) {
			    // p1 and p2 are same point, its a click
			    //browser.canvas.getPixelLocationInImagePlate(x,y,p1);
			    //browser.canvas.getPixelLocationInImagePlate(x,y,p2);
			    //((DragSensor)vs).update(p1,p2,step,stuffPicked[i]);
			    //dragIsSensor = true;
			    //curDragSensor = (DragSensor)vs;
			} else if (vs instanceof Anchor) {
			    String toGoto = ((Anchor)vs).url.strings[0];
			    try {
				browser.canvas.setCursor( 
					new Cursor(Cursor.WAIT_CURSOR));
				browser.loadURL(((Anchor)vs).url.strings,
					(String[])null);
			    } catch ( java.io.IOException ioe ) {
				System.err.println("IO exception reading URL from Ancor ");
			    } catch (InvalidVRMLSyntaxException ivse) {
				System.err.println("VRML parse error");
			    }
			    String status = browser.getName() + " : " + 
				browser.getDescription();
			    if(browser.container instanceof Applet) {
				if(toGoto.endsWith(".wrl")) 
				    ((Applet)(browser.container)).showStatus( 
					status );
				else try {
				    ((Applet)(browser.container)).
					getAppletContext().showDocument(
					new URL(toGoto));
				} catch ( MalformedURLException me ) {
					((Applet)(browser.container)).
					showStatus( me.toString() );
				}
			    } else if (browser.container instanceof Frame ) {
				((Frame)(browser.container)).setTitle(status);
				((com.sun.j3d.loaders.vrml97.Player)
				 (browser.container)).setGotoString(toGoto);
			    }
			    browser.canvas.setCursor(new 
				Cursor(Cursor.HAND_CURSOR));
			}
		    }
		}
	    }
	}

        browser.endRoute();
    }

    void mouseDragged(double when, int dx, int dy, int x, int y, boolean mmb) {
        // bug: if a click sensor and drag sensor are in same group, 
        // drag never autoRotates.
	browser.beginRoute(Time.getNow());
	if (curDragSensor != null && !mmb) { 
	    // dragsensors node reference must have already been set
	    browser.canvas.getPixelLocationInImagePlate(x,y,p1);
	    browser.canvas.getPixelLocationInImagePlate(x+dx,y+dy,p2);
	    p1.z = p2.z = eyeZoffset;
	    curDragSensor.update(p1,p2,null,null);
	} else {
	    SceneGraphPath[] stuffDragged = 
		browser.curScene.pickAllSorted(getPickRay(x,y));
	    if ( stuffDragged != null ) {
	        for(int i = 0; i < stuffDragged.length; i++) {
		    VrmlSensor vs = (VrmlSensor)null;
		    Vector sv = (Vector)null;
		    javax.media.j3d.Node step = null;
		    for(int j = 0; j<stuffDragged[i].nodeCount(); j++) {
		      try {
		        step = stuffDragged[i].getNode(j);
		        Object o = step.getUserData();
		        if ((browser.debug && j==0)) {
			    System.out.println(i+"."+j+": Picked node is " + 
				    step + " user data is " + o);
		        }

		        if( o instanceof Vector && !((Vector)o).isEmpty()) {
			    // save the bottom most to preserve nesting
			    // note, the userData should be a vector
			    // of sensors for this group. 
			    sv = (Vector)o;
			    // the scneExaminer should be the exception
			    if ( navMode.equals("EXAMINE") )
				j = stuffDragged[i].nodeCount(); // break;
		        } 
		      // node might not have capability to read bounds
		      } catch (Exception ed) { ed.printStackTrace();}
		    }
		    if (sv != null) {
		        Enumeration e = sv.elements();
		        while(e.hasMoreElements()) {
		            vs = (VrmlSensor)(e.nextElement());
		            if (vs instanceof DragSensor && !mmb && (navMode.equals("EXAMINE") || vs != sceneExaminer)) {
				browser.canvas.getPixelLocationInImagePlate(x,y,p1);
				browser.canvas.getPixelLocationInImagePlate(x+dx,y+dy,p2);
				p1.z = p2.z = eyeZoffset;
				// this is the first drag segment, 
				// The sensor may need to inspect the stuffDragged path 
				// to get the transform above.
			        ((DragSensor)vs).update(p1,p2,step,stuffDragged[i]);
			        dragIsSensor = true;
			        curDragSensor = (DragSensor)vs;
			    } else {
			        curDragSensor = null;
		  	    }
		        }	
		    }
	        }	
	    }
	}
	browser.endRoute();
    }
    
    void attachPickSphere(com.sun.j3d.utils.geometry.Sphere sp) {
	if( !pickSphereLocator.isLive() ) {
	    try {
	        pickSphereLocator.removeChild(0);
	    } catch (ArrayIndexOutOfBoundsException aioobe) {;}	    

	    pickSphereHandle = new RGroup();
	    pickSphereLocator = new TransformGroup();
	    pickSphereHandle.addChild(pickSphereLocator);
	    pickTransat = new javax.media.j3d.TransparencyAttributes(
		TransparencyAttributes.FASTEST, 0.5f);
	    pickTransat.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE);
	    pickTransat.setCapability(TransparencyAttributes.ALLOW_VALUE_READ);
	    pickAppear = new javax.media.j3d.Appearance();
	    pickAppear.setTransparencyAttributes(pickTransat);
	    // sphere should have been already created the right size
	    sp.setAppearance(pickAppear);
	    pickSphereLocator.addChild(sp);
	    pickSphereLocator.addChild(new 
	        PickSphereTimer(browser,pickSphereHandle,pickTransat));
	    pickSphereLocator.setTransform(this.tfm);
	    browser.locale.addBranchGraph(pickSphereHandle);
	}
    }

}
