/** * SunSphereCanvas maps an image onto a sphere.<p> * * Copyright &copy; 1997 Martin Minow. All Rights Reserved.<p> * * Permission to use, copy, modify, and redistribute this software and its * documentation for personal, non-commercial use is hereby granted provided that * this copyright notice and appropriate documentation appears in all copies. This * software may not be distributed for fee or as part of commercial, "shareware," * and/or not-for-profit endevors including, but not limited to, CD-ROM collections, * online databases, and subscription services without specific license.<p> * * @author <a href="mailto:minow@pobox.com">Martin Minow</a> * @version 1.0.2 *	Moved the actual map computation to a separate class. *	SunSphereCanvas is only a view into the data. * Set tabs every 8 characters. */package org.minow.applets.sunsphere;import java.awt.*;import java.awt.image.*;import java.awt.event.*;import java.util.*;import java.beans.*;public class SunSphereCanvas extends Component	implements	MouseListener,			MouseMotionListener{        protected static final int	preferredDiameter	= 72;    protected static final int	minimumDiameter		= 24;    protected SunSphere		sunSphereApplet;    protected SunSphereData	sunSphereData;    protected int		imageWidth		= 0;	/* Source width	    */    protected int		imageHeight		= 0;	/* Source height    */    protected boolean		isPaintUsable		= false;    protected Dimension		oldSize			= new Dimension();    private Cursor		savedCursor		= null;    private Cursor		handCursor		=    		Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);    private Cursor		grabCursor		=    		Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR);    private boolean		isGrabbed		= false;    private boolean		wasSpinning		= false;    private boolean		isCursorInSphere	= false;    private Rectangle		sphereBounds		= new Rectangle();    private int			sphereRadius		= 0;    private double		radiusSq		= 0.0;    private double[]		startLocation;    private double[]		newLocation;    private long		spinInstant;    /*     * Debug and performance measurement     */    public long			repaintDuration;    public static final double DegRad		= (Math.PI / 180.0);    /*     * Some Java implementations become very unhappy if they are called to     * paint an image while they're painting. This prevents the paint     * method from starting a new image while it's painting. (This may be     * due to an old bug that is no longer interesting.)     */    private int		recursion	= 0;    public SunSphereCanvas(    	    SunSphere		sunSphereApplet,	    int[]		imagePixels,	    int			imageWidth,	    int			imageHeight	)    {	super();	this.sunSphereApplet	= sunSphereApplet;	this.imageWidth		= imageWidth;	this.imageHeight	= imageHeight;	sunSphereData		= new SunSphereData(					sunSphereApplet,					imagePixels,					imageWidth,					imageHeight				);	addMouseListener(this);	addMouseMotionListener(this);	/*	 * For performance logging.	 */	setFont(new Font("SansSerif", Font.PLAIN, 9));    }    /**     * Called whenever the time changes.     */    public void setCurrentTime(	    long		currentTime	)    {	sunSphereData.setCurrentTime(currentTime);	redrawImage();    }    /**     * Called whenever the viewer moves the center of the image.     */    public void redrawImage()    {	if (isPaintUsable) {	    paint(getGraphics());	}    }    public void paint(	    Graphics		g	)    {    	/*    	 * For some strange reason, we get a null Graphics when    	 * run from the BeanBox.    	 */	if (recursion++ == 0 && g != null) {	    long repaintStart		= System.currentTimeMillis();	    Dimension	d		= getSize();	    int		sphereDiameter	= Math.min(d.width, d.height);	    if (d.width != oldSize.width || d.height != oldSize.height) {	    	g.clearRect(0, 0, d.width, d.height);	    	oldSize			= d;	    }	    Image sphereImage	= sunSphereData.getSphereImage(	    		sphereDiameter,			getBackground()		    );	    repaintDuration	= System.currentTimeMillis() - repaintStart;	    /*	     * Center the sphere in the drawing area.	     */	    int		x	= (d.width - sphereDiameter) / 2;	    int		y	= (d.height - sphereDiameter) / 2;	    sphereBounds.setBounds(x, y, sphereDiameter, sphereDiameter);	    sphereRadius	= sphereDiameter / 2;	    radiusSq		= (double) (sphereRadius * sphereRadius);	    g.drawImage(sphereImage, x, y, null);	    /*	     * This flag tells the setCurrentTime and setLocation methods	     * that they can call paint. It was needed because	     * the new (Java 1.1) threading model could call paint	     * from setCurrentTime/setLocation before the Component was	     * placed and located. When that happens, newDiameter	     *Ê(the minimum of height and width) will be zero	     *Êand the MemoryImageSource fails with an array index	     * out of range.	     */	    isPaintUsable	= true;	}	--recursion;    }    /*     * Implement the MouseListener interface.     */    public void mouseClicked(    	    MouseEvent		event    	)    {	try {	    double[] deltaCenter = getMouseLocation(event.getPoint());	    if (false) {	    	System.out.println("click delta: [" +	    		Utility.format(deltaCenter[0])	    		+ ", " + Utility.format(deltaCenter[1])	    		+ "]"	    	    );	    }	   /*	    * We succeeded, rotate the sphere to this location.	    */	   sunSphereApplet.rotate(deltaCenter[0], deltaCenter[1]);	}	catch (IllegalArgumentException e) {	    /* Ignore clicks outside of the sphere */	}    }    public void mouseEntered(    	    MouseEvent		event    	)    {    	savedCursor		= getCursor();    	isCursorInSphere	= false;    	isGrabbed		= true;		/* Force cursor redraw	*/	fixCursor(event.getPoint(), false);    }    public void mouseExited(    	    MouseEvent		event    	)    {    	setCursor(savedCursor);    	isCursorInSphere	= false;    }    public void mousePressed(    	    MouseEvent		event    	)    {	try {	    spinInstant		= System.currentTimeMillis();	    startLocation	= getMouseLocation(event.getPoint());	    wasSpinning		= sunSphereApplet.stopSpinningNow();	    fixCursor(event.getPoint(), true);	}	catch (IllegalArgumentException e) {}	    }    public void mouseReleased(    	    MouseEvent		event    	)    {	try {    	    double deltaTime		=    	    	(double) (System.currentTimeMillis() - spinInstant);    	    if (deltaTime <= 0.0) {    	    	deltaTime		= 1.0;    	    }	    double[] deltaLocation	= getDeltaLocation(event);	    double appletSpinTime	= (double) sunSphereApplet.getSpinInterval();    	    deltaTime			*= appletSpinTime;	    double deltaLatitude	= deltaLocation[0] / deltaTime;	    double deltaLongitude	= deltaLocation[1] / deltaTime;	    if (false) {		System.out.println(Utility.format(deltaTime) + " mouse time");		System.out.println(Utility.format(appletSpinTime) + " applet spin time");		System.out.println(Utility.format(deltaLongitude) + " deltaLongitude");	    }	    if (deltaLatitude != 0.0 || deltaLongitude != 0.0) {		if (sunSphereApplet.isLatitudeGrabOK() && deltaLatitude != 0.0) {		    sunSphereApplet.setLatitudeSpin(-deltaLatitude);		}		if (deltaLongitude != 0.0) {		    sunSphereApplet.setLongitudeSpin(-deltaLongitude);		}	    }	}	catch (IllegalArgumentException e) {}	fixCursor(event.getPoint(), false);	sunSphereApplet.setSpinning(wasSpinning);    }    public synchronized void mouseDragged(    	    MouseEvent		event    	)    {	spinInstant	= System.currentTimeMillis();	fixCursor(event.getPoint(), isGrabbed);	try {	    double[] deltaLocation 	= getDeltaLocation(event);	    if (deltaLocation[0] != 0.0 || deltaLocation[1] != 0.0) {		sunSphereApplet.rotate(-deltaLocation[0], -deltaLocation[1]);	    }	    startLocation = newLocation;	}	catch (IllegalArgumentException e) {}    }    /**     * getDeltaLocation is called after mouseDragged or mouseReleased     * events to the the amount of change in latitude and longitude.     * It throws an IllegalArgumentException if the mouse is outside     * of the sphere.     */    private double[] getDeltaLocation(    	    MouseEvent		event    	)    {	newLocation		= getMouseLocation(event.getPoint());	double[] deltaLocation	= new double[2];	deltaLocation[0]	= newLocation[0] - startLocation[0];	deltaLocation[1]	= newLocation[1] - startLocation[1];	double centerLatitude	= sunSphereApplet.getLatitude();	double normalizedLatitude = Utility.MOD(centerLatitude + 180.0, 360.0) - 180.0;	double trueLatitude	= newLocation[0] + normalizedLatitude;	if (sunSphereApplet.isLatitudeGrabOK() == false) {	    deltaLocation[0]	= 0.0;	}	if (deltaLocation[0] != 0.0 || deltaLocation[1] != 0.0) {	   /*	    * If the mouse latitude (after adjusting for the map	    * location) is "beyond" the pole (i.e. outside the range	    * [+90 .. -90]), rotate the globe in the opposite	    * direction.	    */	   if (trueLatitude > 90.0 || trueLatitude < -90.0) {	       deltaLocation[1]	= (-deltaLocation[1]);	   }    		   if (false) {		/*		 * Current center latitude		 * Mouse click latitude with respect to sphere center		 * Mouse click latitude with respect to center latitude		 * Actual delta latitude		 * True delta longitude		 * Actual delta longitude		 */		System.out.println(		    		 Utility.format(centerLatitude)		    	+ ", " + Utility.format(normalizedLatitude)		    	+ ", " + Utility.format(newLocation[0])		    	+ ", " + Utility.format(newLocation[1])		    	+ ", " + Utility.format(trueLatitude)		    	+ ", " + Utility.format(deltaLocation[0])		    	+ ", " + Utility.format(newLocation[1] - startLocation[1])		    	+ ", " + Utility.format(deltaLocation[1])		    );	    }	}	return (deltaLocation);        }    public void mouseMoved(    	    MouseEvent		event    	)    {	fixCursor(event.getPoint(), isGrabbed);    }        private void fixCursor(    	    Point		point,    	    boolean		isGrabbed    	)    {    	boolean isCursorInSphere	= isPointInSphere(point);    	if (this.isCursorInSphere != isCursorInSphere    	 || this.isGrabbed != isGrabbed) {    	    if (isCursorInSphere) {		setCursor((isGrabbed) ? grabCursor : handCursor);	    }	    else {	    	setCursor(savedCursor);	    }    	    this.isCursorInSphere	= isCursorInSphere;    	    this.isGrabbed		= isGrabbed;    	}    }    private boolean isPointInSphere(    	    Point		point    	)    {    	if (sphereBounds.contains(point) == false) {  	    return (false);  	}  	else {  	    double x		= point.x - sphereRadius;  	    double y		= point.y - sphereRadius;  	    double rhoSq	= (x * x) + (y * y);  	    return (rhoSq < radiusSq);  	}    }    /**     * Convert the mouse point to latitude/longitude with respect to     * the visual sphere center. Throws IllegalArgumentException if the     * point is outside the sphere boundaries. This value is not referenced     * to the latitude/longitude of the projected map. I.e. if the mouse     * is precisely on the center of the sphere, getMouseLocation always     * returns [0, 0].     *     * Throw an Illegal Argument Exception if the point is outside of     * the sphere.     */   public double[] getMouseLocation(   	    Point		mousePt   	)   { 	/* 	 * Convert the mouse point to a value between -radius and +radius 	 */	double result[]		= sunSphereData.mapToWorld(					mousePt.x - sphereBounds.x,					mousePt.y - sphereBounds.y				    ); 	return (result);    }    /*     * This is for debugging and performance analysis.     */    public String getPixelTime()    {		double imageSize	= sphereBounds.width * sphereBounds.height;	double microseconds	= repaintDuration * 1000;	double pixelTime;	try {	   pixelTime		= microseconds / imageSize;	}	catch (ArithmeticException e) {	   pixelTime = 0.0;	}	return (		Utility.format(repaintDuration, 4) + " msec. update, "		+ Utility.format(pixelTime) + " \u00B5/pixel"	    );    }        public Dimension getMinimumSize()    {	return (new Dimension(minimumDiameter, minimumDiameter));    }    public Dimension getPreferredSize()    {	int	diameter	= Math.min(imageWidth, imageHeight);	if (diameter <= 0) {	    diameter		= preferredDiameter;	}	return (new Dimension(diameter, diameter));//	System.out.println("Forcing small diameter");//	return (getMinimumSize());    }}