1 /*
2  * Copyright 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.graphics;
18 
19 import android.annotation.FlaggedApi;
20 import android.annotation.IntRange;
21 import android.annotation.NonNull;
22 
23 import com.android.graphics.hwui.flags.Flags;
24 
25 import java.util.Arrays;
26 
27 /**
28  * The Matrix44 class holds a 4x4 matrix for transforming coordinates. It is similar to
29  * {@link Matrix}, and should be used when you want to manipulate the canvas in 3D. Values are kept
30  * in row-major order. The values and operations are treated as column vectors.
31  */
32 @FlaggedApi(Flags.FLAG_MATRIX_44)
33 public class Matrix44 {
34     final float[] mBackingArray;
35     /**
36      * The default Matrix44 constructor will instantiate an identity matrix.
37      */
38     @FlaggedApi(Flags.FLAG_MATRIX_44)
Matrix44()39     public Matrix44() {
40         mBackingArray = new float[]{1.0f, 0.0f, 0.0f, 0.0f,
41                                     0.0f, 1.0f, 0.0f, 0.0f,
42                                     0.0f, 0.0f, 1.0f, 0.0f,
43                                     0.0f, 0.0f, 0.0f, 1.0f};
44     }
45 
46     /**
47      * Creates and returns a Matrix44 by taking the 3x3 Matrix and placing it on the 0 of the z-axis
48      * by setting row {@code 2} and column {@code 2} to the identity as seen in the following
49      * operation:
50      * <pre class="prettyprint">
51      * [ a b c ]      [ a b 0 c ]
52      * [ d e f ]  ->  [ d e 0 f ]
53      * [ g h i ]      [ 0 0 1 0 ]
54      *                [ g h 0 i ]
55      * </pre>
56      *
57      * @param mat A 3x3 Matrix to be converted (original Matrix will not be changed)
58      */
59     @FlaggedApi(Flags.FLAG_MATRIX_44)
Matrix44(@onNull Matrix mat)60     public Matrix44(@NonNull Matrix mat) {
61         float[] m = new float[9];
62         mat.getValues(m);
63         mBackingArray = new float[]{m[0], m[1], 0.0f, m[2],
64                                     m[3], m[4], 0.0f, m[5],
65                                     0.0f, 0.0f, 1.0f, 0.0f,
66                                     m[6], m[7], 0.0f, m[8]};
67     }
68 
69     /**
70      * Copies matrix values into the provided array in row-major order.
71      *
72      * @param dst The float array where values will be copied, must be of length 16
73      * @throws IllegalArgumentException if the destination float array is not of length 16
74      */
75     @FlaggedApi(Flags.FLAG_MATRIX_44)
getValues(@onNull float [] dst)76     public void getValues(@NonNull float [] dst) {
77         if (dst.length == 16) {
78             System.arraycopy(mBackingArray, 0, dst, 0, mBackingArray.length);
79         } else {
80             throw new IllegalArgumentException("Dst array must be of length 16");
81         }
82     }
83 
84     /**
85      * Replaces the Matrix's values with the values in the provided array.
86      *
87      * @param src A float array of length 16. Floats are treated in row-major order
88      * @throws IllegalArgumentException if the destination float array is not of length 16
89      */
90     @FlaggedApi(Flags.FLAG_MATRIX_44)
setValues(@onNull float[] src)91     public void setValues(@NonNull float[] src) {
92         if (src.length == 16) {
93             System.arraycopy(src, 0, mBackingArray, 0, mBackingArray.length);
94         } else {
95             throw new IllegalArgumentException("Src array must be of length 16");
96         }
97     }
98 
99     /**
100      * Gets the value at the matrix's row and column.
101      *
102      * @param row An integer from 0 to 3 indicating the row of the value to get
103      * @param col An integer from 0 to 3 indicating the column of the value to get
104      */
105     @FlaggedApi(Flags.FLAG_MATRIX_44)
get(@ntRangefrom = 0, to = 3) int row, @IntRange(from = 0, to = 3) int col)106     public float get(@IntRange(from = 0, to = 3) int row, @IntRange(from = 0, to = 3) int col) {
107         if (row >= 0 && row < 4 && col >= 0 && col < 4) {
108             return mBackingArray[row * 4 + col];
109         }
110         throw new IllegalArgumentException("invalid row and column values");
111     }
112 
113     /**
114      * Sets the value at the matrix's row and column to the provided value.
115      *
116      * @param row An integer from 0 to 3 indicating the row of the value to change
117      * @param col An integer from 0 to 3 indicating the column of the value to change
118      * @param val The value the element at the specified index will be set to
119      */
120     @FlaggedApi(Flags.FLAG_MATRIX_44)
set(@ntRangefrom = 0, to = 3) int row, @IntRange(from = 0, to = 3) int col, float val)121     public void set(@IntRange(from = 0, to = 3) int row, @IntRange(from = 0, to = 3) int col,
122             float val) {
123         if (row >= 0 && row < 4 && col >= 0 && col < 4) {
124             mBackingArray[row * 4 + col] = val;
125         } else {
126             throw new IllegalArgumentException("invalid row and column values");
127         }
128     }
129 
130     /**
131      * Sets the Matrix44 to the identity matrix.
132      */
133     @FlaggedApi(Flags.FLAG_MATRIX_44)
reset()134     public void reset() {
135         for (int i = 0; i < mBackingArray.length; i++) {
136             mBackingArray[i] = (i % 4 == i / 4) ? 1.0f : 0.0f;
137         }
138     }
139 
140     /**
141      * Inverts the Matrix44, then return true if successful, false if unable to invert.
142      *
143      * @return {@code true} on success, {@code false} otherwise
144      */
145     @FlaggedApi(Flags.FLAG_MATRIX_44)
invert()146     public boolean invert() {
147         float a00 = mBackingArray[0];
148         float a01 = mBackingArray[1];
149         float a02 = mBackingArray[2];
150         float a03 = mBackingArray[3];
151         float a10 = mBackingArray[4];
152         float a11 = mBackingArray[5];
153         float a12 = mBackingArray[6];
154         float a13 = mBackingArray[7];
155         float a20 = mBackingArray[8];
156         float a21 = mBackingArray[9];
157         float a22 = mBackingArray[10];
158         float a23 = mBackingArray[11];
159         float a30 = mBackingArray[12];
160         float a31 = mBackingArray[13];
161         float a32 = mBackingArray[14];
162         float a33 = mBackingArray[15];
163         float b00 = a00 * a11 - a01 * a10;
164         float b01 = a00 * a12 - a02 * a10;
165         float b02 = a00 * a13 - a03 * a10;
166         float b03 = a01 * a12 - a02 * a11;
167         float b04 = a01 * a13 - a03 * a11;
168         float b05 = a02 * a13 - a03 * a12;
169         float b06 = a20 * a31 - a21 * a30;
170         float b07 = a20 * a32 - a22 * a30;
171         float b08 = a20 * a33 - a23 * a30;
172         float b09 = a21 * a32 - a22 * a31;
173         float b10 = a21 * a33 - a23 * a31;
174         float b11 = a22 * a33 - a23 * a32;
175         float det = (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06);
176         if (det == 0.0f) {
177             return false;
178         }
179         float invDet = 1.0f / det;
180         mBackingArray[0] = ((a11 * b11 - a12 * b10 + a13 * b09) * invDet);
181         mBackingArray[1] = ((-a01 * b11 + a02 * b10 - a03 * b09) * invDet);
182         mBackingArray[2] = ((a31 * b05 - a32 * b04 + a33 * b03) * invDet);
183         mBackingArray[3] = ((-a21 * b05 + a22 * b04 - a23 * b03) * invDet);
184         mBackingArray[4] = ((-a10 * b11 + a12 * b08 - a13 * b07) * invDet);
185         mBackingArray[5] = ((a00 * b11 - a02 * b08 + a03 * b07) * invDet);
186         mBackingArray[6] = ((-a30 * b05 + a32 * b02 - a33 * b01) * invDet);
187         mBackingArray[7] = ((a20 * b05 - a22 * b02 + a23 * b01) * invDet);
188         mBackingArray[8] = ((a10 * b10 - a11 * b08 + a13 * b06) * invDet);
189         mBackingArray[9] = ((-a00 * b10 + a01 * b08 - a03 * b06) * invDet);
190         mBackingArray[10] = ((a30 * b04 - a31 * b02 + a33 * b00) * invDet);
191         mBackingArray[11] = ((-a20 * b04 + a21 * b02 - a23 * b00) * invDet);
192         mBackingArray[12] = ((-a10 * b09 + a11 * b07 - a12 * b06) * invDet);
193         mBackingArray[13] = ((a00 * b09 - a01 * b07 + a02 * b06) * invDet);
194         mBackingArray[14] = ((-a30 * b03 + a31 * b01 - a32 * b00) * invDet);
195         mBackingArray[15] = ((a20 * b03 - a21 * b01 + a22 * b00) * invDet);
196         return true;
197     }
198 
199     /**
200      * Returns true if Matrix44 is equal to identity matrix.
201      */
202     @FlaggedApi(Flags.FLAG_MATRIX_44)
isIdentity()203     public boolean isIdentity() {
204         for (int i = 0; i < mBackingArray.length; i++) {
205             float expected = (i % 4 == i / 4) ? 1.0f : 0.0f;
206             if (expected != mBackingArray[i]) return false;
207         }
208         return true;
209     }
210 
211     @FlaggedApi(Flags.FLAG_MATRIX_44)
dot(Matrix44 a, Matrix44 b, int row, int col)212     private static float dot(Matrix44 a, Matrix44 b, int row, int col) {
213         return (a.get(row, 0) * b.get(0, col))
214                 + (a.get(row, 1) * b.get(1, col))
215                 + (a.get(row, 2) * b.get(2, col))
216                 + (a.get(row, 3) * b.get(3, col));
217     }
218 
219     @FlaggedApi(Flags.FLAG_MATRIX_44)
dot(float r0, float r1, float r2, float r3, float c0, float c1, float c2, float c3)220     private static float dot(float r0, float r1, float r2, float r3,
221                              float c0, float c1, float c2, float c3) {
222         return (r0 * c0) + (r1 * c1) + (r2 * c2) + (r3 * c3);
223     }
224 
225     /**
226      * Multiplies (x, y, z, w) vector by the Matrix44, then returns the new (x, y, z, w). Users
227      * should set {@code w} to 1 to indicate the coordinates are normalized.
228      *
229      * @return An array of length 4 that represents the x, y, z, w (where w is perspective) value
230      * after multiplying x, y, z, 1 by the matrix
231      */
232     @FlaggedApi(Flags.FLAG_MATRIX_44)
map(float x, float y, float z, float w)233     public @NonNull float[] map(float x, float y, float z, float w) {
234         float[] dst = new float[4];
235         this.map(x, y, z, w, dst);
236         return dst;
237     }
238 
239     /**
240      * Multiplies (x, y, z, w) vector by the Matrix44, then returns the new (x, y, z, w). Users
241      * should set {@code w} to 1 to indicate the coordinates are normalized.
242      */
243     @FlaggedApi(Flags.FLAG_MATRIX_44)
map(float x, float y, float z, float w, @NonNull float[] dst)244     public void map(float x, float y, float z, float w, @NonNull float[] dst) {
245         if (dst.length != 4) {
246             throw new IllegalArgumentException("Dst array must be of length 4");
247         }
248         dst[0] = x * mBackingArray[0] + y * mBackingArray[1]
249                 + z * mBackingArray[2] + w * mBackingArray[3];
250         dst[1] = x * mBackingArray[4] + y * mBackingArray[5]
251                 + z * mBackingArray[6] + w * mBackingArray[7];
252         dst[2] = x * mBackingArray[8] + y * mBackingArray[9]
253                 + z * mBackingArray[10] + w * mBackingArray[11];
254         dst[3] = x * mBackingArray[12] + y * mBackingArray[13]
255                 + z * mBackingArray[14] + w * mBackingArray[15];
256     }
257 
258     /**
259      * Multiplies `this` matrix (A) and provided Matrix (B) in the order of A * B.
260      * The result is saved in `this` Matrix.
261      *
262      * @param b The second Matrix in the concatenation operation
263      * @return A reference to this Matrix, which can be used to chain Matrix operations
264      */
265     @FlaggedApi(Flags.FLAG_MATRIX_44)
concat(@onNull Matrix44 b)266     public @NonNull Matrix44 concat(@NonNull Matrix44 b) {
267         float val00 = dot(this, b, 0, 0);
268         float val01 = dot(this, b, 0, 1);
269         float val02 = dot(this, b, 0, 2);
270         float val03 = dot(this, b, 0, 3);
271         float val10 = dot(this, b, 1, 0);
272         float val11 = dot(this, b, 1, 1);
273         float val12 = dot(this, b, 1, 2);
274         float val13 = dot(this, b, 1, 3);
275         float val20 = dot(this, b, 2, 0);
276         float val21 = dot(this, b, 2, 1);
277         float val22 = dot(this, b, 2, 2);
278         float val23 = dot(this, b, 2, 3);
279         float val30 = dot(this, b, 3, 0);
280         float val31 = dot(this, b, 3, 1);
281         float val32 = dot(this, b, 3, 2);
282         float val33 = dot(this, b, 3, 3);
283 
284         mBackingArray[0] = val00;
285         mBackingArray[1] = val01;
286         mBackingArray[2] = val02;
287         mBackingArray[3] = val03;
288         mBackingArray[4] = val10;
289         mBackingArray[5] = val11;
290         mBackingArray[6] = val12;
291         mBackingArray[7] = val13;
292         mBackingArray[8] = val20;
293         mBackingArray[9] = val21;
294         mBackingArray[10] = val22;
295         mBackingArray[11] = val23;
296         mBackingArray[12] = val30;
297         mBackingArray[13] = val31;
298         mBackingArray[14] = val32;
299         mBackingArray[15] = val33;
300 
301         return this;
302     }
303 
304     /**
305      * Applies a rotation around a given axis, then returns self.
306      * {@code x}, {@code y}, {@code z} represent the axis by which to rotate around.
307      * For example, pass in {@code 1, 0, 0} to rotate around the x-axis.
308      * The axis provided will be normalized.
309      *
310      * @param deg Amount in degrees to rotate the matrix about the x-axis
311      * @param xComp X component of the rotation axis
312      * @param yComp Y component of the rotation axis
313      * @param zComp Z component of the rotation axis
314      * @return A reference to this Matrix, which can be used to chain Matrix operations
315      */
316     @FlaggedApi(Flags.FLAG_MATRIX_44)
rotate(float deg, float xComp, float yComp, float zComp)317     public @NonNull Matrix44 rotate(float deg, float xComp, float yComp, float zComp) {
318         float sum = xComp + yComp + zComp;
319         float x = xComp / sum;
320         float y = yComp / sum;
321         float z = zComp / sum;
322 
323         float c = (float) Math.cos(deg * Math.PI / 180.0f);
324         float s = (float) Math.sin(deg * Math.PI / 180.0f);
325         float t = 1 - c;
326 
327         float rotVals00 = t * x * x + c;
328         float rotVals01 = t * x * y - s * z;
329         float rotVals02 = t * x * z + s * y;
330         float rotVals10 = t * x * y + s * z;
331         float rotVals11 = t * y * y + c;
332         float rotVals12 = t * y * z - s * x;
333         float rotVals20 = t * x * z - s * y;
334         float rotVals21 = t * y * z + s * x;
335         float rotVals22 = t * z * z + c;
336 
337         float v00 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
338                 rotVals00, rotVals10, rotVals20, 0);
339         float v01 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
340                 rotVals01, rotVals11, rotVals21, 0);
341         float v02 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
342                 rotVals02, rotVals12, rotVals22, 0);
343         float v03 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
344                 0, 0, 0, 1);
345         float v10 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
346                 rotVals00, rotVals10, rotVals20, 0);
347         float v11 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
348                 rotVals01, rotVals11, rotVals21, 0);
349         float v12 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
350                 rotVals02, rotVals12, rotVals22, 0);
351         float v13 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
352                 0, 0, 0, 1);
353         float v20 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
354                 rotVals00, rotVals10, rotVals20, 0);
355         float v21 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
356                 rotVals01, rotVals11, rotVals21, 0);
357         float v22 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
358                 rotVals02, rotVals12, rotVals22, 0);
359         float v23 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
360                 0, 0, 0, 1);
361         float v30 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
362                 rotVals00, rotVals10, rotVals20, 0);
363         float v31 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
364                 rotVals01, rotVals11, rotVals21, 0);
365         float v32 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
366                 rotVals02, rotVals12, rotVals22, 0);
367         float v33 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
368                 0, 0, 0, 1);
369 
370         mBackingArray[0] = v00;
371         mBackingArray[1] = v01;
372         mBackingArray[2] = v02;
373         mBackingArray[3] = v03;
374         mBackingArray[4] = v10;
375         mBackingArray[5] = v11;
376         mBackingArray[6] = v12;
377         mBackingArray[7] = v13;
378         mBackingArray[8] = v20;
379         mBackingArray[9] = v21;
380         mBackingArray[10] = v22;
381         mBackingArray[11] = v23;
382         mBackingArray[12] = v30;
383         mBackingArray[13] = v31;
384         mBackingArray[14] = v32;
385         mBackingArray[15] = v33;
386 
387         return this;
388     }
389 
390     /**
391      * Applies scaling factors to `this` Matrix44, then returns self. Pass 1s for no change.
392      *
393      * @param x Scaling factor for the x-axis
394      * @param y Scaling factor for the y-axis
395      * @param z Scaling factor for the z-axis
396      * @return A reference to this Matrix, which can be used to chain Matrix operations
397      */
398     @FlaggedApi(Flags.FLAG_MATRIX_44)
scale(float x, float y, float z)399     public @NonNull Matrix44 scale(float x, float y, float z) {
400         mBackingArray[0] *= x;
401         mBackingArray[4] *= x;
402         mBackingArray[8] *= x;
403         mBackingArray[12] *= x;
404         mBackingArray[1] *= y;
405         mBackingArray[5] *= y;
406         mBackingArray[9] *= y;
407         mBackingArray[13] *= y;
408         mBackingArray[2] *= z;
409         mBackingArray[6] *= z;
410         mBackingArray[10] *= z;
411         mBackingArray[14] *= z;
412 
413         return this;
414     }
415 
416     /**
417      * Applies a translation to `this` Matrix44, then returns self.
418      *
419      * @param x Translation for the x-axis
420      * @param y Translation for the y-axis
421      * @param z Translation for the z-axis
422      * @return A reference to this Matrix, which can be used to chain Matrix operations
423      */
424     @FlaggedApi(Flags.FLAG_MATRIX_44)
translate(float x, float y, float z)425     public @NonNull Matrix44 translate(float x, float y, float z) {
426         float newX = x * mBackingArray[0] + y * mBackingArray[1]
427                 + z * mBackingArray[2] + mBackingArray[3];
428         float newY = x * mBackingArray[4] + y * mBackingArray[5]
429                 + z * mBackingArray[6] + mBackingArray[7];
430         float newZ = x * mBackingArray[8] + y * mBackingArray[9]
431                 + z * mBackingArray[10] + mBackingArray[11];
432         float newW = x * mBackingArray[12] + y * mBackingArray[13]
433                 + z * mBackingArray[14] + mBackingArray[15];
434 
435         mBackingArray[3] = newX;
436         mBackingArray[7] = newY;
437         mBackingArray[11] = newZ;
438         mBackingArray[15] = newW;
439 
440         return this;
441     }
442 
443     @Override
toString()444     public String toString() {
445         return String.format("""
446                         | %f %f %f %f |
447                         | %f %f %f %f |
448                         | %f %f %f %f |
449                         | %f %f %f %f |
450                         """, mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
451                 mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
452                 mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
453                 mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15]);
454     }
455 
456     @Override
equals(Object obj)457     public boolean equals(Object obj) {
458         if (obj instanceof Matrix44) {
459             return Arrays.equals(mBackingArray, ((Matrix44) obj).mBackingArray);
460         }
461         return false;
462     }
463 
464     @Override
hashCode()465     public int hashCode() {
466         return (int) mBackingArray[0] + (int) mBackingArray[1] + (int) mBackingArray[2]
467                 + (int) mBackingArray[3] + (int) mBackingArray[4] + (int) mBackingArray[5]
468                 + (int) mBackingArray[6] + (int) mBackingArray[7] + (int) mBackingArray[8]
469                 + (int) mBackingArray[9] + (int) mBackingArray[10] + (int) mBackingArray[11]
470                 + (int) mBackingArray[12] + (int) mBackingArray[13] + (int) mBackingArray[14]
471                 + (int) mBackingArray[15];
472     }
473 
474 }
475