/* * VirtualSphere -- A class that implements the Virtual Sphere algorithm for 3D rotation. * * Copyright (C) 2001 by Joseph A. Huwaldt . * 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. * *

Ported from C to Java by Joseph A. Huwaldt, February 19, 2001

*

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. *

* *

Modified by: Joseph A.Huwaldt

* * @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. * *

Modification of Michael Pique's formula in * Graphics Gems Vol. 1. Andrew Glassner, Ed. Addison-Wesley.

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