mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-03-29 07:48:17 +08:00
Decompose transform matrix in native (for android).
Summary: This diff translates implementation of transform matrix decomposition from JS to java. This is to support offloading animations of transform property, in which case it is required that we can calculate decomposed transform in the UI thread. Since the matrix decomposition code is not being used for other platform I went ahead and deleted parts that are no longer being used. **Test plan** Run UIExplorer Transform example before and after - compare the results Closes https://github.com/facebook/react-native/pull/7916 Reviewed By: ritzau Differential Revision: D3398393 Pulled By: astreet fbshipit-source-id: 9881c3f565e2050e415849b0f76a0cefe11c6afb
This commit is contained in:
committed by
Facebook Github Bot 8
parent
d1690a8f9e
commit
a59afb98d5
@@ -189,6 +189,10 @@ const APIExamples = [
|
||||
key: 'ToastAndroidExample',
|
||||
module: require('./ToastAndroidExample'),
|
||||
},
|
||||
{
|
||||
key: 'TransformExample',
|
||||
module: require('./TransformExample'),
|
||||
},
|
||||
{
|
||||
key: 'VibrationExample',
|
||||
module: require('./VibrationExample'),
|
||||
|
||||
@@ -81,13 +81,6 @@ function processTransform(transform: Object): Object {
|
||||
}
|
||||
});
|
||||
|
||||
// Android does not support the direct application of a transform matrix to
|
||||
// a view, so we need to decompose the result matrix into transforms that can
|
||||
// get applied in the specific order of (1) translate (2) scale (3) rotate.
|
||||
// Once we can directly apply a matrix, we can remove this decomposition.
|
||||
if (Platform.OS === 'android') {
|
||||
return MatrixMath.decomposeMatrix(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
@@ -17,14 +18,6 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
||||
extends ViewManager<T, C> {
|
||||
|
||||
private static final String PROP_BACKGROUND_COLOR = ViewProps.BACKGROUND_COLOR;
|
||||
private static final String PROP_DECOMPOSED_MATRIX = "decomposedMatrix";
|
||||
private static final String PROP_DECOMPOSED_MATRIX_ROTATE = "rotate";
|
||||
private static final String PROP_DECOMPOSED_MATRIX_ROTATE_X = "rotateX";
|
||||
private static final String PROP_DECOMPOSED_MATRIX_ROTATE_Y = "rotateY";
|
||||
private static final String PROP_DECOMPOSED_MATRIX_SCALE_X = "scaleX";
|
||||
private static final String PROP_DECOMPOSED_MATRIX_SCALE_Y = "scaleY";
|
||||
private static final String PROP_DECOMPOSED_MATRIX_TRANSLATE_X = "translateX";
|
||||
private static final String PROP_DECOMPOSED_MATRIX_TRANSLATE_Y = "translateY";
|
||||
private static final String PROP_TRANSFORM = "transform";
|
||||
private static final String PROP_OPACITY = "opacity";
|
||||
private static final String PROP_ELEVATION = "elevation";
|
||||
@@ -46,28 +39,21 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
||||
*/
|
||||
public static final String PROP_TEST_ID = "testID";
|
||||
|
||||
private static MatrixMathHelper.MatrixDecompositionContext sMatrixDecompositionContext =
|
||||
new MatrixMathHelper.MatrixDecompositionContext();
|
||||
private static double[] sTransformDecompositionArray = new double[16];
|
||||
|
||||
@ReactProp(name = PROP_BACKGROUND_COLOR, defaultInt = Color.TRANSPARENT, customType = "Color")
|
||||
public void setBackgroundColor(T view, int backgroundColor) {
|
||||
view.setBackgroundColor(backgroundColor);
|
||||
}
|
||||
|
||||
// TODO: t11041683 Remove this duplicate property name.
|
||||
@ReactProp(name = PROP_DECOMPOSED_MATRIX)
|
||||
public void setDecomposedMatrix(T view, ReadableMap decomposedMatrix) {
|
||||
if (decomposedMatrix == null) {
|
||||
resetTransformMatrix(view);
|
||||
} else {
|
||||
setTransformMatrix(view, decomposedMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_TRANSFORM)
|
||||
public void setTransform(T view, ReadableMap decomposedMatrix) {
|
||||
if (decomposedMatrix == null) {
|
||||
public void setTransform(T view, ReadableArray matrix) {
|
||||
if (matrix == null) {
|
||||
resetTransformMatrix(view);
|
||||
} else {
|
||||
setTransformMatrix(view, decomposedMatrix);
|
||||
setTransformMatrix(view, matrix);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,21 +146,20 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
||||
}
|
||||
}
|
||||
|
||||
private static void setTransformMatrix(View view, ReadableMap matrix) {
|
||||
view.setTranslationX(PixelUtil.toPixelFromDIP(
|
||||
(float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_TRANSLATE_X)));
|
||||
view.setTranslationY(PixelUtil.toPixelFromDIP(
|
||||
(float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_TRANSLATE_Y)));
|
||||
view.setRotation(
|
||||
(float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_ROTATE));
|
||||
view.setRotationX(
|
||||
(float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_ROTATE_X));
|
||||
view.setRotationY(
|
||||
(float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_ROTATE_Y));
|
||||
view.setScaleX(
|
||||
(float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_SCALE_X));
|
||||
view.setScaleY(
|
||||
(float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_SCALE_Y));
|
||||
private static void setTransformMatrix(View view, ReadableArray matrix) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
sTransformDecompositionArray[i] = matrix.getDouble(i);
|
||||
}
|
||||
MatrixMathHelper.decomposeMatrix(sTransformDecompositionArray, sMatrixDecompositionContext);
|
||||
view.setTranslationX(
|
||||
PixelUtil.toPixelFromDIP((float) sMatrixDecompositionContext.translation[0]));
|
||||
view.setTranslationY(
|
||||
PixelUtil.toPixelFromDIP((float) sMatrixDecompositionContext.translation[1]));
|
||||
view.setRotation((float) sMatrixDecompositionContext.rotationDegrees[2]);
|
||||
view.setRotationX((float) sMatrixDecompositionContext.rotationDegrees[0]);
|
||||
view.setRotationY((float) sMatrixDecompositionContext.rotationDegrees[1]);
|
||||
view.setScaleX((float) sMatrixDecompositionContext.scale[0]);
|
||||
view.setScaleY((float) sMatrixDecompositionContext.scale[1]);
|
||||
}
|
||||
|
||||
private static void resetTransformMatrix(View view) {
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
package com.facebook.react.uimanager;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
|
||||
/**
|
||||
* Provides helper methods from decomposing transform matrix into list of translate, scale and
|
||||
* rotate commands.
|
||||
*/
|
||||
public class MatrixMathHelper {
|
||||
|
||||
private static final double EPSILON = .00001d;
|
||||
|
||||
public static class MatrixDecompositionContext {
|
||||
double[] perspective = new double[4];
|
||||
double[] quaternion = new double[4];
|
||||
double[] scale = new double[3];
|
||||
double[] skew = new double[3];
|
||||
double[] translation = new double[3];
|
||||
double[] rotationDegrees = new double[3];
|
||||
}
|
||||
|
||||
private static boolean isZero(double d) {
|
||||
if (Double.isNaN(d)) {
|
||||
return false;
|
||||
}
|
||||
return Math.abs(d) < EPSILON;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param transformMatrix 16-element array of numbers representing 4x4 transform matrix
|
||||
*/
|
||||
public static void decomposeMatrix(double[] transformMatrix, MatrixDecompositionContext ctx) {
|
||||
Assertions.assertCondition(transformMatrix.length == 16);
|
||||
|
||||
// output values
|
||||
final double[] perspective = ctx.perspective;
|
||||
final double[] quaternion = ctx.quaternion;
|
||||
final double[] scale = ctx.scale;
|
||||
final double[] skew = ctx.skew;
|
||||
final double[] translation = ctx.translation;
|
||||
final double[] rotationDegrees = ctx.rotationDegrees;
|
||||
|
||||
// create normalized, 2d array matrix
|
||||
// and normalized 1d array perspectiveMatrix with redefined 4th column
|
||||
if (isZero(transformMatrix[15])) {
|
||||
return;
|
||||
}
|
||||
double[][] matrix = new double[4][4];
|
||||
double[] perspectiveMatrix = new double[16];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
double value = transformMatrix[(i * 4) + j] / transformMatrix[15];
|
||||
matrix[i][j] = value;
|
||||
perspectiveMatrix[(i * 4) + j] = j == 3 ? 0 : value;
|
||||
}
|
||||
}
|
||||
perspectiveMatrix[15] = 1;
|
||||
|
||||
// test for singularity of upper 3x3 part of the perspective matrix
|
||||
if (isZero(determinant(perspectiveMatrix))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// isolate perspective
|
||||
if (!isZero(matrix[0][3]) || !isZero(matrix[1][3]) || !isZero(matrix[2][3])) {
|
||||
// rightHandSide is the right hand side of the equation.
|
||||
// rightHandSide is a vector, or point in 3d space relative to the origin.
|
||||
double[] rightHandSide = { matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3] };
|
||||
|
||||
// Solve the equation by inverting perspectiveMatrix and multiplying
|
||||
// rightHandSide by the inverse.
|
||||
double[] inversePerspectiveMatrix = inverse(
|
||||
perspectiveMatrix
|
||||
);
|
||||
double[] transposedInversePerspectiveMatrix = transpose(
|
||||
inversePerspectiveMatrix
|
||||
);
|
||||
multiplyVectorByMatrix(rightHandSide, transposedInversePerspectiveMatrix, perspective);
|
||||
} else {
|
||||
// no perspective
|
||||
perspective[0] = perspective[1] = perspective[2] = 0d;
|
||||
perspective[3] = 1d;
|
||||
}
|
||||
|
||||
// translation is simple
|
||||
for (int i = 0; i < 3; i++) {
|
||||
translation[i] = matrix[3][i];
|
||||
}
|
||||
|
||||
// Now get scale and shear.
|
||||
// 'row' is a 3 element array of 3 component vectors
|
||||
double[][] row = new double[3][3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
row[i][0] = matrix[i][0];
|
||||
row[i][1] = matrix[i][1];
|
||||
row[i][2] = matrix[i][2];
|
||||
}
|
||||
|
||||
// Compute X scale factor and normalize first row.
|
||||
scale[0] = v3Length(row[0]);
|
||||
row[0] = v3Normalize(row[0], scale[0]);
|
||||
|
||||
// Compute XY shear factor and make 2nd row orthogonal to 1st.
|
||||
skew[0] = v3Dot(row[0], row[1]);
|
||||
row[1] = v3Combine(row[1], row[0], 1.0, -skew[0]);
|
||||
|
||||
// Compute XY shear factor and make 2nd row orthogonal to 1st.
|
||||
skew[0] = v3Dot(row[0], row[1]);
|
||||
row[1] = v3Combine(row[1], row[0], 1.0, -skew[0]);
|
||||
|
||||
// Now, compute Y scale and normalize 2nd row.
|
||||
scale[1] = v3Length(row[1]);
|
||||
row[1] = v3Normalize(row[1], scale[1]);
|
||||
skew[0] /= scale[1];
|
||||
|
||||
// Compute XZ and YZ shears, orthogonalize 3rd row
|
||||
skew[1] = v3Dot(row[0], row[2]);
|
||||
row[2] = v3Combine(row[2], row[0], 1.0, -skew[1]);
|
||||
skew[2] = v3Dot(row[1], row[2]);
|
||||
row[2] = v3Combine(row[2], row[1], 1.0, -skew[2]);
|
||||
|
||||
// Next, get Z scale and normalize 3rd row.
|
||||
scale[2] = v3Length(row[2]);
|
||||
row[2] = v3Normalize(row[2], scale[2]);
|
||||
skew[1] /= scale[2];
|
||||
skew[2] /= scale[2];
|
||||
|
||||
// At this point, the matrix (in rows) is orthonormal.
|
||||
// Check for a coordinate system flip. If the determinant
|
||||
// is -1, then negate the matrix and the scaling factors.
|
||||
double[] pdum3 = v3Cross(row[1], row[2]);
|
||||
if (v3Dot(row[0], pdum3) < 0) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
scale[i] *= -1;
|
||||
row[i][0] *= -1;
|
||||
row[i][1] *= -1;
|
||||
row[i][2] *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Now, get the rotations out
|
||||
quaternion[0] =
|
||||
0.5 * Math.sqrt(Math.max(1 + row[0][0] - row[1][1] - row[2][2], 0));
|
||||
quaternion[1] =
|
||||
0.5 * Math.sqrt(Math.max(1 - row[0][0] + row[1][1] - row[2][2], 0));
|
||||
quaternion[2] =
|
||||
0.5 * Math.sqrt(Math.max(1 - row[0][0] - row[1][1] + row[2][2], 0));
|
||||
quaternion[3] =
|
||||
0.5 * Math.sqrt(Math.max(1 + row[0][0] + row[1][1] + row[2][2], 0));
|
||||
|
||||
if (row[2][1] > row[1][2]) {
|
||||
quaternion[0] = -quaternion[0];
|
||||
}
|
||||
if (row[0][2] > row[2][0]) {
|
||||
quaternion[1] = -quaternion[1];
|
||||
}
|
||||
if (row[1][0] > row[0][1]) {
|
||||
quaternion[2] = -quaternion[2];
|
||||
}
|
||||
|
||||
// correct for occasional, weird Euler synonyms for 2d rotation
|
||||
|
||||
if (quaternion[0] < 0.001 && quaternion[0] >= 0 &&
|
||||
quaternion[1] < 0.001 && quaternion[1] >= 0) {
|
||||
// this is a 2d rotation on the z-axis
|
||||
rotationDegrees[0] = rotationDegrees[1] = 0d;
|
||||
rotationDegrees[2] = roundTo3Places(Math.atan2(row[0][1], row[0][0]) * 180 / Math.PI);
|
||||
} else {
|
||||
quaternionToDegreesXYZ(quaternion, rotationDegrees);
|
||||
}
|
||||
}
|
||||
|
||||
public static double determinant(double[] matrix) {
|
||||
double m00 = matrix[0], m01 = matrix[1], m02 = matrix[2], m03 = matrix[3], m10 = matrix[4],
|
||||
m11 = matrix[5], m12 = matrix[6], m13 = matrix[7], m20 = matrix[8], m21 = matrix[9],
|
||||
m22 = matrix[10], m23 = matrix[11], m30 = matrix[12], m31 = matrix[13], m32 = matrix[14],
|
||||
m33 = matrix[15];
|
||||
return (
|
||||
m03 * m12 * m21 * m30 - m02 * m13 * m21 * m30 -
|
||||
m03 * m11 * m22 * m30 + m01 * m13 * m22 * m30 +
|
||||
m02 * m11 * m23 * m30 - m01 * m12 * m23 * m30 -
|
||||
m03 * m12 * m20 * m31 + m02 * m13 * m20 * m31 +
|
||||
m03 * m10 * m22 * m31 - m00 * m13 * m22 * m31 -
|
||||
m02 * m10 * m23 * m31 + m00 * m12 * m23 * m31 +
|
||||
m03 * m11 * m20 * m32 - m01 * m13 * m20 * m32 -
|
||||
m03 * m10 * m21 * m32 + m00 * m13 * m21 * m32 +
|
||||
m01 * m10 * m23 * m32 - m00 * m11 * m23 * m32 -
|
||||
m02 * m11 * m20 * m33 + m01 * m12 * m20 * m33 +
|
||||
m02 * m10 * m21 * m33 - m00 * m12 * m21 * m33 -
|
||||
m01 * m10 * m22 * m33 + m00 * m11 * m22 * m33
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverse of a matrix. Multiplying by the inverse is used in matrix math
|
||||
* instead of division.
|
||||
*
|
||||
* Formula from:
|
||||
* http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
|
||||
*/
|
||||
public static double[] inverse(double[] matrix) {
|
||||
double det = determinant(matrix);
|
||||
if (isZero(det)) {
|
||||
return matrix;
|
||||
}
|
||||
double m00 = matrix[0], m01 = matrix[1], m02 = matrix[2], m03 = matrix[3], m10 = matrix[4],
|
||||
m11 = matrix[5], m12 = matrix[6], m13 = matrix[7], m20 = matrix[8], m21 = matrix[9],
|
||||
m22 = matrix[10], m23 = matrix[11], m30 = matrix[12], m31 = matrix[13], m32 = matrix[14],
|
||||
m33 = matrix[15];
|
||||
return new double[] {
|
||||
(m12 * m23 * m31 - m13 * m22 * m31 + m13 * m21 * m32 - m11 * m23 * m32 - m12 * m21 * m33 + m11 * m22 * m33) / det,
|
||||
(m03 * m22 * m31 - m02 * m23 * m31 - m03 * m21 * m32 + m01 * m23 * m32 + m02 * m21 * m33 - m01 * m22 * m33) / det,
|
||||
(m02 * m13 * m31 - m03 * m12 * m31 + m03 * m11 * m32 - m01 * m13 * m32 - m02 * m11 * m33 + m01 * m12 * m33) / det,
|
||||
(m03 * m12 * m21 - m02 * m13 * m21 - m03 * m11 * m22 + m01 * m13 * m22 + m02 * m11 * m23 - m01 * m12 * m23) / det,
|
||||
(m13 * m22 * m30 - m12 * m23 * m30 - m13 * m20 * m32 + m10 * m23 * m32 + m12 * m20 * m33 - m10 * m22 * m33) / det,
|
||||
(m02 * m23 * m30 - m03 * m22 * m30 + m03 * m20 * m32 - m00 * m23 * m32 - m02 * m20 * m33 + m00 * m22 * m33) / det,
|
||||
(m03 * m12 * m30 - m02 * m13 * m30 - m03 * m10 * m32 + m00 * m13 * m32 + m02 * m10 * m33 - m00 * m12 * m33) / det,
|
||||
(m02 * m13 * m20 - m03 * m12 * m20 + m03 * m10 * m22 - m00 * m13 * m22 - m02 * m10 * m23 + m00 * m12 * m23) / det,
|
||||
(m11 * m23 * m30 - m13 * m21 * m30 + m13 * m20 * m31 - m10 * m23 * m31 - m11 * m20 * m33 + m10 * m21 * m33) / det,
|
||||
(m03 * m21 * m30 - m01 * m23 * m30 - m03 * m20 * m31 + m00 * m23 * m31 + m01 * m20 * m33 - m00 * m21 * m33) / det,
|
||||
(m01 * m13 * m30 - m03 * m11 * m30 + m03 * m10 * m31 - m00 * m13 * m31 - m01 * m10 * m33 + m00 * m11 * m33) / det,
|
||||
(m03 * m11 * m20 - m01 * m13 * m20 - m03 * m10 * m21 + m00 * m13 * m21 + m01 * m10 * m23 - m00 * m11 * m23) / det,
|
||||
(m12 * m21 * m30 - m11 * m22 * m30 - m12 * m20 * m31 + m10 * m22 * m31 + m11 * m20 * m32 - m10 * m21 * m32) / det,
|
||||
(m01 * m22 * m30 - m02 * m21 * m30 + m02 * m20 * m31 - m00 * m22 * m31 - m01 * m20 * m32 + m00 * m21 * m32) / det,
|
||||
(m02 * m11 * m30 - m01 * m12 * m30 - m02 * m10 * m31 + m00 * m12 * m31 + m01 * m10 * m32 - m00 * m11 * m32) / det,
|
||||
(m01 * m12 * m20 - m02 * m11 * m20 + m02 * m10 * m21 - m00 * m12 * m21 - m01 * m10 * m22 + m00 * m11 * m22) / det
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns columns into rows and rows into columns.
|
||||
*/
|
||||
public static double[] transpose(double[] m) {
|
||||
return new double[] {
|
||||
m[0], m[4], m[8], m[12],
|
||||
m[1], m[5], m[9], m[13],
|
||||
m[2], m[6], m[10], m[14],
|
||||
m[3], m[7], m[11], m[15]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on: http://tog.acm.org/resources/GraphicsGems/gemsii/unmatrix.c
|
||||
*/
|
||||
public static void multiplyVectorByMatrix(double[] v, double[] m, double[] result) {
|
||||
double vx = v[0], vy = v[1], vz = v[2], vw = v[3];
|
||||
result[0] = vx * m[0] + vy * m[4] + vz * m[8] + vw * m[12];
|
||||
result[1] = vx * m[1] + vy * m[5] + vz * m[9] + vw * m[13];
|
||||
result[2] = vx * m[2] + vy * m[6] + vz * m[10] + vw * m[14];
|
||||
result[3] = vx * m[3] + vy * m[7] + vz * m[11] + vw * m[15];
|
||||
}
|
||||
|
||||
/**
|
||||
* From: https://code.google.com/p/webgl-mjs/source/browse/mjs.js
|
||||
*/
|
||||
public static double v3Length(double[] a) {
|
||||
return Math.sqrt(a[0]*a[0] + a[1]*a[1] + a[2]*a[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on: https://code.google.com/p/webgl-mjs/source/browse/mjs.js
|
||||
*/
|
||||
public static double[] v3Normalize(double[] vector, double norm) {
|
||||
double im = 1 / (isZero(norm) ? v3Length(vector) : norm);
|
||||
return new double[] {
|
||||
vector[0] * im,
|
||||
vector[1] * im,
|
||||
vector[2] * im
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The dot product of a and b, two 3-element vectors.
|
||||
* From: https://code.google.com/p/webgl-mjs/source/browse/mjs.js
|
||||
*/
|
||||
public static double v3Dot(double[] a, double[] b) {
|
||||
return a[0] * b[0] +
|
||||
a[1] * b[1] +
|
||||
a[2] * b[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* From:
|
||||
* http://www.opensource.apple.com/source/WebCore/WebCore-514/platform/graphics/transforms/TransformationMatrix.cpp
|
||||
*/
|
||||
public static double[] v3Combine(double[] a, double[] b, double aScale, double bScale) {
|
||||
return new double[]{
|
||||
aScale * a[0] + bScale * b[0],
|
||||
aScale * a[1] + bScale * b[1],
|
||||
aScale * a[2] + bScale * b[2]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* From:
|
||||
* http://www.opensource.apple.com/source/WebCore/WebCore-514/platform/graphics/transforms/TransformationMatrix.cpp
|
||||
*/
|
||||
public static double[] v3Cross(double[] a, double[] b) {
|
||||
return new double[]{
|
||||
a[1] * b[2] - a[2] * b[1],
|
||||
a[2] * b[0] - a[0] * b[2],
|
||||
a[0] * b[1] - a[1] * b[0]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on:
|
||||
* http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/
|
||||
* and:
|
||||
* http://quat.zachbennett.com/
|
||||
*
|
||||
* Note that this rounds degrees to the thousandth of a degree, due to
|
||||
* floating point errors in the creation of the quaternion.
|
||||
*
|
||||
* Also note that this expects the qw value to be last, not first.
|
||||
*
|
||||
* Also, when researching this, remember that:
|
||||
* yaw === heading === z-axis
|
||||
* pitch === elevation/attitude === y-axis
|
||||
* roll === bank === x-axis
|
||||
*/
|
||||
public static void quaternionToDegreesXYZ(double[] q, double[] result) {
|
||||
double qx = q[0], qy = q[1], qz = q[2], qw = q[3];
|
||||
double qw2 = qw * qw;
|
||||
double qx2 = qx * qx;
|
||||
double qy2 = qy * qy;
|
||||
double qz2 = qz * qz;
|
||||
double test = qx * qy + qz * qw;
|
||||
double unit = qw2 + qx2 + qy2 + qz2;
|
||||
double conv = 180 / Math.PI;
|
||||
|
||||
if (test > 0.49999 * unit) {
|
||||
result[0] = 0;
|
||||
result[1] = 2 * Math.atan2(qx, qw) * conv;
|
||||
result[2] = 90;
|
||||
return;
|
||||
}
|
||||
if (test < -0.49999 * unit) {
|
||||
result[0] = 0;
|
||||
result[1] = -2 * Math.atan2(qx, qw) * conv;
|
||||
result[2] = -90;
|
||||
return;
|
||||
}
|
||||
|
||||
result[0] = roundTo3Places(Math.atan2(2 * qx * qw - 2 * qy * qz, 1 - 2 * qx2 - 2 * qz2) * conv);
|
||||
result[1] = roundTo3Places(Math.atan2(2 * qy * qw - 2 * qx * qz, 1 - 2 * qy2 - 2 * qz2) * conv);
|
||||
result[2] = roundTo3Places(Math.asin(2 * qx * qy + 2 * qz * qw) * conv);
|
||||
}
|
||||
|
||||
public static double roundTo3Places(double n) {
|
||||
return Math.round(n * 1000d) * 0.001;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package com.facebook.react.uimanager;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Test for {@link MatrixMathHelper}
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class MatrixMathHelperTest {
|
||||
|
||||
private void verifyZRotatedMatrix(double degrees, double rotX, double rotY, double rotZ) {
|
||||
MatrixMathHelper.MatrixDecompositionContext ctx =
|
||||
new MatrixMathHelper.MatrixDecompositionContext();
|
||||
double[] matrix = createRotateZ(degreesToRadians(degrees));
|
||||
MatrixMathHelper.decomposeMatrix(matrix, ctx);
|
||||
assertThat(ctx.rotationDegrees).containsSequence(rotX, rotY, rotZ);
|
||||
}
|
||||
|
||||
private void verifyYRotatedMatrix(double degrees, double rotX, double rotY, double rotZ) {
|
||||
MatrixMathHelper.MatrixDecompositionContext ctx =
|
||||
new MatrixMathHelper.MatrixDecompositionContext();
|
||||
double[] matrix = createRotateY(degreesToRadians(degrees));
|
||||
MatrixMathHelper.decomposeMatrix(matrix, ctx);
|
||||
assertThat(ctx.rotationDegrees).containsSequence(rotX, rotY, rotZ);
|
||||
}
|
||||
|
||||
private void verifyXRotatedMatrix(double degrees, double rotX, double rotY, double rotZ) {
|
||||
MatrixMathHelper.MatrixDecompositionContext ctx =
|
||||
new MatrixMathHelper.MatrixDecompositionContext();
|
||||
double[] matrix = createRotateX(degreesToRadians(degrees));
|
||||
MatrixMathHelper.decomposeMatrix(matrix, ctx);
|
||||
assertThat(ctx.rotationDegrees).containsSequence(rotX, rotY, rotZ);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecomposing4x4MatrixToProduceAccurateZaxisAngles() {
|
||||
|
||||
MatrixMathHelper.MatrixDecompositionContext ctx =
|
||||
new MatrixMathHelper.MatrixDecompositionContext();
|
||||
|
||||
MatrixMathHelper.decomposeMatrix(
|
||||
new double[]{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
|
||||
ctx);
|
||||
|
||||
assertThat(ctx.rotationDegrees).containsSequence(0d, 0d, 0d);
|
||||
|
||||
double[] angles = new double[]{30, 45, 60, 75, 90, 100, 115, 120, 133, 167};
|
||||
for (double angle : angles) {
|
||||
verifyZRotatedMatrix(angle, 0d, 0d, angle);
|
||||
verifyZRotatedMatrix(-angle, 0d, 0d, -angle);
|
||||
}
|
||||
|
||||
verifyZRotatedMatrix(180d, 0d, 0d, 180d);
|
||||
|
||||
// all values are between 0 and 180;
|
||||
// change of sign and direction in the third and fourth quadrant
|
||||
verifyZRotatedMatrix(222, 0d, 0d, -138d);
|
||||
|
||||
verifyZRotatedMatrix(270, 0d, 0d, -90d);
|
||||
|
||||
// 360 is expressed as 0
|
||||
verifyZRotatedMatrix(360, 0d, 0d, 0d);
|
||||
|
||||
verifyZRotatedMatrix(33.33333333, 0d, 0d, 33.333d);
|
||||
|
||||
verifyZRotatedMatrix(86.75309, 0d, 0d, 86.753d);
|
||||
|
||||
verifyZRotatedMatrix(42.00000000001, 0d, 0d, 42d);
|
||||
|
||||
verifyZRotatedMatrix(42.99999999999, 0d, 0d, 43d);
|
||||
|
||||
verifyZRotatedMatrix(42.99999999999, 0d, 0d, 43d);
|
||||
|
||||
verifyZRotatedMatrix(42.49999999999, 0d, 0d, 42.5d);
|
||||
|
||||
verifyZRotatedMatrix(42.55555555555, 0d, 0d, 42.556d);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecomposing4x4MatrixToProduceAccurateYaxisAngles() {
|
||||
double[] angles = new double[]{30, 45, 60, 75, 90, 100, 110, 120, 133, 167};
|
||||
for (double angle : angles) {
|
||||
verifyYRotatedMatrix(angle, 0d, angle, 0d);
|
||||
verifyYRotatedMatrix(-angle, 0d, -angle, 0d);
|
||||
}
|
||||
|
||||
// all values are between 0 and 180;
|
||||
// change of sign and direction in the third and fourth quadrant
|
||||
verifyYRotatedMatrix(222, 0d, -138d, 0d);
|
||||
|
||||
verifyYRotatedMatrix(270, 0d, -90d, 0d);
|
||||
|
||||
verifyYRotatedMatrix(360, 0d, 0d, 0d);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecomposing4x4MatrixToProduceAccurateXaxisAngles() {
|
||||
double[] angles = new double[]{30, 45, 60, 75, 90, 100, 110, 120, 133, 167};
|
||||
for (double angle : angles) {
|
||||
verifyXRotatedMatrix(angle, angle, 0d, 0d);
|
||||
verifyXRotatedMatrix(-angle, -angle, 0d, 0d);
|
||||
}
|
||||
|
||||
// all values are between 0 and 180;
|
||||
// change of sign and direction in the third and fourth quadrant
|
||||
verifyXRotatedMatrix(222, -138d, 0d, 0d);
|
||||
|
||||
verifyXRotatedMatrix(270, -90d, 0d, 0d);
|
||||
|
||||
verifyXRotatedMatrix(360, 0d, 0d, 0d);
|
||||
}
|
||||
|
||||
private static double[] createIdentityMatrix() {
|
||||
return new double[] {
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
};
|
||||
}
|
||||
|
||||
private static double degreesToRadians(double degrees) {
|
||||
return degrees * Math.PI / 180;
|
||||
}
|
||||
|
||||
private static double[] createRotateZ(double radians) {
|
||||
double[] mat = createIdentityMatrix();
|
||||
mat[0] = Math.cos(radians);
|
||||
mat[1] = Math.sin(radians);
|
||||
mat[4] = -Math.sin(radians);
|
||||
mat[5] = Math.cos(radians);
|
||||
return mat;
|
||||
}
|
||||
|
||||
private static double[] createRotateY(double radians) {
|
||||
double[] mat = createIdentityMatrix();
|
||||
mat[0] = Math.cos(radians);
|
||||
mat[2] = -Math.sin(radians);
|
||||
mat[8] = Math.sin(radians);
|
||||
mat[10] = Math.cos(radians);
|
||||
return mat;
|
||||
}
|
||||
|
||||
private static double[] createRotateX(double radians) {
|
||||
double[] mat = createIdentityMatrix();
|
||||
mat[5] = Math.cos(radians);
|
||||
mat[6] = Math.sin(radians);
|
||||
mat[9] = -Math.sin(radians);
|
||||
mat[10] = Math.cos(radians);
|
||||
return mat;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user