From 2cc3a62e1ae63428db854af668e963f1b23af553 Mon Sep 17 00:00:00 2001 From: gmungoc Date: Fri, 27 Apr 2018 15:14:23 +0100 Subject: [PATCH] JAL-1767 refactoring and tidying of RotatableCanvas and related --- src/jalview/analysis/PCA.java | 12 +- src/jalview/appletgui/PCAPanel.java | 4 +- src/jalview/appletgui/RotatableCanvas.java | 275 +++++++++------------- src/jalview/datamodel/Point.java | 20 ++ src/jalview/datamodel/SequencePoint.java | 49 +++- src/jalview/gui/PCAPanel.java | 4 +- src/jalview/gui/RotatableCanvas.java | 346 +++++++++++----------------- src/jalview/math/RotatableMatrix.java | 326 +++++++++++++------------- src/jalview/viewmodel/PCAModel.java | 24 +- test/jalview/math/RotatableMatrixTest.java | 157 +++++++++++++ 10 files changed, 640 insertions(+), 577 deletions(-) create mode 100644 src/jalview/datamodel/Point.java create mode 100644 test/jalview/math/RotatableMatrixTest.java diff --git a/src/jalview/analysis/PCA.java b/src/jalview/analysis/PCA.java index 6f02b71..1cf21fd 100755 --- a/src/jalview/analysis/PCA.java +++ b/src/jalview/analysis/PCA.java @@ -23,6 +23,7 @@ package jalview.analysis; 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; @@ -92,15 +93,16 @@ public class PCA implements Runnable * * @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; diff --git a/src/jalview/appletgui/PCAPanel.java b/src/jalview/appletgui/PCAPanel.java index a32f785..105aa78 100644 --- a/src/jalview/appletgui/PCAPanel.java +++ b/src/jalview/appletgui/PCAPanel.java @@ -167,9 +167,7 @@ public class PCAPanel extends EmbmenuFrame 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()); } diff --git a/src/jalview/appletgui/RotatableCanvas.java b/src/jalview/appletgui/RotatableCanvas.java index c5ae339..179a91d 100755 --- a/src/jalview/appletgui/RotatableCanvas.java +++ b/src/jalview/appletgui/RotatableCanvas.java @@ -21,11 +21,12 @@ 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; @@ -45,27 +46,21 @@ import java.util.Vector; 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; @@ -73,13 +68,13 @@ public class RotatableCanvas extends Panel implements MouseListener, 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; @@ -89,9 +84,9 @@ public class RotatableCanvas extends Panel implements MouseListener, Vector points; - float[][] orig; + Point[] orig; - float[][] axes; + Point[] axisEndPoints; int startx; @@ -115,9 +110,10 @@ public class RotatableCanvas extends Panel implements MouseListener, 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) @@ -127,46 +123,22 @@ public class RotatableCanvas extends Panel implements MouseListener, } @Override - public void setPoints(Vector points, int npoint) + public void setPoints(Vector 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(); @@ -196,92 +168,69 @@ public class RotatableCanvas extends Panel implements MouseListener, * 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 { @@ -291,18 +240,18 @@ public class RotatableCanvas extends Panel implements MouseListener, 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 @@ -360,7 +309,7 @@ public class RotatableCanvas extends Panel implements MouseListener, drawBackground(ig, Color.black); drawScene(ig); - if (drawAxes == true) + if (drawAxes) { drawAxes(ig); } @@ -382,8 +331,8 @@ public class RotatableCanvas extends Panel implements MouseListener, 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)); } } @@ -403,10 +352,10 @@ public class RotatableCanvas extends Panel implements MouseListener, 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) @@ -440,16 +389,6 @@ public class RotatableCanvas extends Panel implements MouseListener, } } - public Dimension minimumsize() - { - return prefsize; - } - - public Dimension preferredsize() - { - return prefsize; - } - @Override public void keyTyped(KeyEvent evt) { @@ -485,20 +424,6 @@ public class RotatableCanvas extends Panel implements MouseListener, 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) { @@ -525,11 +450,8 @@ public class RotatableCanvas extends Panel implements MouseListener, int x = evt.getX(); int y = evt.getY(); - mx = x; - my = y; - - omx = mx; - omy = my; + mouseX = x; + mouseY = y; startx = x; starty = y; @@ -540,7 +462,7 @@ public class RotatableCanvas extends Panel implements MouseListener, rectx2 = -1; recty2 = -1; - SequenceI found = findPoint(x, y); + SequenceI found = findSequenceAtPoint(x, y); if (found != null) { @@ -567,7 +489,7 @@ public class RotatableCanvas extends Panel implements MouseListener, @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; @@ -584,36 +506,32 @@ public class RotatableCanvas extends Panel implements MouseListener, @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()); } @@ -624,9 +542,9 @@ public class RotatableCanvas extends Panel implements MouseListener, 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(); @@ -644,9 +562,17 @@ public class RotatableCanvas extends Panel implements MouseListener, } } - 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; @@ -656,16 +582,18 @@ public class RotatableCanvas extends Panel implements MouseListener, { 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(); @@ -676,4 +604,13 @@ public class RotatableCanvas extends Panel implements MouseListener, } } + /** + * Resets the view to initial state (no rotation) + */ + public void resetView() + { + img = null; + resetAxes(); + } + } diff --git a/src/jalview/datamodel/Point.java b/src/jalview/datamodel/Point.java new file mode 100644 index 0000000..af6a634 --- /dev/null +++ b/src/jalview/datamodel/Point.java @@ -0,0 +1,20 @@ +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; + } +} diff --git a/src/jalview/datamodel/SequencePoint.java b/src/jalview/datamodel/SequencePoint.java index 28db278..7fcb713 100755 --- a/src/jalview/datamodel/SequencePoint.java +++ b/src/jalview/datamodel/SequencePoint.java @@ -32,9 +32,9 @@ public class SequencePoint 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 @@ -42,14 +42,55 @@ public class SequencePoint * @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); + } } diff --git a/src/jalview/gui/PCAPanel.java b/src/jalview/gui/PCAPanel.java index 641105b..f5e33cd 100644 --- a/src/jalview/gui/PCAPanel.java +++ b/src/jalview/gui/PCAPanel.java @@ -222,9 +222,7 @@ public class PCAPanel extends GPCAPanel 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()); } diff --git a/src/jalview/gui/RotatableCanvas.java b/src/jalview/gui/RotatableCanvas.java index dc33b36..617180f 100755 --- a/src/jalview/gui/RotatableCanvas.java +++ b/src/jalview/gui/RotatableCanvas.java @@ -21,11 +21,12 @@ 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; @@ -50,30 +51,20 @@ import javax.swing.JPanel; 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; @@ -81,13 +72,13 @@ public class RotatableCanvas extends JPanel implements MouseListener, 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; @@ -97,9 +88,9 @@ public class RotatableCanvas extends JPanel implements MouseListener, Vector points; - float[][] orig; + Point[] orig; - float[][] axes; + Point[] axisEndPoints; int startx; @@ -131,10 +122,16 @@ public class RotatableCanvas extends JPanel implements MouseListener, 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() { @@ -181,40 +178,15 @@ public class RotatableCanvas extends JPanel implements MouseListener, 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(); @@ -222,86 +194,56 @@ public class RotatableCanvas extends JPanel implements MouseListener, 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])); } /** @@ -339,20 +281,17 @@ public class RotatableCanvas extends JPanel implements MouseListener, } /** - * 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); } /** @@ -434,31 +373,37 @@ public class RotatableCanvas extends JPanel implements MouseListener, } /** - * 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) { @@ -467,14 +412,16 @@ public class RotatableCanvas extends JPanel implements MouseListener, } /** - * 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, @@ -486,10 +433,10 @@ public class RotatableCanvas extends JPanel implements MouseListener, 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) @@ -531,53 +478,20 @@ public class RotatableCanvas extends JPanel implements MouseListener, // } } - /** - * 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) @@ -594,7 +508,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, } 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)) @@ -638,11 +552,8 @@ public class RotatableCanvas extends JPanel implements MouseListener, int x = evt.getX(); int y = evt.getY(); - mx = x; - my = y; - - omx = mx; - omy = my; + mouseX = x; + mouseY = y; startx = x; starty = y; @@ -694,16 +605,21 @@ public class RotatableCanvas extends JPanel implements MouseListener, } /** - * 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) @@ -713,34 +629,55 @@ public class RotatableCanvas extends JPanel implements MouseListener, } 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()); } @@ -761,9 +698,9 @@ public class RotatableCanvas extends JPanel implements MouseListener, 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)) @@ -800,14 +737,15 @@ public class RotatableCanvas extends JPanel implements MouseListener, 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; } } @@ -821,6 +759,12 @@ public class RotatableCanvas extends JPanel implements MouseListener, } } + /** + * 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) @@ -832,20 +776,4 @@ public class RotatableCanvas extends JPanel implements MouseListener, 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; - } - } diff --git a/src/jalview/math/RotatableMatrix.java b/src/jalview/math/RotatableMatrix.java index 5971227..602c5e4 100755 --- a/src/jalview/math/RotatableMatrix.java +++ b/src/jalview/math/RotatableMatrix.java @@ -20,54 +20,86 @@ */ 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> cachedRotations; - float[][] rot; + static + { + cachedRotations = new HashMap<>(); + for (Axis axis : Axis.values()) + { + HashMap 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() { @@ -82,174 +114,137 @@ public class RotatableMatrix } /** - * 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 mat 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 mat. * * @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; } /** @@ -260,47 +255,47 @@ public class RotatableMatrix */ 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(); @@ -321,26 +316,15 @@ public class RotatableMatrix } /** - * 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]); } } diff --git a/src/jalview/viewmodel/PCAModel.java b/src/jalview/viewmodel/PCAModel.java index aa7c938..3403887 100644 --- a/src/jalview/viewmodel/PCAModel.java +++ b/src/jalview/viewmodel/PCAModel.java @@ -25,6 +25,7 @@ import jalview.api.RotatableCanvasI; 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; @@ -99,7 +100,7 @@ public class PCAModel 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++) { @@ -146,7 +147,7 @@ public class PCAModel 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++) { @@ -202,13 +203,10 @@ public class PCAModel } 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"); } @@ -223,10 +221,10 @@ public class PCAModel 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; } diff --git a/test/jalview/math/RotatableMatrixTest.java b/test/jalview/math/RotatableMatrixTest.java new file mode 100644 index 0000000..06982d5 --- /dev/null +++ b/test/jalview/math/RotatableMatrixTest.java @@ -0,0 +1,157 @@ +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); + } +} -- 1.7.10.2