//
// ProjectionControl.java
//
/*
VisAD system for interactive analysis and visualization of numerical
data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom
Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
Tommy Jasmin.
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.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
*/
package visad;
import java.rmi.*;
import java.util.StringTokenizer;
import visad.browser.Convert;
import visad.util.Util;
/**
ProjectionControl is the VisAD interface for controlling the Projection
from 3-D to 2-D.<P>
*/
public abstract class ProjectionControl extends Control {
/** matrix[] shouldn't be used by non-ProjectionControl classes */
protected double[] matrix = null;
private double[] savedProjectionMatrix = null;
private double[] asp = {1.0, 1.0, 1.0}; // WLH 24 Nov 2000
/** Length of a 2D matrix */
public static final int MATRIX2D_LENGTH = 6;
/** Major dimension of the 2D matrix */
public static final int MATRIX2D_MAJOR = 3;
/** Minor dimension of the 2D matrix */
public static final int MATRIX2D_MINOR = 2;
/** Length of a 2D matrix */
public static final int MATRIX3D_LENGTH = 16;
/** Major dimension of the 3D matrix */
public static final int MATRIX3D_MAJOR = 4;
/** Minor dimension of the 3D matrix */
public static final int MATRIX3D_MINOR = 4;
/**
* Construct a ProjectionControl for the display in question.
* @param d display to control
* @throws VisADException d already has a ProjectionControl or some other
* VisAD Error
*/
public ProjectionControl(DisplayImpl d) throws VisADException {
super(d);
if (d.getProjectionControl() != null) {
throw new DisplayException("display already has a ProjectionControl");
}
}
/**
* Returns a copy of the graphics projection matrix. The matrix has
* 6 elements in the 2-D case and 16 elements in the 3-D case.
*
* @return A copy of the graphics projection matrix.
*/
public double[] getMatrix() {
double[] c = new double[matrix.length];
System.arraycopy(matrix, 0, c, 0, matrix.length);
return c;
}
/**
* Set the matrix that defines the graphics projection
* @param m array of the matrix values (16 elements in Java3D case,
* 6 elements in Java2D case)
* @throws VisADException invalid matrix length
* @throws RemoteException Java RMI failure.
*/
public void setMatrix(double[] m)
throws VisADException, RemoteException {
if (m == null) return;
if (m.length != matrix.length) {
throw new DisplayException("setMatrix: input length must be " +
matrix.length);
}
System.arraycopy(m, 0, matrix, 0, matrix.length);
}
/**
* Get a string that can be used to reconstruct this control later
* @return String representation of this object that can be used for
* reconstruction.
*/
public String getSaveString() {
final int len = matrix.length;
final int major, minor;
if (len == MATRIX2D_LENGTH) {
major = MATRIX2D_MAJOR;
minor = MATRIX2D_MINOR;
} else if (len == MATRIX3D_LENGTH) {
major = MATRIX3D_MAJOR;
minor = MATRIX3D_MINOR;
} else {
major = len;
minor = 1;
}
StringBuffer sb = new StringBuffer(25 * len);
sb.append(major);
if (minor > 1) {
sb.append(" x ");
sb.append(minor);
}
sb.append('\n');
for (int j=0; j<minor; j++) {
for (int i=0; i<major; i++) {
if (i > 0) {
sb.append(' ');
}
sb.append(matrix[major * j + i]);
}
sb.append('\n');
}
return sb.toString();
}
/**
* Set the properties of this control using the specified save string
* @param save String to use for setting the properties.
* @throws VisADException VisAD failure.
* @throws RemoteException Java RMI failure.
*/
public void setSaveString(String save)
throws VisADException, RemoteException
{
if (save == null) throw new VisADException("Invalid save string");
int eol = save.indexOf('\n');
if (eol < 0) throw new VisADException("Invalid save string");
StringTokenizer st = new StringTokenizer(save.substring(0, eol));
int numTokens = st.countTokens();
// determine matrix size
int size = -1;
if (numTokens == 3) {
int len = Convert.getInt(st.nextToken());
if (len < 1) {
throw new VisADException("First matrix dimension is not positive");
}
if (!st.nextToken().equalsIgnoreCase("x")) {
throw new VisADException("Invalid save string");
}
int len0 = Convert.getInt(st.nextToken());
if (len0 < 1) {
throw new VisADException("Second matrix dimension is not positive");
}
size = len * len0;
}
else if (numTokens == 1) {
size = Convert.getInt(st.nextToken());
if (size < 1) {
throw new VisADException("Matrix size is not positive");
}
}
else throw new VisADException("Cannot determine matrix size");
// get matrix entries
st = new StringTokenizer(save.substring(eol + 1));
numTokens = st.countTokens();
if (numTokens < size) {
throw new VisADException("Not enough matrix entries");
}
double[] m = new double[size];
for (int i=0; i<size; i++) m[i] = Convert.getDouble(st.nextToken());
setMatrix(m);
}
/**
* Set aspect ratio of axes
* @param aspect ratios; 3 elements for Java3D, 2 for Java2D
* @throws VisADException VisAD failure.
* @throws RemoteException Java RMI failure.
*/
public abstract void setAspect(double[] aspect)
throws VisADException, RemoteException;
// WLH 24 Nov 2000
/**
* Set aspect ratio of axes, in ScalarMaps rather than matrix
* @param aspect ratios; 3 elements for Java3D, 2 for Java2D
* @throws VisADException VisAD failure.
* @throws RemoteException Java RMI failure.
*/
public void setAspectCartesian(double[] aspect)
throws VisADException, RemoteException {
if (aspect != null) {
for (int i=0; i<aspect.length; i++) {
if (aspect[i] <= 0.0) {
throw new DisplayException("aspect must be positive");
}
asp[i] = aspect[i];
}
}
getDisplay().setAspectCartesian(asp);
}
public double[] getAspectCartesian() {
return (double[]) asp.clone();
}
/**
* Saves the current display projection matrix. The projection may
* later be restored by the method <code>resetProjection()</code>.
* @see #resetProjection()
*/
public void saveProjection()
{
savedProjectionMatrix = getMatrix();
}
/**
* Get the matrix that defines the saved graphics projection
* @return array of the matrix values (16 elements in Java3D case,
* 6 elements in Java2D case)
*/
public double[] getSavedProjectionMatrix() {
double[] c = new double[savedProjectionMatrix.length];
System.arraycopy(
savedProjectionMatrix, 0, c, 0, savedProjectionMatrix.length);
return c;
}
/**
* Restores to projection matrix at time of last <code>saveProjection()</code>
* call -- if one was made -- or to initial projection otherwise.
* @see #saveProjection()
* @throws VisADException VisAD failure.
* @throws RemoteException Java RMI failure.
*/
public void resetProjection()
throws VisADException, RemoteException
{
setMatrix(savedProjectionMatrix);
}
/** Default scaling factor for 2D matrix */
public static final double SCALE2D = 0.65;
/** Inverse of SCALE2D */
public static final double INVSCALE2D = 1.0 / SCALE2D;
/**
* Convert a 2D matrix to a 3D matrix, retaining the scale and aspect
* of the 2D matrix.
* @param matrix 2D matrix to convert
* @throws VisADException wrong length for matrix (not MATRIX2D_LENGTH)
*/
public static double[] matrix2DTo3D(double[] matrix)
throws VisADException {
if (matrix.length != MATRIX2D_LENGTH) {
throw new DisplayException("matrix2DTo3D: input length must be " +
MATRIX2D_LENGTH);
}
double[] mat = new double[MATRIX3D_LENGTH];
for (int i=0; i<MATRIX3D_LENGTH; i++) mat[i] = 0.0;
mat[0] = SCALE2D * matrix[0];
mat[1] = SCALE2D * matrix[2];
mat[3] = matrix[4];
mat[4] = SCALE2D * matrix[1];
mat[5] = -SCALE2D * matrix[3];
mat[7] = -matrix[5];
mat[10] = 1.0;
mat[15] = 1.0;
return mat;
}
/**
* Convert a 3D matrix to a 2D matrix, retaining the scale and aspect
* of the 3D matrix.
* @param matrix 3D matrix to convert
* @throws VisADException wrong length for matrix (not MATRIX3D_LENGTH)
*/
public static double[] matrix3DTo2D(double[] matrix)
throws VisADException {
if (matrix.length != MATRIX3D_LENGTH) {
throw new DisplayException("matrix3DTo2D: input length must be " +
MATRIX3D_LENGTH);
}
double[] mat = new double[MATRIX2D_LENGTH];
mat[0] = INVSCALE2D * matrix[0];
mat[1] = INVSCALE2D * matrix[4];
mat[2] = INVSCALE2D * matrix[1];
mat[3] = -INVSCALE2D * matrix[5];
mat[4] = matrix[3];
mat[5] = -matrix[7];
return mat;
}
/**
* Convert a 3D matrix to a 2D matrix or vice-versa, retaining the scale
* and aspect of the original matrix. Helper interface to pass an unknown
* matrix to <CODE>matrix3DTo2D</CODE> or <CODE>matrix2DTo3D</CODE>.
* @see #matrix3DTo2D
* @see #matrix2DTo3D
* @param matrix matrix to convert
* @throws VisADException wrong length for matrix (not MATRIX3D_LENGTH)
*/
public static double[] matrixDConvert(double[] matrix)
throws VisADException {
if (matrix.length == MATRIX3D_LENGTH) {
return matrix3DTo2D(matrix);
}
if (matrix.length == MATRIX2D_LENGTH) {
return matrix2DTo3D(matrix);
}
throw new DisplayException("matrixDConvert: input length must be " +
MATRIX3D_LENGTH + " or " + MATRIX2D_LENGTH);
}
/** clear all 'pairs' in switches that involve re */
public void clearSwitches(DataRenderer re) {
}
private boolean matrixEquals(double[] newMatrix)
{
if (matrix == null) {
if (newMatrix != null) {
return false;
}
} else if (newMatrix == null) {
return false;
} else {
if (matrix.length != newMatrix.length) {
return false;
}
for (int i = 0; i < matrix.length; i++) {
if (!Util.isApproximatelyEqual(matrix[i], newMatrix[i])) {
return false;
}
}
}
return true;
}
// WLH 24 Nov 2000
private boolean aspEquals(double[] newAsp)
{
if (asp == null) {
if (newAsp != null) {
return false;
}
} else if (newAsp == null) {
return false;
} else {
if (asp.length != newAsp.length) {
return false;
}
for (int i = 0; i < asp.length; i++) {
if (!Util.isApproximatelyEqual(asp[i], newAsp[i])) {
return false;
}
}
}
return true;
}
/**
* Copy the state of a remote control to this control
* @param rmt remote control
* @throws VisADException rmt is null or not a ProjectionControl or
* some other VisAD error
*/
public void syncControl(Control rmt)
throws VisADException
{
if (rmt == null) {
throw new VisADException("Cannot synchronize " + getClass().getName() +
" with null Control object");
}
if (!(rmt instanceof ProjectionControl)) {
throw new VisADException("Cannot synchronize " + getClass().getName() +
" with " + rmt.getClass().getName());
}
ProjectionControl pc = (ProjectionControl )rmt;
if (!matrixEquals(pc.matrix)) {
try {
setMatrix(pc.matrix);
} catch (RemoteException re) {
throw new VisADException("Could not set matrix: " + re.getMessage());
}
}
// WLH 24 Nov 2000
if (!aspEquals(pc.asp)) {
try {
setAspectCartesian(pc.asp);
} catch (RemoteException re) {
throw new VisADException("Could not setAspectCartesian: " + re.getMessage());
}
}
}
/**
* Check to see if the object in question is equal to this ProjectionControl.
* The two are equal if they are both ProjectionControls and their projection
* matrices are equal.
* @param o object in question.
*/
public boolean equals(Object o)
{
if (!super.equals(o)) {
return false;
}
ProjectionControl pc = (ProjectionControl )o;
if (!matrixEquals(pc.matrix)) {
return false;
}
// WLH 24 Nov 2000
if (!aspEquals(pc.asp)) {
return false;
}
return true;
}
/**
* Create a clone of this ProjectionControl.
* @return clone
*/
public Object clone()
{
ProjectionControl pc = (ProjectionControl )super.clone();
if (matrix != null) {
pc.matrix = (double[] )matrix.clone();
}
// WLH 24 Nov 2000
if (asp != null) {
pc.asp = (double[] )asp.clone();
}
return pc;
}
/**
* A string representation of this ProjectionControl.
* @return human readable string that tells the properties of this control.
*/
public String toString()
{
StringBuffer buf = new StringBuffer("ProjectionControl[");
if (matrix == null) {
buf.append("null");
} else {
int major, minor;
if (matrix.length == MATRIX2D_LENGTH) {
major = MATRIX2D_MAJOR;
minor = MATRIX2D_MINOR;
} else if (matrix.length == MATRIX3D_LENGTH) {
major = MATRIX3D_MAJOR;
minor = MATRIX3D_MINOR;
} else {
major = 1;
minor = matrix.length;
}
int offset = 0;
for (int i = 0; i < major; i++) {
if (i > 0) {
buf.append(',');
}
for (int j = 0; j < minor; j++) {
buf.append(j == 0 ? '(' : ',');
buf.append(matrix[offset + j]);
}
buf.append(')');
offset += minor;
}
}
buf.append(']');
return buf.toString();
}
}