Merge branch 'features/pca_jaxb_datasetrefs_JAL-3171_JAL-3063_JAL-1767' into develop
[jalview.git] / src / jalview / math / RotatableMatrix.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.math;
22
23 import jalview.datamodel.Point;
24
25 import java.util.HashMap;
26 import java.util.Map;
27
28 /**
29  * Model for a 3x3 matrix which provides methods for rotation in 3-D space
30  */
31 public class RotatableMatrix
32 {
33   private static final int DIMS = 3;
34
35   /*
36    * cache the most used rotations: +/- 1, 2, 3, 4 degrees around x or y axis
37    */
38   private static Map<Axis, Map<Float, float[][]>> cachedRotations;
39
40   static
41   {
42     cachedRotations = new HashMap<>();
43     for (Axis axis : Axis.values())
44     {
45       HashMap<Float, float[][]> map = new HashMap<>();
46       cachedRotations.put(axis, map);
47       for (int deg = 1; deg < 5; deg++)
48       {
49         float[][] rotation = getRotation(deg, axis);
50         map.put(Float.valueOf(deg), rotation);
51         rotation = getRotation(-deg, axis);
52         map.put(Float.valueOf(-deg), rotation);
53       }
54     }
55   }
56
57   public enum Axis
58   {
59     X, Y, Z
60   };
61
62   float[][] matrix;
63
64   /**
65    * Constructor creates a new identity matrix (all values zero except for 1 on
66    * the diagonal)
67    */
68   public RotatableMatrix()
69   {
70     matrix = new float[DIMS][DIMS];
71     for (int j = 0; j < DIMS; j++)
72     {
73       matrix[j][j] = 1f;
74     }
75   }
76
77   /**
78    * Sets the value at position (i, j) of the matrix
79    * 
80    * @param i
81    * @param j
82    * @param value
83    */
84   public void setValue(int i, int j, float value)
85   {
86     matrix[i][j] = value;
87   }
88
89   /**
90    * Answers the value at position (i, j) of the matrix
91    * 
92    * @param i
93    * @param j
94    * @return
95    */
96   public float getValue(int i, int j)
97   {
98     return matrix[i][j];
99   }
100
101   /**
102    * Prints the matrix in rows of space-delimited values
103    */
104   public void print()
105   {
106     System.out.println(
107             matrix[0][0] + " " + matrix[0][1] + " " + matrix[0][2]);
108
109     System.out.println(
110             matrix[1][0] + " " + matrix[1][1] + " " + matrix[1][2]);
111
112     System.out.println(
113             matrix[2][0] + " " + matrix[2][1] + " " + matrix[2][2]);
114   }
115
116   /**
117    * Rotates the matrix through the specified number of degrees around the
118    * specified axis
119    * 
120    * @param degrees
121    * @param axis
122    */
123   public void rotate(float degrees, Axis axis)
124   {
125     float[][] rot = getRotation(degrees, axis);
126
127     preMultiply(rot);
128   }
129
130   /**
131    * Answers a matrix which, when it pre-multiplies another matrix, applies a
132    * rotation of the specified number of degrees around the specified axis
133    * 
134    * @param degrees
135    * @param axis
136    * @return
137    * @see https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations
138    */
139   protected static float[][] getRotation(float degrees, Axis axis)
140   {
141     Float floatValue = Float.valueOf(degrees);
142     if (cachedRotations.get(axis).containsKey(floatValue))
143     {
144       // System.out.println("getRotation from cache: " + (int) degrees);
145       return cachedRotations.get(axis).get(floatValue);
146     }
147
148     float costheta = (float) Math.cos(degrees * Math.PI / 180f);
149
150     float sintheta = (float) Math.sin(degrees * Math.PI / 180f);
151
152     float[][] rot = new float[DIMS][DIMS];
153
154     switch (axis)
155     {
156     case X:
157       rot[0][0] = 1f;
158       rot[1][1] = costheta;
159       rot[1][2] = sintheta;
160       rot[2][1] = -sintheta;
161       rot[2][2] = costheta;
162       break;
163     case Y:
164       rot[0][0] = costheta;
165       rot[0][2] = -sintheta;
166       rot[1][1] = 1f;
167       rot[2][0] = sintheta;
168       rot[2][2] = costheta;
169       break;
170     case Z:
171       rot[0][0] = costheta;
172       rot[0][1] = -sintheta;
173       rot[1][0] = sintheta;
174       rot[1][1] = costheta;
175       rot[2][2] = 1f;
176       break;
177     }
178     return rot;
179   }
180
181   /**
182    * Answers a new array of float values which is the result of pre-multiplying
183    * this matrix by the given vector. Each value of the result is the dot
184    * product of the vector with one column of this matrix. The matrix and input
185    * vector are not modified.
186    * 
187    * @param vect
188    * 
189    * @return
190    */
191   public float[] vectorMultiply(float[] vect)
192   {
193     float[] result = new float[DIMS];
194
195     for (int i = 0; i < DIMS; i++)
196     {
197       result[i] = (matrix[i][0] * vect[0]) + (matrix[i][1] * vect[1])
198               + (matrix[i][2] * vect[2]);
199     }
200
201     return result;
202   }
203
204   /**
205    * Performs pre-multiplication of this matrix by the given one. Value (i, j)
206    * of the result is the dot product of the i'th row of <code>mat</code> with
207    * the j'th column of this matrix.
208    * 
209    * @param mat
210    */
211   public void preMultiply(float[][] mat)
212   {
213     float[][] tmp = new float[DIMS][DIMS];
214
215     for (int i = 0; i < DIMS; i++)
216     {
217       for (int j = 0; j < DIMS; j++)
218       {
219         tmp[i][j] = (mat[i][0] * matrix[0][j]) + (mat[i][1] * matrix[1][j])
220                 + (mat[i][2] * matrix[2][j]);
221       }
222     }
223
224     matrix = tmp;
225   }
226
227   /**
228    * Performs post-multiplication of this matrix by the given one. Value (i, j)
229    * of the result is the dot product of the i'th row of this matrix with the
230    * j'th column of <code>mat</code>.
231    * 
232    * @param mat
233    */
234   public void postMultiply(float[][] mat)
235   {
236     float[][] tmp = new float[DIMS][DIMS];
237
238     for (int i = 0; i < DIMS; i++)
239     {
240       for (int j = 0; j < DIMS; j++)
241       {
242         tmp[i][j] = (matrix[i][0] * mat[0][j]) + (matrix[i][1] * mat[1][j])
243                 + (matrix[i][2] * mat[2][j]);
244       }
245     }
246
247     matrix = tmp;
248   }
249
250   /**
251    * DOCUMENT ME!
252    * 
253    * @param args
254    *          DOCUMENT ME!
255    */
256   public static void main(String[] args)
257   {
258     RotatableMatrix m = new RotatableMatrix();
259
260     m.setValue(0, 0, 1);
261
262     m.setValue(0, 1, 0);
263
264     m.setValue(0, 2, 0);
265
266     m.setValue(1, 0, 0);
267
268     m.setValue(1, 1, 2);
269
270     m.setValue(1, 2, 0);
271
272     m.setValue(2, 0, 0);
273
274     m.setValue(2, 1, 0);
275
276     m.setValue(2, 2, 1);
277
278     m.print();
279
280     RotatableMatrix n = new RotatableMatrix();
281
282     n.setValue(0, 0, 2);
283
284     n.setValue(0, 1, 1);
285
286     n.setValue(0, 2, 1);
287
288     n.setValue(1, 0, 2);
289
290     n.setValue(1, 1, 1);
291
292     n.setValue(1, 2, 1);
293
294     n.setValue(2, 0, 2);
295
296     n.setValue(2, 1, 1);
297
298     n.setValue(2, 2, 1);
299
300     n.print();
301
302     // m.postMultiply(n.matrix);
303     // m.print();
304     // m.rotate(45,'z',new RotatableMatrix(3,3));
305     float[] vect = new float[3];
306
307     vect[0] = 2;
308
309     vect[1] = 4;
310
311     vect[2] = 6;
312
313     vect = m.vectorMultiply(vect);
314
315     System.out.println(vect[0] + " " + vect[1] + " " + vect[2]);
316   }
317
318   /**
319    * Performs a vector multiplication whose result is the Point representing the
320    * input point's value vector post-multiplied by this matrix.
321    * 
322    * @param coord
323    * @return
324    */
325   public Point vectorMultiply(Point coord)
326   {
327     float[] v = vectorMultiply(new float[] { coord.x, coord.y, coord.z });
328     return new Point(v[0], v[1], v[2]);
329   }
330 }