f3bf436e13bb5b03877a796fa04a2ac4a1c54316
[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.io.PrintStream;
26 import java.util.HashMap;
27 import java.util.Map;
28
29 /**
30  * Model for a 3x3 matrix which provides methods for rotation in 3-D space
31  */
32 public class RotatableMatrix
33 {
34   private static final int DIMS = 3;
35
36   /*
37    * cache the most used rotations: +/- 1, 2, 3, 4 degrees around x or y axis
38    */
39   private static Map<Axis, Map<Float, float[][]>> cachedRotations;
40
41   static
42   {
43     cachedRotations = new HashMap<>();
44     for (Axis axis : Axis.values())
45     {
46       HashMap<Float, float[][]> map = new HashMap<>();
47       cachedRotations.put(axis, map);
48       for (int deg = 1; deg < 5; deg++)
49       {
50         float[][] rotation = getRotation(deg, axis);
51         map.put(Float.valueOf(deg), rotation);
52         rotation = getRotation(-deg, axis);
53         map.put(Float.valueOf(-deg), rotation);
54       }
55     }
56   }
57
58   public enum Axis
59   {
60     X, Y, Z
61   }
62
63   float[][] matrix;
64
65   /**
66    * Constructor creates a new identity matrix (all values zero except for 1 on
67    * the diagonal)
68    */
69   public RotatableMatrix()
70   {
71     matrix = new float[DIMS][DIMS];
72     for (int j = 0; j < DIMS; j++)
73     {
74       matrix[j][j] = 1f;
75     }
76   }
77
78   /**
79    * Sets the value at position (i, j) of the matrix
80    * 
81    * @param i
82    * @param j
83    * @param value
84    */
85   public void setValue(int i, int j, float value)
86   {
87     matrix[i][j] = value;
88   }
89
90   /**
91    * Answers the value at position (i, j) of the matrix
92    * 
93    * @param i
94    * @param j
95    * @return
96    */
97   public float getValue(int i, int j)
98   {
99     return matrix[i][j];
100   }
101
102   /**
103    * Prints the matrix in rows of space-delimited values
104    */
105   public void print(PrintStream ps)
106   {
107     ps.println(matrix[0][0] + " " + matrix[0][1] + " " + matrix[0][2]);
108     ps.println(matrix[1][0] + " " + matrix[1][1] + " " + matrix[1][2]);
109     ps.println(matrix[2][0] + " " + matrix[2][1] + " " + matrix[2][2]);
110   }
111
112   /**
113    * Rotates the matrix through the specified number of degrees around the
114    * specified axis
115    * 
116    * @param degrees
117    * @param axis
118    */
119   public void rotate(float degrees, Axis axis)
120   {
121     float[][] rot = getRotation(degrees, axis);
122
123     preMultiply(rot);
124   }
125
126   /**
127    * Answers a matrix which, when it pre-multiplies another matrix, applies a
128    * rotation of the specified number of degrees around the specified axis
129    * 
130    * @param degrees
131    * @param axis
132    * @return
133    * @see https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations
134    */
135   protected static float[][] getRotation(float degrees, Axis axis)
136   {
137     Float floatValue = Float.valueOf(degrees);
138     if (cachedRotations.get(axis).containsKey(floatValue))
139     {
140       // System.out.println("getRotation from cache: " + (int) degrees);
141       return cachedRotations.get(axis).get(floatValue);
142     }
143
144     float costheta = (float) Math.cos(degrees * Math.PI / 180f);
145
146     float sintheta = (float) Math.sin(degrees * Math.PI / 180f);
147
148     float[][] rot = new float[DIMS][DIMS];
149
150     switch (axis)
151     {
152     case X:
153       rot[0][0] = 1f;
154       rot[1][1] = costheta;
155       rot[1][2] = sintheta;
156       rot[2][1] = -sintheta;
157       rot[2][2] = costheta;
158       break;
159     case Y:
160       rot[0][0] = costheta;
161       rot[0][2] = -sintheta;
162       rot[1][1] = 1f;
163       rot[2][0] = sintheta;
164       rot[2][2] = costheta;
165       break;
166     case Z:
167       rot[0][0] = costheta;
168       rot[0][1] = -sintheta;
169       rot[1][0] = sintheta;
170       rot[1][1] = costheta;
171       rot[2][2] = 1f;
172       break;
173     }
174     return rot;
175   }
176
177   /**
178    * Answers a new array of float values which is the result of pre-multiplying
179    * this matrix by the given vector. Each value of the result is the dot
180    * product of the vector with one column of this matrix. The matrix and input
181    * vector are not modified.
182    * 
183    * @param vect
184    * 
185    * @return
186    */
187   public float[] vectorMultiply(float[] vect)
188   {
189     float[] result = new float[DIMS];
190
191     for (int i = 0; i < DIMS; i++)
192     {
193       result[i] = (matrix[i][0] * vect[0]) + (matrix[i][1] * vect[1])
194               + (matrix[i][2] * vect[2]);
195     }
196
197     return result;
198   }
199
200   /**
201    * Performs pre-multiplication of this matrix by the given one. Value (i, j)
202    * of the result is the dot product of the i'th row of <code>mat</code> with
203    * the j'th column of this matrix.
204    * 
205    * @param mat
206    */
207   public void preMultiply(float[][] mat)
208   {
209     float[][] tmp = new float[DIMS][DIMS];
210
211     for (int i = 0; i < DIMS; i++)
212     {
213       for (int j = 0; j < DIMS; j++)
214       {
215         tmp[i][j] = (mat[i][0] * matrix[0][j]) + (mat[i][1] * matrix[1][j])
216                 + (mat[i][2] * matrix[2][j]);
217       }
218     }
219
220     matrix = tmp;
221   }
222
223   /**
224    * Performs post-multiplication of this matrix by the given one. Value (i, j)
225    * of the result is the dot product of the i'th row of this matrix with the
226    * j'th column of <code>mat</code>.
227    * 
228    * @param mat
229    */
230   public void postMultiply(float[][] mat)
231   {
232     float[][] tmp = new float[DIMS][DIMS];
233
234     for (int i = 0; i < DIMS; i++)
235     {
236       for (int j = 0; j < DIMS; j++)
237       {
238         tmp[i][j] = (matrix[i][0] * mat[0][j]) + (matrix[i][1] * mat[1][j])
239                 + (matrix[i][2] * mat[2][j]);
240       }
241     }
242
243     matrix = tmp;
244   }
245
246   /**
247    * Performs a vector multiplication whose result is the Point representing the
248    * input point's value vector post-multiplied by this matrix.
249    * 
250    * @param coord
251    * @return
252    */
253   public Point vectorMultiply(Point coord)
254   {
255     float[] v = vectorMultiply(new float[] { coord.x, coord.y, coord.z });
256     return new Point(v[0], v[1], v[2]);
257   }
258 }