1   package fi.jyu.mit.graphics;
2   import static java.lang.Math.round;
3   
4   /**
5    * Matriisliluokka kuvamuunnoslaskuihin. 
6    * @author Markus Kivioja
7    * @author Vesa Lappalainen
8    *
9    */
10  public class Matrix {
11      
12      protected final static int VSIZE = 4;
13      private double mat[][] = new double[VSIZE][VSIZE];
14      private Matrix inversion;
15      
16      /**
17       * Luo uuden 4x4 yksikkömatriin
18       */
19      public Matrix() {
20          this.ident();
21      }
22      
23      /**
24       * Luo kopion matriisista M
25       * @param M matriisi josta kopio luodaan
26       */
27      public Matrix(Matrix M) {
28          for (int r = 0; r < VSIZE; r++)
29              for (int s = 0; s < VSIZE; s++)
30                mat[r][s] = M.mat[r][s];
31      }
32      
33      /**
34       * Muuttaa matriisin yksikkömatriisiksi
35       */
36      protected final void ident() {
37          this.inversion = null;
38          for (int r = 0; r < VSIZE; r++)
39              for (int s = 0; s < VSIZE; s++)
40                  mat[r][s] = 0.0;
41          for (int r = 0; r < VSIZE; r++)
42              mat[r][r] = 1.0;
43      }
44      
45      /**
46       * Palauttaa matriisin r:n rivin kopion
47       * @param r palautettava rivi
48       * @return r:n rivin kopio
49       */
50      public Vector row(int r) {   // NOPMD
51          Vector v = new Vector(); // NOPMD
52          for (int s = 0; s < VSIZE; s++) {
53              v.set(s, mat[r][s]);
54          }
55          return v;
56      }
57      
58      /**
59       * Palauttaa matriisin s:n sarakkeen kopion
60       * @param s palautettava sarake
61       * @return s:n sarakkeen kopio
62       */
63      public Vector column(int s) { // NOPMD
64          Vector v = new Vector();  // NOPMD
65          for (int r = 0; r < VSIZE; r++) {
66              v.set(r, mat[r][s]);
67          }
68          return v;
69      }
70      
71      /**
72       * Asettaa tämän matriisin annetuksi riviksi annetun vektorin
73       * @param r muutettava rivi
74       * @param v vektori joka asetetaan tämän matriisin riviksi
75       */
76      public void setRow(int r, Vector v) {
77          this.inversion = null;
78          for (int i = 0; i < VSIZE; i++)
79              mat[r][i] = v.a(i);
80      }
81      
82      /**
83       * Asettaa tämän matriisin annetuksi sarakkeeksi annetun vektorin
84       * @param s muutettava sarake
85       * @param v vektori joka asetetaan tämän matriisin sarakkeeksi
86       */
87      public void setColumn(int s, Vector v) {
88          this.inversion = null;
89          for (int i = 0; i < VSIZE; i++)
90              mat[i][s] = v.a(i);
91      }
92      
93      /**
94       * Palauttaa matriisin alkion paikassa r,s
95       * @param r rivi jolta alkio halutaan
96       * @param s sarake jolta alkio halutaan
97       * @return r,s paikassa oleva alkio
98       */
99      public double m(int r, int s) { // NOPMD
100         return mat[r][s];
101     }
102     
103     /**
104      * Asettaa matriisin paikassa r,s olevan alkion
105      * @param r rivi jonka alkio asetetaan
106      * @param s sarake jonka alkio asetetaan
107      * @param d uusi arvo alkiolle
108      */
109     protected void set(int r, int s, double d) {
110         this.inversion = null;
111         mat[r][s] = d;
112     }
113     
114     /**
115      * Kertoo matriisin b matriisilla ja palauttaa uuden matriisin tuloksena
116      * @param b matriisi joka kerrotaan
117      * @return uusi matriisi this * b
118      */
119     public Matrix multiply(Matrix b) {
120         if ( b == null ) return this;
121         return multiply(b,new Matrix());
122     }
123 
124     /**
125      * Kertoo matriisin b matriisilla ja palauttaa uuden matriisin tuloksena.
126      * this eikä b ei saa olla c!
127      * @param b matriisi joka kerrotaan
128      * @param c matriisi johon tulos tulee
129      * @return uusi matriisi this * b
130      */
131     public Matrix multiply(Matrix b, Matrix c) {
132         // if ( b == c ) throw new Execption
133         for (int r = 0; r < VSIZE; r++)
134             for (int s = 0; s < VSIZE; s++) {
135                 double sum = 0;
136                 for (int i = 0; i < VSIZE; i++) 
137                     sum += mat[r][i] * b.mat[i][s];
138                 c.mat[r][s] = sum;  
139             }   
140         return c;
141     }
142     
143     /**
144      * Kertoo matriisin b matriisilla ja laittaa tuloksen itseensä
145      * @param b matriisi joka kerrotaan
146      * @return  this * b
147      */
148     public Matrix multiplyThis(Matrix b) {
149         this.inversion = null;
150         double row[] = {0,0,0,0};
151         for (int r = 0; r < VSIZE; r++) {
152             for (int s = 0; s < VSIZE; s++) {
153                 double sum = 0;
154                 for (int i = 0; i < VSIZE; i++) 
155                     sum += mat[r][i] * b.mat[i][s];
156                 row[s] = sum;   
157             }   
158             for (int s = 0; s < VSIZE; s++) 
159                 mat[r][s] = row[s];
160         }   
161         return this;
162     }
163     
164     /**
165      * Kertoo matriisin b matriisilla ja laittaa tuloksen itseensä
166      * @param b matriisi joka kerrotaan
167      * @param row lakun avuksi tuotava 4 paikkainen taulukko
168      * @return  this * b
169      */
170     public Matrix multiplyThis(Matrix b,double row[]) {
171         this.inversion = null;
172         //double row[] = {0,0,0,0};
173         for (int r = 0; r < VSIZE; r++) {
174             for (int s = 0; s < VSIZE; s++) {
175                 double sum = 0;
176                 for (int i = 0; i < VSIZE; i++) 
177                     sum += mat[r][i] * b.mat[i][s];
178                 row[s] = sum;   
179             }   
180             for (int s = 0; s < VSIZE; s++) 
181                 mat[r][s] = row[s];
182         }   
183         return this;
184     }
185     
186     /**
187      * Kertoo vektorin tällä matriisilla, luo uuden paluuvektorin
188      * @param b vektori joka kerrotaan
189      * @return this * b
190      */
191     public Vector multiply(Vector b) { // NOPMD
192         Vector y = new Vector();       // NOPMD
193         for (int r = 0; r < VSIZE; r++) {
194             double sum = 0;
195             for (int s = 0; s < VSIZE; s++)
196                 sum += mat[r][s] * b.a(s);
197             y.set(r, sum );
198         }
199         return y;
200     }
201     
202     /**
203      * Kertoo vektorin tällä matriisilla ja tuloksen vektoriin y
204      * @param b vektori joka kerrotaan
205      * @param y vektori johon tulos tulee
206      * @return this * b
207      */
208     public Vector multiply(Vector b, Vector y) { // NOPMD
209         for (int r = 0; r < VSIZE; r++) {
210             double sum = 0;
211             for (int s = 0; s < VSIZE; s++)
212                 sum += mat[r][s] * b.a(s);
213             y.set(r, sum );
214         }
215         return y;
216     }
217     
218     /**
219      * Kertoo vektorin tällä matriisilla ja sijoittaa tuloksen näyttökoordinaattiin iy.
220      * Spesiaalikertominen erityisesti kuvamuunnoksiin, joissa
221      * tarvitaan vain 2 ensimmäistä alkiota vektorista iy.
222      * @param b vektori joka kerrotaan
223      * @param iy vektori johon tulos tulee
224      * @return this * b
225      */
226     public SPoint transform(Vector b, SPoint iy) { // NOPMD
227         double sum0 = 0, sum1 = 0;
228         for (int s = 0; s < VSIZE; s++) sum0 += mat[0][s] * b.a(s);
229         for (int s = 0; s < VSIZE; s++) sum1 += mat[1][s] * b.a(s);
230         // TODO: pitäisi vielä laskea viimeisen rivin arvo, jos
231         //       se on !=1; niin sillä jaetaan x ja y.
232         //       Tarvitaan sitten kun mat[3] rivillä on jotakin
233         return iy.set((int)round(sum0), (int)round(sum1));
234     }
235     
236     /**
237      * Kertoo vektorin tällä matriisilla ja sijoittaa tuloksen näyttökoordinaattiin iy.
238      * Spesiaalikertominen erityisesti kuvamuunnoksiin, joissa
239      * tarvitaan vain 2 ensimmäistä alkiota vektorista iy.
240      * @param b vektori joka kerrotaan
241      * @param iy vektori johon tulos tulee
242      * @return this * b
243      */
244     public RPoint transform(Vector b, RPoint iy) { // NOPMD
245         double sum0 = 0, sum1 = 0;
246         for (int s = 0; s < VSIZE; s++) sum0 += mat[0][s] * b.a(s);
247         for (int s = 0; s < VSIZE; s++) sum1 += mat[1][s] * b.a(s);
248         // TODO: pitäisi vielä laskea viimeisen rivin arvo, jos
249         //       se on !=1; niin sillä jaetaan x ja y.
250         //       Tarvitaan sitten kun mat[3] rivillä on jotakin
251         iy.set(sum0,sum1,0);
252         return iy; 
253     }
254     
255     /**
256      * Kertoo vektorin tällä matriisilla ja sijoittaa tuloksen näyttökoordinaattiin iy.
257      * Spesiaalikertominen erityisesti kuvamuunnoksiin, joissa
258      * tarvitaan vain 2 ensimmäistä alkiota vektorista iy.
259      * @param b vektori joka kerrotaan
260      * @param iy vektori johon tulos tulee
261      * @return this * b
262      */
263     public Vector transform(Vector b,  Vector iy) { // NOPMD
264         double sum0 = 0, sum1 = 0;
265         for (int s = 0; s < VSIZE; s++) sum0 += mat[0][s] * b.a(s);
266         for (int s = 0; s < VSIZE; s++) sum1 += mat[1][s] * b.a(s);
267         // TODO: pitäisi vielä laskea viimeisen rivin arvo, jos
268         //       se on !=1; niin sillä jaetaan x ja y.
269         //       Tarvitaan sitten kun mat[3] rivillä on jotakin
270         return iy.set(sum0, sum1);
271     }
272     
273     /**
274      * Muuntaa koordinaatin (x,y,z) näytön pisteeksi. 
275      * @param x
276      * @param y
277      * @param z
278      * @return uusi näytön piste joka edustaa (x,y,z)
279      */
280     public SPoint transform(double x, double y, double z) {
281         Vector vr = new Vector(x, y, z); // NOPMD
282         SPoint sp = new SPoint(0,0);
283         return this.transform(vr,sp);
284     }
285     
286     /**
287      * Muuntaa koordinaatin (x,y,z) näytön pisteeksi.
288      * "Optimoitu" versio, jolle viedään työtilat parametrina. 
289      * @param x
290      * @param y
291      * @param z
292      * @param vr vektori johon saa sijoittaa väliaikaisesti (x,y,z)
293      * @param sp näytön piste johon tuloksen saa sijoittaa.
294      * @return parametrina tuotu näytön piste joka edustaa (x,y,z)
295      */
296     public SPoint transform(double x, double y, double z, Vector vr, SPoint sp) { // NOPMD
297         return this.transform(vr.set(x,y,z),sp);
298     }
299     
300 
301     /**
302      * Muuntaa koordinaatin (x,y,z) näytön pisteeksi.
303      * "Optimoitu" versio, jolle viedään työtilat parametrina. 
304      * @param x
305      * @param y
306      * @param z
307      * @param vr vektori johon saa sijoittaa väliaikaisesti (x,y,z)
308      * @param sp näytön piste johon tuloksen saa sijoittaa.
309      * @return parametrina tuotu näytön piste joka edustaa (x,y,z)
310      */
311     public Vector transform(double x, double y, double z, Vector vr, Vector sp) { // NOPMD
312         return this.transform(vr.set(x,y,z),sp);
313     }
314     
315 
316     /**
317      * Muuntaa pisteen p näytön pisteeksi. 
318      * @param p muunettava piste.
319      * @return uusi näytönpiste joka vastaa pistettä p.
320      */
321     public SPoint transform(RPoint p) {
322         SPoint sp = new SPoint(0,0);
323         return this.transform(p,sp);
324     }
325     
326     /**
327      * Muuntaa pisteen p näytön pisteeksi. 
328      * @param p muunettava piste.
329      * @return uusi näytönpiste joka vastaa pistettä p.
330      */
331     public RPoint transformR(RPoint p) {
332         RPoint rp = new RPoint(0,0);
333         return this.transform(p,rp);
334     }
335     
336     /**
337      * Laskee tämän matriisin käänteismatriisin Gaussin 
338      * eliminointimenetelmällä ja palauttaa sen.
339      * (Menetelmä on hidas suurilla VSIZE-muuttujan arvoilla)
340      * @return inversion, tämän matriisin käänteismatriisi
341      */
342     public Matrix getInversion() {
343         if (this.inversion == null) {
344             Matrix temp = new Matrix(this);
345             Matrix tempInversion = new Matrix();
346             double multiplier = 0;
347 
348             // Nollataan diagonaalin alapuolella olevat alkiot
349             for (int i = 0; i < VSIZE; i++) {
350                 for (int j = i + 1; j < VSIZE; j++) {
351                     multiplier = -(temp.m(j, i) / temp.m(i, i));
352                     temp.setRow(j, temp.row(j).sum(
353                             temp.row(i).scalarProduct(multiplier)));
354                     tempInversion.setRow(j, tempInversion.row(j).sum(
355                             tempInversion.row(i).scalarProduct(multiplier)));
356                 }
357             }
358 
359             // Nollataan diagonaalin yläpuolella olevat alkiot
360             for (int i = VSIZE - 1; i >= 0; i--) {
361                 for (int j = i - 1; j >= 0; j--) {
362                     multiplier = -(temp.m(j, i) / temp.m(i, i));
363                     temp.setRow(j, temp.row(j).sum(
364                             temp.row(i).scalarProduct(multiplier)));
365                     tempInversion.setRow(j, tempInversion.row(j).sum(
366                             tempInversion.row(i).scalarProduct(multiplier)));
367                 }
368             }
369 
370             // Jaetaan rivit diagonaalialkioilla
371             for (int i = 0; i < VSIZE; i++) {
372                 multiplier = 1 / temp.m(i, i);
373                 tempInversion.setRow(i, tempInversion.row(i).scalarProduct(
374                         multiplier));
375             }
376             this.inversion = tempInversion;
377         }
378         return this.inversion;
379     }
380     
381     /**
382      * Palauttaa tämän matrisiin merkkijonona
383      * @return tämä matriisi merkkijonona
384      */
385     @Override
386     public String toString() {
387         StringBuilder sb = new StringBuilder();
388         for (int r = 0; r < VSIZE; r++) {
389             sb.append("( ");
390             for (int s = 0; s < VSIZE; s++) 
391                 sb.append(" " + mat[r][s]);
392             sb.append(" )\n");
393         }   
394         return sb.toString();       
395     }
396     
397 }
398 
399