import jalview.api.analysis.ScoreModelI;
import jalview.api.analysis.SimilarityParamsI;
import jalview.datamodel.AlignmentView;
+import jalview.datamodel.Point;
import jalview.math.MatrixI;
import java.io.PrintStream;
*
* @return DOCUMENT ME!
*/
- public float[][] getComponents(int l, int n, int mm, float factor)
+ public Point[] getComponents(int l, int n, int mm, float factor)
{
- float[][] out = new float[getHeight()][3];
+ Point[] out = new Point[getHeight()];
for (int i = 0; i < getHeight(); i++)
{
- out[i][0] = (float) component(i, l) * factor;
- out[i][1] = (float) component(i, n) * factor;
- out[i][2] = (float) component(i, mm) * factor;
+ float x = (float) component(i, l) * factor;
+ float y = (float) component(i, n) * factor;
+ float z = (float) component(i, mm) * factor;
+ out[i] = new Point(x, y, z);
}
return out;
int dim2 = top - yCombobox.getSelectedIndex();
int dim3 = top - zCombobox.getSelectedIndex();
pcaModel.updateRcView(dim1, dim2, dim3);
- rc.img = null;
- rc.rotmat.setIdentity();
- rc.initAxes();
+ rc.resetView();
rc.paint(rc.getGraphics());
}
package jalview.appletgui;
import jalview.api.RotatableCanvasI;
+import jalview.datamodel.Point;
import jalview.datamodel.SequenceGroup;
import jalview.datamodel.SequenceI;
import jalview.datamodel.SequencePoint;
import jalview.math.RotatableMatrix;
-import jalview.util.Format;
+import jalview.math.RotatableMatrix.Axis;
import jalview.util.MessageManager;
import jalview.viewmodel.AlignmentViewport;
public class RotatableCanvas extends Panel implements MouseListener,
MouseMotionListener, KeyListener, RotatableCanvasI
{
- RotatableMatrix idmat = new RotatableMatrix(3, 3);
-
- RotatableMatrix objmat = new RotatableMatrix(3, 3);
-
- RotatableMatrix rotmat = new RotatableMatrix(3, 3);
+ private static final int DIMS = 3;
String tooltip;
- int toolx, tooly;
+ int toolx;
+
+ int tooly;
// RubberbandRectangle rubberband;
boolean drawAxes = true;
- int omx = 0;
-
- int mx = 0;
-
- int omy = 0;
+ int mouseX = 0;
- int my = 0;
+ int mouseY = 0;
Image img;
Dimension prefsize;
- float centre[] = new float[3];
+ Point centre;
- float width[] = new float[3];
+ float[] width = new float[DIMS];
- float max[] = new float[3];
+ float[] max = new float[DIMS];
- float min[] = new float[3];
+ float[] min = new float[DIMS];
float maxwidth;
Vector<SequencePoint> points;
- float[][] orig;
+ Point[] orig;
- float[][] axes;
+ Point[] axisEndPoints;
int startx;
boolean showLabels = false;
- public RotatableCanvas(AlignmentViewport av)
+ public RotatableCanvas(AlignmentViewport viewport)
{
- this.av = av;
+ this.av = viewport;
+ axisEndPoints = new Point[DIMS];
}
public void showLabels(boolean b)
}
@Override
- public void setPoints(Vector points, int npoint)
+ public void setPoints(Vector<SequencePoint> points, int npoint)
{
this.points = points;
this.npoint = npoint;
PaintRefresher.Register(this, av.getSequenceSetId());
prefsize = getPreferredSize();
- orig = new float[npoint][3];
+ orig = new Point[npoint];
for (int i = 0; i < npoint; i++)
{
- SequencePoint sp = (SequencePoint) points.elementAt(i);
- for (int j = 0; j < 3; j++)
- {
- orig[i][j] = sp.coord[j];
- }
- }
- // Initialize the matrices to identity
-
- for (int i = 0; i < 3; i++)
- {
- for (int j = 0; j < 3; j++)
- {
- if (i != j)
- {
- idmat.addElement(i, j, 0);
- objmat.addElement(i, j, 0);
- rotmat.addElement(i, j, 0);
- }
- else
- {
- idmat.addElement(i, j, 0);
- objmat.addElement(i, j, 0);
- rotmat.addElement(i, j, 0);
- }
- }
+ SequencePoint sp = points.elementAt(i);
+ orig[i] = sp.coord;
}
- axes = new float[3][3];
- initAxes();
+ resetAxes();
findCentre();
findWidth();
* super.removeNotify(); }
*/
- public void initAxes()
+ /**
+ * Resets axes to the initial state: x-axis to the right, y-axis up, z-axis to
+ * back (so obscured in a 2-D display)
+ */
+ public void resetAxes()
{
- for (int i = 0; i < 3; i++)
- {
- for (int j = 0; j < 3; j++)
- {
- if (i != j)
- {
- axes[i][j] = 0;
- }
- else
- {
- axes[i][j] = 1;
- }
- }
- }
+ axisEndPoints[0] = new Point(1f, 0f, 0f);
+ axisEndPoints[1] = new Point(0f, 1f, 0f);
+ axisEndPoints[2] = new Point(0f, 0f, 1f);
}
+ /**
+ * Computes and saves the maximum and minimum (x, y, z) positions of any
+ * sequence point, and also the min-max range (width) for each dimension, and
+ * the maximum width for all dimensions
+ */
public void findWidth()
{
max = new float[3];
min = new float[3];
- max[0] = (float) -1e30;
- max[1] = (float) -1e30;
- max[2] = (float) -1e30;
+ max[0] = Float.MIN_VALUE;
+ max[1] = Float.MIN_VALUE;
+ max[2] = Float.MIN_VALUE;
- min[0] = (float) 1e30;
- min[1] = (float) 1e30;
- min[2] = (float) 1e30;
+ min[0] = Float.MAX_VALUE;
+ min[1] = Float.MAX_VALUE;
+ min[2] = Float.MAX_VALUE;
- for (int i = 0; i < 3; i++)
+ for (SequencePoint sp : points)
{
- for (int j = 0; j < npoint; j++)
- {
- SequencePoint sp = points.elementAt(j);
- if (sp.coord[i] >= max[i])
- {
- max[i] = sp.coord[i];
- }
- if (sp.coord[i] <= min[i])
- {
- min[i] = sp.coord[i];
- }
- }
+ max[0] = Math.max(max[0], sp.coord.x);
+ max[1] = Math.max(max[1], sp.coord.y);
+ max[2] = Math.max(max[2], sp.coord.z);
+ min[0] = Math.min(min[0], sp.coord.x);
+ min[1] = Math.min(min[1], sp.coord.y);
+ min[2] = Math.min(min[2], sp.coord.z);
}
- // System.out.println("xmax " + max[0] + " min " + min[0]);
- // System.out.println("ymax " + max[1] + " min " + min[1]);
- // System.out.println("zmax " + max[2] + " min " + min[2]);
-
width[0] = Math.abs(max[0] - min[0]);
width[1] = Math.abs(max[1] - min[1]);
width[2] = Math.abs(max[2] - min[2]);
- maxwidth = width[0];
-
- if (width[1] > width[0])
- {
- maxwidth = width[1];
- }
- if (width[2] > width[1])
- {
- maxwidth = width[2];
- }
-
- // System.out.println("Maxwidth = " + maxwidth);
+ maxwidth = Math.max(width[0], Math.max(width[1], width[2]));
}
public float findScale()
{
- int dim, width, height;
+ int dim, w, height;
if (getSize().width != 0)
{
- width = getSize().width;
+ w = getSize().width;
height = getSize().height;
}
else
{
- width = prefsize.width;
+ w = prefsize.width;
height = prefsize.height;
}
- if (width < height)
+ if (w < height)
{
- dim = width;
+ dim = w;
}
else
{
return dim * scalefactor / (2 * maxwidth);
}
+ /**
+ * Computes and saves the position of the centre of the view
+ */
public void findCentre()
{
- // Find centre coordinate
findWidth();
- centre[0] = (max[0] + min[0]) / 2;
- centre[1] = (max[1] + min[1]) / 2;
- centre[2] = (max[2] + min[2]) / 2;
+ float x = (max[0] + min[0]) / 2;
+ float y = (max[1] + min[1]) / 2;
+ float z = (max[2] + min[2]) / 2;
- // System.out.println("Centre x " + centre[0]);
- // System.out.println("Centre y " + centre[1]);
- // System.out.println("Centre z " + centre[2]);
+ centre = new Point(x, y, z);
}
@Override
drawBackground(ig, Color.black);
drawScene(ig);
- if (drawAxes == true)
+ if (drawAxes)
{
drawAxes(ig);
}
for (int i = 0; i < 3; i++)
{
g.drawLine(getSize().width / 2, getSize().height / 2,
- (int) (axes[i][0] * scale * max[0] + getSize().width / 2),
- (int) (axes[i][1] * scale * max[1] + getSize().height / 2));
+ (int) (axisEndPoints[i].x * scale * max[0] + getSize().width / 2),
+ (int) (axisEndPoints[i].y * scale * max[1] + getSize().height / 2));
}
}
for (int i = 0; i < npoint; i++)
{
SequencePoint sp = points.elementAt(i);
- int x = (int) ((sp.coord[0] - centre[0]) * scale) + halfwidth;
- int y = (int) ((sp.coord[1] - centre[1]) * scale)
+ int x = (int) ((sp.coord.x - centre.x) * scale) + halfwidth;
+ int y = (int) ((sp.coord.y - centre.y) * scale)
+ halfheight;
- float z = sp.coord[1] - centre[2];
+ float z = sp.coord.y - centre.z;
SequenceI sequence = sp.getSequence();
if (av.getSequenceColour(sequence) == Color.black)
}
}
- public Dimension minimumsize()
- {
- return prefsize;
- }
-
- public Dimension preferredsize()
- {
- return prefsize;
- }
-
@Override
public void keyTyped(KeyEvent evt)
{
repaint();
}
- public void printPoints()
- {
- for (int i = 0; i < npoint; i++)
- {
- SequencePoint sp = points.elementAt(i);
- Format.print(System.out, "%5d ", i);
- for (int j = 0; j < 3; j++)
- {
- Format.print(System.out, "%13.3f ", sp.coord[j]);
- }
- System.out.println();
- }
- }
-
@Override
public void mouseClicked(MouseEvent evt)
{
int x = evt.getX();
int y = evt.getY();
- mx = x;
- my = y;
-
- omx = mx;
- omy = my;
+ mouseX = x;
+ mouseY = y;
startx = x;
starty = y;
rectx2 = -1;
recty2 = -1;
- SequenceI found = findPoint(x, y);
+ SequenceI found = findSequenceAtPoint(x, y);
if (found != null)
{
@Override
public void mouseMoved(MouseEvent evt)
{
- SequenceI found = findPoint(evt.getX(), evt.getY());
+ SequenceI found = findSequenceAtPoint(evt.getX(), evt.getY());
if (found == null)
{
tooltip = null;
@Override
public void mouseDragged(MouseEvent evt)
{
- mx = evt.getX();
- my = evt.getY();
+ int xPos = evt.getX();
+ int yPos = evt.getY();
- rotmat.setIdentity();
+ RotatableMatrix rotmat = new RotatableMatrix();
- rotmat.rotate(my - omy, 'x');
- rotmat.rotate(mx - omx, 'y');
+ rotmat.rotate(yPos - mouseY, Axis.X);
+ rotmat.rotate(xPos - mouseX, Axis.Y);
for (int i = 0; i < npoint; i++)
{
SequencePoint sp = points.elementAt(i);
- sp.coord[0] -= centre[0];
- sp.coord[1] -= centre[1];
- sp.coord[2] -= centre[2];
+ sp.translateBack(centre);
// Now apply the rotation matrix
sp.coord = rotmat.vectorMultiply(sp.coord);
// Now translate back again
- sp.coord[0] += centre[0];
- sp.coord[1] += centre[1];
- sp.coord[2] += centre[2];
+ sp.translate(centre);
}
for (int i = 0; i < 3; i++)
{
- axes[i] = rotmat.vectorMultiply(axes[i]);
+ axisEndPoints[i] = rotmat.vectorMultiply(axisEndPoints[i]);
}
- omx = mx;
- omy = my;
+ mouseX = xPos;
+ mouseY = yPos;
paint(this.getGraphics());
}
for (int i = 0; i < npoint; i++)
{
SequencePoint sp = points.elementAt(i);
- int tmp1 = (int) ((sp.coord[0] - centre[0]) * scale
+ int tmp1 = (int) ((sp.coord.x - centre.x) * scale
+ getSize().width / 2.0);
- int tmp2 = (int) ((sp.coord[1] - centre[1]) * scale
+ int tmp2 = (int) ((sp.coord.y - centre.y) * scale
+ getSize().height / 2.0);
SequenceI sequence = sp.getSequence();
}
}
- public SequenceI findPoint(int x, int y)
+ /**
+ * Answers the first sequence found whose point on the display is within 2
+ * pixels of the given coordinates, or null if none is found
+ *
+ * @param x
+ * @param y
+ *
+ * @return
+ */
+ public SequenceI findSequenceAtPoint(int x, int y)
{
-
int halfwidth = getSize().width / 2;
int halfheight = getSize().height / 2;
{
SequencePoint sp = points.elementAt(i);
- int px = (int) ((sp.coord[0] - centre[0]) * scale)
+ int px = (int) ((sp.coord.x - centre.x) * scale)
+ halfwidth;
- int py = (int) ((sp.coord[1] - centre[1]) * scale)
+ int py = (int) ((sp.coord.y - centre.y) * scale)
+ halfheight;
if (Math.abs(px - x) < 3 && Math.abs(py - y) < 3)
{
found = i;
+ break;
}
}
+
if (found != -1)
{
return points.elementAt(found).getSequence();
}
}
+ /**
+ * Resets the view to initial state (no rotation)
+ */
+ public void resetView()
+ {
+ img = null;
+ resetAxes();
+ }
+
}
--- /dev/null
+package jalview.datamodel;
+
+/**
+ * A bean that models an (x, y, z) position in 3-D space
+ */
+public final class Point
+{
+ public final float x;
+
+ public final float y;
+
+ public final float z;
+
+ public Point(float xVal, float yVal, float zVal)
+ {
+ x = xVal;
+ y = yVal;
+ z = zVal;
+ }
+}
private final SequenceI sequence;
/*
- * array of coordinates in embedded sequence space
+ * x, y, z position in 3-D space
*/
- public float[] coord;
+ public Point coord;
/**
* Constructor
* @param sequence
* @param coord
*/
- public SequencePoint(SequenceI sequence, float[] coord)
+ public SequencePoint(SequenceI sequence, Point pt)
{
this.sequence = sequence;
- this.coord = coord;
+ this.coord = pt;
+ }
+
+ /**
+ * Constructor given a sequence and an array of x, y, z coordinate positions
+ *
+ * @param sequence
+ * @param coords
+ * @throws ArrayIndexOutOfBoundsException
+ * if array length is less than 3
+ */
+ public SequencePoint(SequenceI sequence, float[] coords)
+ {
+ this(sequence, new Point(coords[0], coords[1], coords[2]));
}
public SequenceI getSequence()
{
return sequence;
}
+
+ /**
+ * Applies a negative translation of the 'shift' (x, y, z) coordinates
+ *
+ * @param centre
+ */
+ public void translateBack(Point shift)
+ {
+ float x = coord.x - shift.x;
+ float y = coord.y - shift.y;
+ float z = coord.z - shift.z;
+
+ coord = new Point(x, y, z);
+ }
+
+ /**
+ * Applies a positive translation of the 'shift' (x, y, z) coordinates
+ *
+ * @param centre
+ */
+ public void translate(Point shift)
+ {
+ float x = coord.x + shift.x;
+ float y = coord.y + shift.y;
+ float z = coord.z + shift.z;
+
+ coord = new Point(x, y, z);
+ }
}
int dim2 = top - yCombobox.getSelectedIndex();
int dim3 = top - zCombobox.getSelectedIndex();
pcaModel.updateRcView(dim1, dim2, dim3);
- rc.img = null;
- rc.rotmat.setIdentity();
- rc.initAxes();
+ rc.resetView();
rc.paint(rc.getGraphics());
}
package jalview.gui;
import jalview.api.RotatableCanvasI;
-import jalview.bin.Cache;
+import jalview.datamodel.Point;
import jalview.datamodel.SequenceGroup;
import jalview.datamodel.SequenceI;
import jalview.datamodel.SequencePoint;
import jalview.math.RotatableMatrix;
+import jalview.math.RotatableMatrix.Axis;
import jalview.util.MessageManager;
import jalview.viewmodel.AlignmentViewport;
import javax.swing.ToolTipManager;
/**
- * DOCUMENT ME!
- *
- * @author $author$
- * @version $Revision$
+ * Models a Panel on which a set of points, and optionally x/y/z axes, can be
+ * drawn, and rotated or zoomed with the mouse
*/
public class RotatableCanvas extends JPanel implements MouseListener,
MouseMotionListener, KeyListener, RotatableCanvasI
{
- RotatableMatrix idmat = new RotatableMatrix(3, 3);
-
- RotatableMatrix objmat = new RotatableMatrix(3, 3);
-
- RotatableMatrix rotmat = new RotatableMatrix(3, 3);
+ private static final int DIMS = 3;
// RubberbandRectangle rubberband;
boolean drawAxes = true;
- int omx = 0;
-
- int mx = 0;
+ int mouseX = 0;
- int omy = 0;
-
- int my = 0;
+ int mouseY = 0;
Image img;
Dimension prefsize;
- float[] centre = new float[3];
+ Point centre;
- float[] width = new float[3];
+ float[] width = new float[DIMS];
- float[] max = new float[3];
+ float[] max = new float[DIMS];
- float[] min = new float[3];
+ float[] min = new float[DIMS];
float maxwidth;
Vector<SequencePoint> points;
- float[][] orig;
+ Point[] orig;
- float[][] axes;
+ Point[] axisEndPoints;
int startx;
boolean first = true;
- public RotatableCanvas(AlignmentPanel ap)
+ /**
+ * Constructor
+ *
+ * @param panel
+ */
+ public RotatableCanvas(AlignmentPanel panel)
{
- this.av = ap.av;
- this.ap = ap;
+ this.av = panel.av;
+ this.ap = panel;
+ axisEndPoints = new Point[DIMS];
addMouseWheelListener(new MouseWheelListener()
{
ToolTipManager.sharedInstance().setDismissDelay(10000);
}
prefsize = getPreferredSize();
- orig = new float[npoint][3];
+ orig = new Point[npoint];
for (int i = 0; i < npoint; i++)
{
SequencePoint sp = points.elementAt(i);
-
- for (int j = 0; j < 3; j++)
- {
- orig[i][j] = sp.coord[j];
- }
+ orig[i] = sp.coord;
}
- // Initialize the matrices to identity
- for (int i = 0; i < 3; i++)
- {
- for (int j = 0; j < 3; j++)
- {
- if (i != j)
- {
- idmat.addElement(i, j, 0);
- objmat.addElement(i, j, 0);
- rotmat.addElement(i, j, 0);
- }
- else
- {
- idmat.addElement(i, j, 0);
- objmat.addElement(i, j, 0);
- rotmat.addElement(i, j, 0);
- }
- }
- }
-
- axes = new float[3][3];
- initAxes();
+ resetAxes();
findCentre();
findWidth();
scale = findScale();
if (first)
{
-
addMouseListener(this);
-
addMouseMotionListener(this);
}
first = false;
}
- public void initAxes()
+ /**
+ * Resets axes to the initial state: x-axis to the right, y-axis up, z-axis to
+ * back (so obscured in a 2-D display)
+ */
+ public void resetAxes()
{
- for (int i = 0; i < 3; i++)
- {
- for (int j = 0; j < 3; j++)
- {
- if (i != j)
- {
- axes[i][j] = 0;
- }
- else
- {
- axes[i][j] = 1;
- }
- }
- }
+ axisEndPoints[0] = new Point(1f, 0f, 0f);
+ axisEndPoints[1] = new Point(0f, 1f, 0f);
+ axisEndPoints[2] = new Point(0f, 0f, 1f);
}
/**
- * DOCUMENT ME!
+ * Computes and saves the maximum and minimum (x, y, z) positions of any
+ * sequence point, and also the min-max range (width) for each dimension, and
+ * the maximum width for all dimensions
*/
public void findWidth()
{
- max = new float[3];
- min = new float[3];
+ max = new float[DIMS];
+ min = new float[DIMS];
- max[0] = (float) -1e30;
- max[1] = (float) -1e30;
- max[2] = (float) -1e30;
+ max[0] = Float.MIN_VALUE;
+ max[1] = Float.MIN_VALUE;
+ max[2] = Float.MIN_VALUE;
- min[0] = (float) 1e30;
- min[1] = (float) 1e30;
- min[2] = (float) 1e30;
+ min[0] = Float.MAX_VALUE;
+ min[1] = Float.MAX_VALUE;
+ min[2] = Float.MAX_VALUE;
- for (int i = 0; i < 3; i++)
+ for (SequencePoint sp : points)
{
- for (int j = 0; j < npoint; j++)
- {
- SequencePoint sp = points.elementAt(j);
-
- if (sp.coord[i] >= max[i])
- {
- max[i] = sp.coord[i];
- }
-
- if (sp.coord[i] <= min[i])
- {
- min[i] = sp.coord[i];
- }
- }
+ max[0] = Math.max(max[0], sp.coord.x);
+ max[1] = Math.max(max[1], sp.coord.y);
+ max[2] = Math.max(max[2], sp.coord.z);
+ min[0] = Math.min(min[0], sp.coord.x);
+ min[1] = Math.min(min[1], sp.coord.y);
+ min[2] = Math.min(min[2], sp.coord.z);
}
- // System.out.println("xmax " + max[0] + " min " + min[0]);
- // System.out.println("ymax " + max[1] + " min " + min[1]);
- // System.out.println("zmax " + max[2] + " min " + min[2]);
width[0] = Math.abs(max[0] - min[0]);
width[1] = Math.abs(max[1] - min[1]);
width[2] = Math.abs(max[2] - min[2]);
- maxwidth = width[0];
-
- if (width[1] > width[0])
- {
- maxwidth = width[1];
- }
-
- if (width[2] > width[1])
- {
- maxwidth = width[2];
- }
-
- // System.out.println("Maxwidth = " + maxwidth);
+ maxwidth = Math.max(width[0], Math.max(width[1], width[2]));
}
/**
}
/**
- * DOCUMENT ME!
+ * Computes and saves the position of the centre of the view
*/
public void findCentre()
{
- // Find centre coordinate
findWidth();
- centre[0] = (max[0] + min[0]) / 2;
- centre[1] = (max[1] + min[1]) / 2;
- centre[2] = (max[2] + min[2]) / 2;
+ float x = (max[0] + min[0]) / 2;
+ float y = (max[1] + min[1]) / 2;
+ float z = (max[2] + min[2]) / 2;
- // System.out.println("Centre x " + centre[0]);
- // System.out.println("Centre y " + centre[1]);
- // System.out.println("Centre z " + centre[2]);
+ centre = new Point(x, y, z);
}
/**
}
/**
- * DOCUMENT ME!
+ * Resets the view to initial state (no rotation)
+ */
+ public void resetView()
+ {
+ img = null;
+ resetAxes();
+ }
+
+ /**
+ * Draws lines for the x, y, z axes
*
* @param g
- * DOCUMENT ME!
*/
public void drawAxes(Graphics g)
{
g.setColor(Color.yellow);
- for (int i = 0; i < 3; i++)
+ for (int i = 0; i < DIMS; i++)
{
g.drawLine(getWidth() / 2, getHeight() / 2,
- (int) ((axes[i][0] * scale * max[0]) + (getWidth() / 2)),
- (int) ((axes[i][1] * scale * max[1]) + (getHeight() / 2)));
+ (int) ((axisEndPoints[i].x * scale * max[0]) + (getWidth() / 2)),
+ (int) ((axisEndPoints[i].y * scale * max[1]) + (getHeight() / 2)));
}
}
/**
- * DOCUMENT ME!
+ * Fills the background with the specified colour
*
* @param g
- * DOCUMENT ME!
* @param col
- * DOCUMENT ME!
*/
public void drawBackground(Graphics g, Color col)
{
}
/**
- * DOCUMENT ME!
+ * Draws points (6x6 squares) for the sequences of the PCA, and labels
+ * (sequence names) if configured to do so. The sequence points colours are
+ * taken from the sequence ids in the alignment (converting black to white).
+ * Sequences 'at the back' (z-coordinate is negative) are shaded slightly
+ * darker to help give a 3-D sensation.
*
* @param g
- * DOCUMENT ME!
*/
public void drawScene(Graphics g1)
{
-
Graphics2D g = (Graphics2D) g1;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
for (int i = 0; i < npoint; i++)
{
SequencePoint sp = points.elementAt(i);
- int x = (int) ((sp.coord[0] - centre[0]) * scale) + halfwidth;
- int y = (int) ((sp.coord[1] - centre[1]) * scale)
+ int x = (int) ((sp.coord.x - centre.x) * scale) + halfwidth;
+ int y = (int) ((sp.coord.y - centre.y) * scale)
+ halfheight;
- float z = sp.coord[1] - centre[2];
+ float z = sp.coord.y - centre.z; // todo sp.coord.z JAL-2963
SequenceI sequence = sp.getSequence();
if (av.getSequenceColour(sequence) == Color.black)
// }
}
- /**
- * DOCUMENT ME!
- *
- * @return DOCUMENT ME!
- */
- public Dimension minimumsize()
- {
- return prefsize;
- }
-
- /**
- * DOCUMENT ME!
- *
- * @return DOCUMENT ME!
- */
- public Dimension preferredsize()
- {
- return prefsize;
- }
-
- /**
- * DOCUMENT ME!
- *
- * @param evt
- * DOCUMENT ME!
- */
@Override
public void keyTyped(KeyEvent evt)
{
}
- /**
- * DOCUMENT ME!
- *
- * @param evt
- * DOCUMENT ME!
- */
@Override
public void keyReleased(KeyEvent evt)
{
}
/**
- * DOCUMENT ME!
+ * Responds to up or down arrow key by zooming in or out, respectively
*
* @param evt
- * DOCUMENT ME!
*/
@Override
public void keyPressed(KeyEvent evt)
}
else if (evt.getKeyChar() == 's')
{
- Cache.log.warn("DEBUG: Rectangle selection");
+ // Cache.log.warn("DEBUG: Rectangle selection");
// todo not yet enabled as rectx2, recty2 are always -1
// need to set them in mouseDragged
if ((rectx2 != -1) && (recty2 != -1))
int x = evt.getX();
int y = evt.getY();
- mx = x;
- my = y;
-
- omx = mx;
- omy = my;
+ mouseX = x;
+ mouseY = y;
startx = x;
starty = y;
}
/**
- * DOCUMENT ME!
+ * Action handler for a mouse drag. Rotates the display around the X axis (for
+ * up/down mouse movement) and/or the Y axis (for left/right mouse movement).
*
* @param evt
- * DOCUMENT ME!
*/
@Override
public void mouseDragged(MouseEvent evt)
{
- mx = evt.getX();
- my = evt.getY();
+ int xPos = evt.getX();
+ int yPos = evt.getY();
+
+ if (xPos == mouseX && yPos == mouseY)
+ {
+ return;
+ }
// Check if this is a rectangle drawing drag
if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) != 0)
}
else
{
- rotmat.setIdentity();
+ /*
+ * get the identity transformation...
+ */
+ RotatableMatrix rotmat = new RotatableMatrix();
+
+ /*
+ * rotate around the X axis for change in Y
+ * (mouse movement up/down); note we are equating a
+ * number of pixels with degrees of rotation here!
+ */
+ if (yPos != mouseY)
+ {
+ rotmat.rotate(yPos - mouseY, Axis.X);
+ }
- rotmat.rotate(my - omy, 'x');
- rotmat.rotate(mx - omx, 'y');
+ /*
+ * rotate around the Y axis for change in X
+ * (mouse movement left/right)
+ */
+ if (xPos != mouseX)
+ {
+ rotmat.rotate(xPos - mouseX, Axis.Y);
+ }
+ /*
+ * apply the composite transformation to sequence points
+ */
for (int i = 0; i < npoint; i++)
{
SequencePoint sp = points.elementAt(i);
- sp.coord[0] -= centre[0];
- sp.coord[1] -= centre[1];
- sp.coord[2] -= centre[2];
+ sp.translateBack(centre);
// Now apply the rotation matrix
sp.coord = rotmat.vectorMultiply(sp.coord);
// Now translate back again
- sp.coord[0] += centre[0];
- sp.coord[1] += centre[1];
- sp.coord[2] += centre[2];
+ sp.translate(centre);
}
- for (int i = 0; i < 3; i++)
+ /*
+ * rotate the x/y/z axis positions
+ */
+ for (int i = 0; i < DIMS; i++)
{
- axes[i] = rotmat.vectorMultiply(axes[i]);
+ axisEndPoints[i] = rotmat.vectorMultiply(axisEndPoints[i]);
}
- omx = mx;
- omy = my;
+ mouseX = xPos;
+ mouseY = yPos;
paint(this.getGraphics());
}
for (int i = 0; i < npoint; i++)
{
SequencePoint sp = points.elementAt(i);
- int tmp1 = (int) (((sp.coord[0] - centre[0]) * scale)
+ int tmp1 = (int) (((sp.coord.x - centre.x) * scale)
+ (getWidth() / 2.0));
- int tmp2 = (int) (((sp.coord[1] - centre[1]) * scale)
+ int tmp2 = (int) (((sp.coord.y - centre.y) * scale)
+ (getHeight() / 2.0));
if ((tmp1 > x1) && (tmp1 < x2) && (tmp2 > y1) && (tmp2 < y2))
for (int i = 0; i < npoint; i++)
{
SequencePoint sp = points.elementAt(i);
- int px = (int) ((sp.coord[0] - centre[0]) * scale)
+ int px = (int) ((sp.coord.x - centre.x) * scale)
+ halfwidth;
- int py = (int) ((sp.coord[1] - centre[1]) * scale)
+ int py = (int) ((sp.coord.y - centre.y) * scale)
+ halfheight;
if ((Math.abs(px - x) < 3) && (Math.abs(py - y) < 3))
{
found = i;
+ break;
}
}
}
}
+ /**
+ * Answers the panel the PCA is associated with (all panels for this alignment
+ * if 'associate with all panels' is selected).
+ *
+ * @return
+ */
AlignmentPanel[] getAssociatedPanels()
{
if (applyToAllViews)
return new AlignmentPanel[] { ap };
}
}
-
- /**
- *
- * @return x,y,z positions of point s (index into points) under current
- * transform.
- */
- public double[] getPointPosition(int s)
- {
- double[] pts = new double[3];
- float[] p = points.elementAt(s).coord;
- pts[0] = p[0];
- pts[1] = p[1];
- pts[2] = p[2];
- return pts;
- }
-
}
*/
package jalview.math;
+import jalview.datamodel.Point;
+
+import java.util.HashMap;
+import java.util.Map;
+
/**
- * DOCUMENT ME!
- *
- * @author $author$
- * @version $Revision$
+ * Model for a 3x3 matrix which provides methods for rotation in 3-D space
*/
public class RotatableMatrix
{
- float[][] matrix;
+ private static final int DIMS = 3;
- float[] temp;
+ /*
+ * cache the most used rotations: +/- 1, 2, 3, 4 degrees around x or y axis
+ */
+ private static Map<Axis, Map<Float, float[][]>> cachedRotations;
- float[][] rot;
+ static
+ {
+ cachedRotations = new HashMap<>();
+ for (Axis axis : Axis.values())
+ {
+ HashMap<Float, float[][]> map = new HashMap<>();
+ cachedRotations.put(axis, map);
+ for (int deg = 1; deg < 5; deg++)
+ {
+ float[][] rotation = getRotation(deg, axis);
+ map.put(Float.valueOf(deg), rotation);
+ rotation = getRotation(-deg, axis);
+ map.put(Float.valueOf(-deg), rotation);
+ }
+ }
+ }
- /**
- * Creates a new RotatableMatrix object.
- *
- * @param rows
- * DOCUMENT ME!
- * @param cols
- * DOCUMENT ME!
- */
- public RotatableMatrix(int rows, int cols)
+ public enum Axis
{
- matrix = new float[rows][cols];
+ X, Y, Z
+ };
- temp = new float[3];
+ float[][] matrix;
- rot = new float[3][3];
+ /**
+ * Constructor creates a new identity matrix (all values zero except for 1 on
+ * the diagonal)
+ */
+ public RotatableMatrix()
+ {
+ matrix = new float[DIMS][DIMS];
+ for (int j = 0; j < DIMS; j++)
+ {
+ matrix[j][j] = 1f;
+ }
}
/**
- * DOCUMENT ME!
+ * Sets the value at position (i, j) of the matrix
*
* @param i
- * DOCUMENT ME!
* @param j
- * DOCUMENT ME!
* @param value
- * DOCUMENT ME!
*/
- public void addElement(int i, int j, float value)
+ public void setValue(int i, int j, float value)
{
matrix[i][j] = value;
}
/**
- * DOCUMENT ME!
+ * Answers the value at position (i, j) of the matrix
+ *
+ * @param i
+ * @param j
+ * @return
+ */
+ public float getValue(int i, int j)
+ {
+ return matrix[i][j];
+ }
+
+ /**
+ * Prints the matrix in rows of space-delimited values
*/
public void print()
{
}
/**
- * DOCUMENT ME!
+ * Rotates the matrix through the specified number of degrees around the
+ * specified axis
*
* @param degrees
- * DOCUMENT ME!
* @param axis
- * DOCUMENT ME!
*/
- public void rotate(float degrees, char axis)
+ public void rotate(float degrees, Axis axis)
{
- float costheta = (float) Math.cos((degrees * Math.PI) / (float) 180.0);
+ float[][] rot = getRotation(degrees, axis);
- float sintheta = (float) Math.sin((degrees * Math.PI) / (float) 180.0);
+ preMultiply(rot);
+ }
- if (axis == 'z')
+ /**
+ * Answers a matrix which, when it pre-multiplies another matrix, applies a
+ * rotation of the specified number of degrees around the specified axis
+ *
+ * @param degrees
+ * @param axis
+ * @return
+ * @see https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations
+ */
+ protected static float[][] getRotation(float degrees, Axis axis)
+ {
+ Float floatValue = Float.valueOf(degrees);
+ if (cachedRotations.get(axis).containsKey(floatValue))
{
- rot[0][0] = (float) costheta;
-
- rot[0][1] = (float) -sintheta;
-
- rot[0][2] = (float) 0.0;
-
- rot[1][0] = (float) sintheta;
-
- rot[1][1] = (float) costheta;
-
- rot[1][2] = (float) 0.0;
-
- rot[2][0] = (float) 0.0;
-
- rot[2][1] = (float) 0.0;
-
- rot[2][2] = (float) 1.0;
-
- preMultiply(rot);
+ // System.out.println("getRotation from cache: " + (int) degrees);
+ return cachedRotations.get(axis).get(floatValue);
}
- if (axis == 'x')
- {
- rot[0][0] = (float) 1.0;
-
- rot[0][1] = (float) 0.0;
-
- rot[0][2] = (float) 0.0;
-
- rot[1][0] = (float) 0.0;
+ float costheta = (float) Math.cos(degrees * Math.PI / 180f);
- rot[1][1] = (float) costheta;
+ float sintheta = (float) Math.sin(degrees * Math.PI / 180f);
- rot[1][2] = (float) sintheta;
+ float[][] rot = new float[DIMS][DIMS];
- rot[2][0] = (float) 0.0;
-
- rot[2][1] = (float) -sintheta;
-
- rot[2][2] = (float) costheta;
-
- preMultiply(rot);
- }
-
- if (axis == 'y')
+ switch (axis)
{
- rot[0][0] = (float) costheta;
-
- rot[0][1] = (float) 0.0;
-
- rot[0][2] = (float) -sintheta;
-
- rot[1][0] = (float) 0.0;
-
- rot[1][1] = (float) 1.0;
-
- rot[1][2] = (float) 0.0;
-
- rot[2][0] = (float) sintheta;
-
- rot[2][1] = (float) 0.0;
-
- rot[2][2] = (float) costheta;
-
- preMultiply(rot);
+ case X:
+ rot[0][0] = 1f;
+ rot[1][1] = costheta;
+ rot[1][2] = sintheta;
+ rot[2][1] = -sintheta;
+ rot[2][2] = costheta;
+ break;
+ case Y:
+ rot[0][0] = costheta;
+ rot[0][2] = -sintheta;
+ rot[1][1] = 1f;
+ rot[2][0] = sintheta;
+ rot[2][2] = costheta;
+ break;
+ case Z:
+ rot[0][0] = costheta;
+ rot[0][1] = -sintheta;
+ rot[1][0] = sintheta;
+ rot[1][1] = costheta;
+ rot[2][2] = 1f;
+ break;
}
+ return rot;
}
/**
- * DOCUMENT ME!
+ * Answers a new array of float values which is the result of pre-multiplying
+ * this matrix by the given vector. Each value of the result is the dot
+ * product of the vector with one column of this matrix. The matrix and input
+ * vector are not modified.
*
* @param vect
- * DOCUMENT ME!
*
- * @return DOCUMENT ME!
+ * @return
*/
public float[] vectorMultiply(float[] vect)
{
- temp[0] = vect[0];
-
- temp[1] = vect[1];
-
- temp[2] = vect[2];
+ float[] result = new float[DIMS];
- for (int i = 0; i < 3; i++)
+ for (int i = 0; i < DIMS; i++)
{
- temp[i] = (matrix[i][0] * vect[0]) + (matrix[i][1] * vect[1])
+ result[i] = (matrix[i][0] * vect[0]) + (matrix[i][1] * vect[1])
+ (matrix[i][2] * vect[2]);
}
- vect[0] = temp[0];
-
- vect[1] = temp[1];
-
- vect[2] = temp[2];
-
- return vect;
+ return result;
}
/**
- * DOCUMENT ME!
+ * Performs pre-multiplication of this matrix by the given one. Value (i, j)
+ * of the result is the dot product of the i'th row of <code>mat</code> with
+ * the j'th column of this matrix.
*
* @param mat
- * DOCUMENT ME!
*/
public void preMultiply(float[][] mat)
{
- float[][] tmp = new float[3][3];
+ float[][] tmp = new float[DIMS][DIMS];
- for (int i = 0; i < 3; i++)
+ for (int i = 0; i < DIMS; i++)
{
- for (int j = 0; j < 3; j++)
+ for (int j = 0; j < DIMS; j++)
{
tmp[i][j] = (mat[i][0] * matrix[0][j]) + (mat[i][1] * matrix[1][j])
+ (mat[i][2] * matrix[2][j]);
}
}
- for (int i = 0; i < 3; i++)
- {
- for (int j = 0; j < 3; j++)
- {
- matrix[i][j] = tmp[i][j];
- }
- }
+ matrix = tmp;
}
/**
- * DOCUMENT ME!
+ * Performs post-multiplication of this matrix by the given one. Value (i, j)
+ * of the result is the dot product of the i'th row of this matrix with the
+ * j'th column of <code>mat</code>.
*
* @param mat
- * DOCUMENT ME!
*/
public void postMultiply(float[][] mat)
{
- float[][] tmp = new float[3][3];
+ float[][] tmp = new float[DIMS][DIMS];
- for (int i = 0; i < 3; i++)
+ for (int i = 0; i < DIMS; i++)
{
- for (int j = 0; j < 3; j++)
+ for (int j = 0; j < DIMS; j++)
{
tmp[i][j] = (matrix[i][0] * mat[0][j]) + (matrix[i][1] * mat[1][j])
+ (matrix[i][2] * mat[2][j]);
}
}
- for (int i = 0; i < 3; i++)
- {
- for (int j = 0; j < 3; j++)
- {
- matrix[i][j] = tmp[i][j];
- }
- }
+ matrix = tmp;
}
/**
*/
public static void main(String[] args)
{
- RotatableMatrix m = new RotatableMatrix(3, 3);
+ RotatableMatrix m = new RotatableMatrix();
- m.addElement(0, 0, 1);
+ m.setValue(0, 0, 1);
- m.addElement(0, 1, 0);
+ m.setValue(0, 1, 0);
- m.addElement(0, 2, 0);
+ m.setValue(0, 2, 0);
- m.addElement(1, 0, 0);
+ m.setValue(1, 0, 0);
- m.addElement(1, 1, 2);
+ m.setValue(1, 1, 2);
- m.addElement(1, 2, 0);
+ m.setValue(1, 2, 0);
- m.addElement(2, 0, 0);
+ m.setValue(2, 0, 0);
- m.addElement(2, 1, 0);
+ m.setValue(2, 1, 0);
- m.addElement(2, 2, 1);
+ m.setValue(2, 2, 1);
m.print();
- RotatableMatrix n = new RotatableMatrix(3, 3);
+ RotatableMatrix n = new RotatableMatrix();
- n.addElement(0, 0, 2);
+ n.setValue(0, 0, 2);
- n.addElement(0, 1, 1);
+ n.setValue(0, 1, 1);
- n.addElement(0, 2, 1);
+ n.setValue(0, 2, 1);
- n.addElement(1, 0, 2);
+ n.setValue(1, 0, 2);
- n.addElement(1, 1, 1);
+ n.setValue(1, 1, 1);
- n.addElement(1, 2, 1);
+ n.setValue(1, 2, 1);
- n.addElement(2, 0, 2);
+ n.setValue(2, 0, 2);
- n.addElement(2, 1, 1);
+ n.setValue(2, 1, 1);
- n.addElement(2, 2, 1);
+ n.setValue(2, 2, 1);
n.print();
}
/**
- * DOCUMENT ME!
+ * Performs a vector multiplication whose result is the Point representing the
+ * input point's value vector post-multiplied by this matrix.
+ *
+ * @param coord
+ * @return
*/
- public void setIdentity()
+ public Point vectorMultiply(Point coord)
{
- matrix[0][0] = (float) 1.0;
-
- matrix[1][1] = (float) 1.0;
-
- matrix[2][2] = (float) 1.0;
-
- matrix[0][1] = (float) 0.0;
-
- matrix[0][2] = (float) 0.0;
-
- matrix[1][0] = (float) 0.0;
-
- matrix[1][2] = (float) 0.0;
-
- matrix[2][0] = (float) 0.0;
-
- matrix[2][1] = (float) 0.0;
+ float[] v = vectorMultiply(new float[] { coord.x, coord.y, coord.z });
+ return new Point(v[0], v[1], v[2]);
}
}
import jalview.api.analysis.ScoreModelI;
import jalview.api.analysis.SimilarityParamsI;
import jalview.datamodel.AlignmentView;
+import jalview.datamodel.Point;
import jalview.datamodel.SequenceI;
import jalview.datamodel.SequencePoint;
top = height - 1;
points = new Vector<>();
- float[][] scores = pca.getComponents(top - 1, top - 2, top - 3, 100);
+ Point[] scores = pca.getComponents(top - 1, top - 2, top - 3, 100);
for (int i = 0; i < height; i++)
{
public void updateRcView(int dim1, int dim2, int dim3)
{
// note: actual indices for components are dim1-1, etc (patch for JAL-1123)
- float[][] scores = pca.getComponents(dim1 - 1, dim2 - 1, dim3 - 1, 100);
+ Point[] scores = pca.getComponents(dim1 - 1, dim2 - 1, dim3 - 1, 100);
for (int i = 0; i < pca.getHeight(); i++)
{
}
else
{
- // output current x,y,z coords for points
- fl = getPointPosition(s);
- for (int d = 0; d < fl.length; d++)
- {
- csv.append(",");
- csv.append(fl[d]);
- }
+ Point p = points.elementAt(s).coord;
+ csv.append(",").append(p.x);
+ csv.append(",").append(p.y);
+ csv.append(",").append(p.z);
}
csv.append("\n");
}
public double[] getPointPosition(int s)
{
double pts[] = new double[3];
- float[] p = points.elementAt(s).coord;
- pts[0] = p[0];
- pts[1] = p[1];
- pts[2] = p[2];
+ Point p = points.elementAt(s).coord;
+ pts[0] = p.x;
+ pts[1] = p.y;
+ pts[2] = p.z;
return pts;
}
--- /dev/null
+package jalview.math;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.math.RotatableMatrix.Axis;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class RotatableMatrixTest
+{
+ private RotatableMatrix rm;
+
+ @BeforeMethod(alwaysRun = true)
+ public void setUp()
+ {
+ rm = new RotatableMatrix();
+
+ /*
+ * 0.5 1.0 1.5
+ * 1.0 2.0 3.0
+ * 1.5 3.0 4.5
+ */
+ for (int i = 1; i <= 3; i++)
+ {
+ for (int j = 1; j <= 3; j++)
+ {
+ rm.setValue(i - 1, j - 1, i * j / 2f);
+ }
+ }
+ }
+
+ @Test(groups = "Functional")
+ public void testPreMultiply()
+ {
+ float[][] pre = new float[3][3];
+ int i = 1;
+ for (int j = 0; j < 3; j++)
+ {
+ for (int k = 0; k < 3; k++)
+ {
+ pre[j][k] = i++;
+ }
+ }
+
+ rm.preMultiply(pre);
+
+ /*
+ * check rm[i, j] is now the product of the i'th row of pre
+ * and the j'th column of (original) rm
+ */
+ for (int j = 0; j < 3; j++)
+ {
+ for (int k = 0; k < 3; k++)
+ {
+ float expected = 0f;
+ for (int l = 0; l < 3; l++)
+ {
+ float rm_l_k = (l + 1) * (k + 1) / 2f;
+ expected += pre[j][l] * rm_l_k;
+ }
+ assertEquals(rm.getValue(j, k), expected,
+ String.format("[%d, %d]", j, k));
+ }
+ }
+ }
+
+ @Test(groups = "Functional")
+ public void testVectorMultiply()
+ {
+ float[] result = rm.vectorMultiply(new float[] { 2f, 3f, 4.5f });
+
+ // vector times first column of matrix
+ assertEquals(result[0], 2f * 0.5f + 3f * 1f + 4.5f * 1.5f);
+
+ // vector times second column of matrix
+ assertEquals(result[1], 2f * 1.0f + 3f * 2f + 4.5f * 3f);
+
+ // vector times third column of matrix
+ assertEquals(result[2], 2f * 1.5f + 3f * 3f + 4.5f * 4.5f);
+ }
+
+ @Test(groups = "Functional")
+ public void testGetRotation()
+ {
+ float theta = 60f;
+ double cosTheta = Math.cos((theta * Math.PI / 180f));
+ double sinTheta = Math.sin((theta * Math.PI / 180f));
+
+ /*
+ * sanity check that sin(60) = sqrt(3) / 2, cos(60) = 1/2
+ */
+ double delta = 0.0001d;
+ assertEquals(cosTheta, 0.5f, delta);
+ assertEquals(sinTheta, Math.sqrt(3d) / 2d, delta);
+
+ /*
+ * so far so good, now verify rotations
+ * @see https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations
+ */
+
+ /*
+ * 60 degrees about X axis should be
+ * 1 0 0
+ * 0 cos -sin
+ * 0 sin cos
+ * but code applies the negative of this
+ * nb cos(-x) = cos(x), sin(-x) = -sin(x)
+ */
+ float[][] rot = RotatableMatrix.getRotation(theta, Axis.X);
+ assertEquals(rot[0][0], 1f, delta);
+ assertEquals(rot[0][1], 0f, delta);
+ assertEquals(rot[0][2], 0f, delta);
+ assertEquals(rot[1][0], 0f, delta);
+ assertEquals(rot[1][1], cosTheta, delta);
+ assertEquals(rot[1][2], sinTheta, delta);
+ assertEquals(rot[2][0], 0f, delta);
+ assertEquals(rot[2][1], -sinTheta, delta);
+ assertEquals(rot[2][2], cosTheta, delta);
+
+ /*
+ * 60 degrees about Y axis should be
+ * cos 0 sin
+ * 0 1 0
+ * -sin 0 cos
+ * but code applies the negative of this
+ */
+ rot = RotatableMatrix.getRotation(theta, Axis.Y);
+ assertEquals(rot[0][0], cosTheta, delta);
+ assertEquals(rot[0][1], 0f, delta);
+ assertEquals(rot[0][2], -sinTheta, delta);
+ assertEquals(rot[1][0], 0f, delta);
+ assertEquals(rot[1][1], 1f, delta);
+ assertEquals(rot[1][2], 0f, delta);
+ assertEquals(rot[2][0], sinTheta, delta);
+ assertEquals(rot[2][1], 0f, delta);
+ assertEquals(rot[2][2], cosTheta, delta);
+
+ /*
+ * 60 degrees about Z axis should be
+ * cos -sin 0
+ * sin cos 0
+ * 0 0 1
+ * - and it is!
+ */
+ rot = RotatableMatrix.getRotation(theta, Axis.Z);
+ assertEquals(rot[0][0], cosTheta, delta);
+ assertEquals(rot[0][1], -sinTheta, delta);
+ assertEquals(rot[0][2], 0f, delta);
+ assertEquals(rot[1][0], sinTheta, delta);
+ assertEquals(rot[1][1], cosTheta, delta);
+ assertEquals(rot[1][2], 0f, delta);
+ assertEquals(rot[2][0], 0f, delta);
+ assertEquals(rot[2][1], 0f, delta);
+ assertEquals(rot[2][2], 1f, delta);
+ }
+}