/* * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Codename One designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Codename One through http://www.codenameone.com/ if you * need additional information or have any questions. */ package com.codename1.impl.android; import android.opengl.Matrix; import java.util.Arrays; /** * Encapsulates a 4x4 transformation matrix that can be used to apply 3D transformations * to a {@link com.codename1.ui.Graphics} context. This can also be used for 2D transformations, * by only using the upper left 3x3 grid of the matrix. * * <h4>Internal Representation</h4> * * <p>Although matrix data can be set in several different formats (See {@link #setData}), the internal representation * is always that of a 4x4 matrix stored in a 16-element {@literal float} array in <a target="_blank" href="http://en.wikipedia.org/wiki/Row-major_order">row-major order</a>. * If you are working with 2D transformations only, then the upper left sub-matrix will contain * your 3x3 affine transformation, and the 4th row and 4th columns will be zeroes, except in the lower-right most * column, which will be a {@literal 1}.</p> * * @author shannah * @see com.codename1.ui.Graphics#setTransform * @see com.codename1.ui.Graphics#getTransform */ public final class CN1Matrix4f { public static final int TYPE_UNKNOWN = -1; public static final int TYPE_IDENTITY = 0; public static final int TYPE_TRANSLATION = 1; public static final int TYPE_ROTATION = 2; public static final int TYPE_SCALE = 3; public static final int M00=0; public static final int M01=4; public static final int M02=8; public static final int M03=12; public static final int M10=1; public static final int M11=5; public static final int M12=9; public static final int M13=13; public static final int M20=2; public static final int M21=6; public static final int M22=10; public static final int M23=14; public static final int M30=3; public static final int M31=7; public static final int M32=11; public static final int M33=15; public final float[] data; private int type = TYPE_UNKNOWN; private Factory factory; public static class Factory { private float[] sTemp = new float[32]; private static Factory defaultFactory = null; public static Factory getDefault() { if (defaultFactory == null) { defaultFactory = new Factory(); } return defaultFactory; } public CN1Matrix4f makeMatrix(float[] data) { CN1Matrix4f m = new CN1Matrix4f(data); m.factory = this; return m; } public CN1Matrix4f makeIdentity() { CN1Matrix4f out = makeMatrix(null); out.factory = this; out.type = TYPE_IDENTITY; return out; } public CN1Matrix4f makeRotation(float angle, float x, float y, float z) { float[] m = new float[16]; Matrix.setRotateM(m, 0, (float) (angle * 180f / Math.PI), x, y, z); CN1Matrix4f out = makeMatrix(m); out.factory = this; out.type = TYPE_ROTATION; return out; } public CN1Matrix4f makeTranslation(float x, float y, float z) { CN1Matrix4f m = makeIdentity(); Matrix.translateM(m.data, 0, x, y, z); m.factory = this; m.type = TYPE_TRANSLATION; return m; } public CN1Matrix4f makePerspective(float fovy, float aspect, float zNear, float zFar) { float[] m = new float[16]; Matrix.perspectiveM(m, 0, (float) (fovy * 180f / Math.PI), aspect, zNear, zFar); CN1Matrix4f out = new CN1Matrix4f(m); out.factory = this; return out; } public CN1Matrix4f makeOrtho(float left, float right, float bottom, float top, float near, float far) { float[] m = new float[16]; Matrix.orthoM(m, 0, left, right, bottom, top, near, far); CN1Matrix4f out = new CN1Matrix4f(m); out.factory = this; return out; } public CN1Matrix4f makeCamera(float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ) { float[] m = new float[16]; Matrix.setLookAtM(m, 0, eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ); CN1Matrix4f out = new CN1Matrix4f(m); out.factory = this; return out; } } public boolean equals(CN1Matrix4f matrix){ if ( matrix == null ){ return false; } return Arrays.equals(data, matrix.data); } public static CN1Matrix4f make(float[] data) { return Factory.getDefault().makeMatrix(data); } public static CN1Matrix4f makeIdentity() { return Factory.getDefault().makeMatrix(null); } public static CN1Matrix4f makeTranslation(float x, float y, float z){ return Factory.getDefault().makeTranslation(x, y, z); } public static CN1Matrix4f makeRotation(float angle, float x, float y, float z) { return Factory.getDefault().makeRotation(angle, x, y, z); } public static CN1Matrix4f makeOrtho(float left, float right, float bottom, float top, float near, float far) { return Factory.getDefault().makeOrtho(left, right, bottom, top, near, far); } public static CN1Matrix4f makePerspective(float fovy, float aspect, float zNear, float zFar) { return Factory.getDefault().makePerspective(fovy, aspect, zNear, zFar); } public static CN1Matrix4f makeCamera(float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ) { return Factory.getDefault().makeCamera(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ); } public void rotate(float a, float x, float y, float z) { Matrix.setRotateM(factory.sTemp, 0, (float) (a * 180f / Math.PI), x, y, z); Matrix.multiplyMM(factory.sTemp, 16, data, 0, factory.sTemp, 0); System.arraycopy(factory.sTemp, 16, data, 0, 16); if ( type == TYPE_IDENTITY ){ type = TYPE_ROTATION; } else { type = TYPE_UNKNOWN; } } public void translate(float x, float y, float z) { Matrix.translateM(data, 0, x, y, z); if ( type == TYPE_IDENTITY || type == TYPE_TRANSLATION ){ type = TYPE_TRANSLATION; } else { type = TYPE_UNKNOWN; } } public void scale(float x, float y, float z) { Matrix.scaleM(data, 0, x, y, z); if ( type == TYPE_IDENTITY || type == TYPE_SCALE ){ type = TYPE_SCALE; } else { type = TYPE_UNKNOWN; } } public void setPerspective(float fovy, float aspect, float zNear, float zFar) { Matrix.perspectiveM(data, 0, (float) (fovy * 180f / Math.PI), aspect, zNear, zFar); type = TYPE_UNKNOWN; } public void setOrtho(float left, float right, float bottom, float top, float near, float far) { Matrix.orthoM(data, 0, left, right, bottom, top, near, far); type = TYPE_UNKNOWN; } public void setCamera(float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ) { Matrix.setLookAtM(data, 0, eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ); type = TYPE_UNKNOWN; } public void setIdentity() { reset(); } public void transformCoord(float[] pIn, float[] pOut) { //Log.p("Transforming "+pIn[0]+","+pIn[1]); //Log.p("Transform is "+this); int len = pIn.length; factory.sTemp[2] = 0; factory.sTemp[3] = 1f; System.arraycopy(pIn, 0, factory.sTemp, 0, len); Matrix.multiplyMV(factory.sTemp, 4, data, 0, factory.sTemp, 0); float w = factory.sTemp[7]; if ( w != 1 && w != 0 ){ for ( int i=4; i<7; i++){ factory.sTemp[i] = factory.sTemp[i]/w; } } //len = pOut.length; System.arraycopy(factory.sTemp, 4, pOut, 0, len); } public String toString() { //StringBuilder sb = new StringBuilder(); return "[[" + data[0] + "," + data[4] + "," + data[8] + "," + data[12] + "]\n" + "[" + data[1] + "," + data[5] + "," + data[9] + "," + data[13] + "]\n" + "[" + data[2] + "," + data[6] + "," + data[10] + "," + data[14] + "]\n" + "[" + data[3] + "," + data[7] + "," + data[11] + "," + data[15] + "]"; } public boolean isIdentity() { for (int i = 0; i < 16; i++) { if (i % 5 == 0 && data[i] != 1f) { return false; } else if (i % 5 != 0 && data[i] != 0f) { return false; } } return true; } public boolean invert() { boolean res = Matrix.invertM(factory.sTemp, 0, data, 0); if (!res) { return res; } else { System.arraycopy(factory.sTemp, 0, data, 0, 16); return res; } } /** * Constructor. Copies data from the provided data array. See * {@link #setData} documentation for information acceptable formats for the * {@literal m} array. * * @param m An array containing data for the matrix. This can be in several * different formats. See {@link #setData} for a list of acceptable formats. * * @see #setData */ private CN1Matrix4f(float[] m) { //Log.p("Creating new matrix"); if (m == null) { m = new float[]{1f}; } if (m.length == 16) { data = m; } else { data = new float[16]; setData(m); } //Log.p("Exiting CN1Matrix4f constructor"); } public void concatenate(CN1Matrix4f m){ //Matrix.setRotateM(factory.sTemp, 0, (float) (a * 180f / Math.PI), x, y, z); Matrix.multiplyMM(factory.sTemp, 16, data, 0, m.data, 0); System.arraycopy(factory.sTemp, 16, data, 0, 16); type = TYPE_UNKNOWN; } /** * Resets the transformation to the identify matrix */ public void reset() { for (int i = 0; i < 16; i++) { data[i] = 0; } data[0] = data[5] = data[10] = data[15] = 1; type = TYPE_IDENTITY; } /** * Obtains a reference to the 4x4 matrix cell data in <a target="_blank" * href="http://en.wikipedia.org/wiki/Row-major_order">row-major order</a>. * * @return A 16-element{@literal float} array representing the 4x4 matrix * data in row-major order. */ public float[] getData() { return data; } /** * Sets the matrix data. This will accept the data in several different * formats to facilitate the creation of common matrix use-cases. * <h5>Acceptable Formats</h5> * * <table> * <tr><th>Array * Length</th><th>Interpretation</th><th>Example</th><th>Resulting 4x4 * CN1Matrix4f</th></tr> * <tr> * <td>1</td> * <td>Apply both {@literal x} and {@literal y} scaling with a single * value.</td> * <td>{@code setData(new float[]{2f});}</td> * <td><pre>{@literal * [2,0,0,0], * [0,2,0,0], * [0,0,1,0], * [0,0,0,1]}</pre></td> * </tr> * <tr> * <td>2</td> * <td>Applies {@literal x} and {@literal y} scaling. First element is * {@literal x} scale. Second element is {@literal y} scale.</td> * <td>{@code setData(new float[]{2f, 3f});}</td> * <td><pre>{@literal * [2,0,0,0], * [0,3,0,0], * [0,0,1,0], * [0,0,0,1]}</pre></td> * </tr> * <tr> * <td>4</td> * <td>Recognized as a 2x2 2D transformation matrix.</td> * <td>{@code setData(new float[]{1f, 2f, 3f, 4f});}</td> * <td><pre>{@literal * [1,2,0,0], * [3,4,0,0], * [0,0,1,0], * [0,0,0,1]}</pre></td> * </tr> * <tr> * <td>6</td> * <td>An affine transformation.</td> * <td>{@code setData(new float[]{1f, 2f, 3f, 4f, 5f, 6f});}</td> * <td><pre>{@literal * [1,2,3,0], * [4,5,6,0], * [0,0,1,0], * [0,0,0,1]}</pre></td> * </tr> * <tr> * <td>9</td> * <td>A 3x3 matrix.</td> * <td>{@code setData(new float[]{1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f});}</td> * <td><pre>{@literal * [1,2,3,0], * [4,5,6,0], * [7,8,9,0], * [0,0,0,1]}</pre></td> * </tr> * <tr> * <td>12</td> * <td>The top 3 rows of the 4x4 matrix. This is all the information * necessary for a 3D transformation since the last row is always * [0,0,0,1].</td> * <td>{@code setData(new float[]{1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 10f, 11f, 12f});}</td> * <td><pre>{@literal * [ 1, 2, 3, 4], * [ 5, 6, 7, 8], * [ 9, 10,11,12], * [ 0, 0, 0, 1]}</pre></td> * </tr> * <tr> * <td>16</td> * <td>A 4x4 transformation matrix.</td> * <td>{@code setData(new float[]{1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 10f, 11f, 12f, 13f, 14f, 15f, 16f});}</td> * <td><pre>{@literal * [ 1, 2, 3, 4], * [ 5, 6, 7, 8], * [ 9,10,11,12], * [13,14,15,16]}</pre></td> * </tr> * </table> * * @param m The data to populate the matrix. This will always replace the * matrix data in full. */ public void setData(float[] m) { if (m == null) { reset(); return; } switch (m.length) { case 1: reset(); data[0] = m[0]; data[5] = m[0]; break; case 2: reset(); data[0] = m[0]; data[5] = m[1]; break; case 4: // This is just a 2D transformation reset(); data[0] = m[0]; data[1] = m[1]; data[4] = m[2]; data[5] = m[3]; break; case 6: reset(); data[0] = m[0]; data[1] = m[1]; data[2] = m[2]; data[4] = m[3]; data[5] = m[4]; data[6] = m[5]; break; case 9: reset(); data[0] = m[0]; data[1] = m[1]; data[2] = m[2]; data[4] = m[3]; data[5] = m[4]; data[6] = m[5]; data[8] = m[6]; data[9] = m[7]; data[10] = m[8]; break; case 12: reset(); System.arraycopy(m, 0, data, 0, 12); break; case 16: System.arraycopy(m, 0, data, 0, 16); break; default: throw new IllegalArgumentException("Transforms must be array of length 1, 2, 4, 6, 9, 12, or 16"); } } public CN1Matrix4f copy() { float[] data = new float[16]; System.arraycopy(this.data, 0, data, 0, 16); return CN1Matrix4f.make(data); } }