aboutsummaryrefslogtreecommitdiffstats
path: root/demos/MiscDemos/jahuwaldt/gl/VirtualSphere.java
blob: 2ec79174dc195fcaf06243e938465d82c838d08a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
/*
*   VirtualSphere -- A class that implements the Virtual Sphere algorithm for 3D rotation.
*   
*   Copyright (C) 2001 by Joseph A. Huwaldt <jhuwaldt@gte.net>.
*   All rights reserved.
*   
*   This library is free software; you can redistribute it and/or
*   modify it under the terms of the GNU Library General Public
*   License as published by the Free Software Foundation; either
*   version 2 of the License, or (at your option) any later version.
*   
*   This library 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
*   Library General Public License for more details.
**/

package jahuwaldt.gl;


import java.awt.*;


/**
*  Implements the Virtual Sphere algorithm for 3D rotation using a 2D input device.
*  See paper "A Study in Interactive 3-D Rotation Using 2-D Control Devices" by
*  Michael Chen, S. Joy Mountford and Abigail Sellen published in the ACM Siggraph '88
*  proceedings (Volume 22, Number 4, August 1988) for more detail.  The code here
*  provides a much simpler implementation than that described in the paper.
*  This is also known by as the "virtual track ball" or "cue ball" interface.
*
*  <p>  Ported from C to Java by Joseph A. Huwaldt, February 19, 2001  </p>
*  <p>  Original C version had the following comments:
*           Author: Michael Chen, Human Interface Group / ATG,
*           Copyright � 1991-1993 Apple Computer, Inc.  All rights reserved.
*           Part of Virtual Sphere Sample Code Release v1.1.
*  </p>
*
*  <p>  Modified by:  Joseph A.Huwaldt  </p>
*
*  @author:  Joseph A. Huwaldt   Date:  Feburary 19, 2001
*  @version  February 20, 2001
**/
public class VirtualSphere {

	// Some constants for convenience.
	private static final int X = 0;
	private static final int Y = 1;
	private static final int Z = 2;
	
	/**
	*  Storage for 3D point information passed between methods.
	**/
	private final float[] op = new float[3], oq = new float[3], a = new float[3];
	
	/**
	*  Calculate a rotation matrix based on the axis and angle of rotation
	*  from the last 2 locations of the mouse relative to the Virtual
	*  Sphere cue circle.
	*
	*  @param  pnt1       The 1st mouse location in the window.
	*  @param  pnt2       The 2nd mouse location in the window.
	*  @param  cueCenter  The center of the virtual sphere in the window.
	*  @param  cueRadius  The radius of the virtual sphere.
	*  @param  rotMatrix  Preallocated rotation matrix to be filled in
	*                     by this method.  Must have 16 floating point
	*                     elements.  This matrix will be overwritten by
	*                     this method.
	*  @return A reference to the input rotMatrix is returned with the elements filled in.
	**/
	public float[] makeRotationMtx(Point pnt1, Point pnt2, Point cueCenter, int cueRadius,
									float[] rotMatrix) {
	
		// Vectors op and oq are defined as class variables to avoid wastefull memory allocations.
		
		// Project mouse points to 3-D points on the +z hemisphere of a unit sphere.
		pointOnUnitSphere (pnt1, cueCenter, cueRadius, op);
		pointOnUnitSphere (pnt2, cueCenter, cueRadius, oq);
		
		/* Consider the two projected points as vectors from the center of the 
		*  unit sphere. Compute the rotation matrix that will transform vector
		*  op to oq.  */
		setRotationMatrix(rotMatrix, op, oq);
		
		return rotMatrix;
	}
	
	
	/**
	*  Draw a 2D representation of the virtual sphere to the specified graphics context.
	*  The representation includes a circle for the perimiter of the virtual sphere
	*  and a cross-hair indicating where the mouse pointer is over the virtual sphere.
	*
	*  @param gc         The graphics context to be drawn into.
	*  @param mousePnt   The location in window of the mouse.
	*  @param cueCenter  The location in the window of the center of the virtual sphere.
	*  @param cueRadius  The radius (in pixels) of the virtual sphere in the window.
	**/
	public void draw(Graphics gc, Point mousePnt, Point cueCenter, int cueRadius) {
		
    	// Draw the outline of the virtual sphere.
    	int x = cueCenter.x - cueRadius;
    	int y = cueCenter.y - cueRadius;
    	int diameter = 2*cueRadius;
    	gc.drawOval(x, y, diameter, diameter);
    		
		int mouseX = mousePnt.x - cueCenter.x;
		int mouseY = cueCenter.y - mousePnt.y;
		int mouseX2 = mouseX*mouseX;
		int mouseY2 = mouseY*mouseY;
		int cueRadius2 = cueRadius*cueRadius;
			
		// Draw the mouse cue if we are over the sphere.
		if (mouseX2 + mouseY2 < cueRadius2) {

			// Draw the vertical cue line.
			if (Math.abs(mouseX) > 4) {
				//	Draw a vertical elliptical arc through the mouse point.
				double a = Math.sqrt(mouseX2/(1 - mouseY2/(float)cueRadius2));
				double newMouseX = mouseX*cueRadius/a;
				int angle = (int)(Math.atan2(mouseY, newMouseX)*180/Math.PI);
				int width = (int)(2*a);
				int nx = cueCenter.x - (int)a;
				gc.drawArc(nx, y, width, diameter, angle - 10, 20);
			
			} else {
				//	Mouse X near zero is a special case, just draw a vertical line.
				float vy = mouseY/(float)cueRadius;
				float vy2 = vy*vy;
				double vz = Math.sqrt(1 - vy2);
				double angle = Math.atan2(vy, vz) - 10*Math.PI/180;
				double length = Math.sqrt(vy2 + vz*vz)*cueRadius;
				int yl = cueCenter.y - (int)(length*Math.sin(angle));
				int yh = cueCenter.y - (int)(length*Math.sin(angle + 20*Math.PI/180));
				gc.drawLine(mousePnt.x, yl, mousePnt.x, yh);
			}
			
			//	Draw the horizontal cue line.
			if (Math.abs(mouseY) > 4) {
				//	Draw a horizontal elliptical arc through the mouse point.
				double a = Math.sqrt(mouseY2/(1 - mouseX2/(float)cueRadius2));
				double newMouseY = mouseY*cueRadius/a;
				int angle = (int)(Math.atan2(newMouseY, mouseX)*180/Math.PI);
				int width = (int)(2*a);
				y = cueCenter.y - (int)a;
				gc.drawArc(x, y, diameter, width, angle - 10, 20);
				
			} else {
				//	Mouse Y near zero is a special case, just draw a horizontal line.
				float vx = mouseX/(float)cueRadius;
				float vx2 = vx*vx;
				double vz = Math.sqrt(1 - vx2);
				double angle = Math.atan2(vx, vz) - 10*Math.PI/180;
				double length = Math.sqrt(vx2 + vz*vz)*cueRadius;
				int xl = cueCenter.x + (int)(length*Math.sin(angle));
				int xh = cueCenter.x + (int)(length*Math.sin(angle + 20*Math.PI/180));
				gc.drawLine(xl, mousePnt.y, xh, mousePnt.y);
			}
		}
	}
	
	/**
	*  Project a 2D point on a circle to a 3D point on the +z hemisphere of a unit sphere.
	*  If the 2D point is outside the circle, it is first mapped to the nearest point on
	*  the circle before projection.
	*  Orthographic projection is used, though technically the field of view of the camera
	*  should be taken into account.  However, the discrepancy is neglegible.
	*
	*  @param  p         Window point to be projected onto the sphere.
	*  @param  cueCenter Location of center of virtual sphere in window.
	*  @param  cueRadius The radius of the virtual sphere.
	*  @param  v         Storage for the 3D projected point created by this method.
	**/
	private static void pointOnUnitSphere(Point p, Point cueCenter, int cueRadius, float[] v) {
		
		/* Turn the mouse points into vectors relative to the center of the circle
	 	*  and normalize them.  Note we need to flip the y value since the 3D coordinate
	 	*  has positive y going up.  */
		float vx = (p.x - cueCenter.x)/(float)cueRadius;
		float vy = (cueCenter.y - p.y)/(float)cueRadius;
		float lengthSqared = vx*vx + vy*vy;
		
		/* Project the point onto the sphere, assuming orthographic projection.
	 	*  Points beyond the virtual sphere are normalized onto 
	 	*  edge of the sphere (where z = 0).  */
	 	float vz = 0;
		if (lengthSqared < 1)
			vz = (float)Math.sqrt(1.0 - lengthSqared);
			
		else {
			float length = (float)Math.sqrt(lengthSqared);
			vx /= length;
			vy /= length;
		}
		
		v[X] = vx;
		v[Y] = vy;
		v[Z] = vz;
	}
	
	/**
	*  Computes a rotation matrix that would map (rotate) vectors op onto oq.
	*  The rotation is about an axis perpendicular to op and oq.
	*  Note this routine won't work if op or oq are zero vectors, or if they
	*  are parallel or antiparallel to each other.
	*
	*  <p>  Modification of Michael Pique's formula in 
	*       Graphics Gems Vol. 1.  Andrew Glassner, Ed.  Addison-Wesley.  </p>
	*
	*  @param  rotationMatrix  The 16 element rotation matrix to be filled in.
	*  @param  op              The 1st 3D vector.
	*  @param  oq              The 2nd 3D vector.
	**/
	private void setRotationMatrix(float[] rotationMatrix, float[] op, float[] oq) {
		
		// Vector a is defined as a class variable to avoid wastefull memory allocations.

		GLTools.crossProduct3D(op, oq, a);
		float s = GLTools.length3D(a);
		float c = GLTools.dotProduct3D(op, oq);
		float t = 1 - c;

		float ax = a[X];
		float ay = a[Y];
		float az = a[Z];
		if (s > 0) {
			ax /= s;
			ay /= s;
			az /= s;
		}

		float tax = t*ax;
		float taxay = tax*ay, taxaz = tax*az;
		float saz = s*az, say = s*ay;
		rotationMatrix[0] = tax*ax + c;
		rotationMatrix[1] = taxay + saz;
		rotationMatrix[2] = taxaz - say;

		float tay = t*ay;
		float tayaz = tay*az;
		float sax = s*ax;
		rotationMatrix[4] = taxay - saz;
		rotationMatrix[5] = tay*ay + c;
		rotationMatrix[6] = tayaz + sax;

		rotationMatrix[8] = taxaz + say;
		rotationMatrix[9] = tayaz - sax;
		rotationMatrix[10] = t*az*az + c;

		rotationMatrix[3] = rotationMatrix[7] = rotationMatrix[11] = 
			rotationMatrix[12] = rotationMatrix[13] = rotationMatrix[14] = 0;
		rotationMatrix[15] = 1;
	}
}