/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ package com.facebook.react.uimanager import com.facebook.infer.annotation.Assertions import kotlin.math.abs import kotlin.math.atan2 import kotlin.math.cos import kotlin.math.sin import kotlin.math.sqrt import kotlin.math.tan /** * Provides helper methods for converting transform operations into a matrix and then into a list of * translate, scale and rotate commands. */ public object MatrixMathHelper { private const val EPSILON = .00001 private fun isZero(d: Double): Boolean { return if (java.lang.Double.isNaN(d)) { false } else abs(d) < EPSILON } @JvmStatic public fun multiplyInto(out: DoubleArray, a: DoubleArray, b: DoubleArray) { val a00 = a[0] val a01 = a[1] val a02 = a[2] val a03 = a[3] val a10 = a[4] val a11 = a[5] val a12 = a[6] val a13 = a[7] val a20 = a[8] val a21 = a[9] val a22 = a[10] val a23 = a[11] val a30 = a[12] val a31 = a[13] val a32 = a[14] val a33 = a[15] var b0 = b[0] var b1 = b[1] var b2 = b[2] var b3 = b[3] out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30 out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31 out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32 out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33 b0 = b[4] b1 = b[5] b2 = b[6] b3 = b[7] out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30 out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31 out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32 out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33 b0 = b[8] b1 = b[9] b2 = b[10] b3 = b[11] out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30 out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31 out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32 out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33 b0 = b[12] b1 = b[13] b2 = b[14] b3 = b[15] out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30 out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31 out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32 out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33 } /** @param transformMatrix 16-element array of numbers representing 4x4 transform matrix */ @JvmStatic public fun decomposeMatrix(transformMatrix: DoubleArray, ctx: MatrixDecompositionContext) { Assertions.assertCondition(transformMatrix.size == 16) // output values val perspective = ctx.perspective val scale = ctx.scale val skew = ctx.skew val translation = ctx.translation val rotationDegrees = ctx.rotationDegrees // create normalized, 2d array matrix // and normalized 1d array perspectiveMatrix with redefined 4th column if (isZero(transformMatrix[15])) { return } val matrix = Array(4) { DoubleArray(4) } val perspectiveMatrix = DoubleArray(16) for (i in 0..3) { for (j in 0..3) { val value = transformMatrix[i * 4 + j] / transformMatrix[15] matrix[i][j] = value perspectiveMatrix[i * 4 + j] = if (j == 3) 0.0 else value } } perspectiveMatrix[15] = 1.0 // 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. val rightHandSide = doubleArrayOf(matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3]) // Solve the equation by inverting perspectiveMatrix and multiplying // rightHandSide by the inverse. val inversePerspectiveMatrix = inverse(perspectiveMatrix) val transposedInversePerspectiveMatrix = transpose(inversePerspectiveMatrix) multiplyVectorByMatrix(rightHandSide, transposedInversePerspectiveMatrix, perspective) } else { // no perspective perspective[2] = 0.0 perspective[1] = perspective[2] perspective[0] = perspective[1] perspective[3] = 1.0 } // translation is simple for (i in 0..2) { translation[i] = matrix[3][i] } // Now get scale and shear. // 'row' is a 3 element array of 3 component vectors val row = Array(3) { DoubleArray(3) } for (i in 0..2) { 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]) // 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. val pdum3 = v3Cross(row[1], row[2]) if (v3Dot(row[0], pdum3) < 0) { for (i in 0..2) { scale[i] *= -1.0 row[i][0] *= -1.0 row[i][1] *= -1.0 row[i][2] *= -1.0 } } // Now, get the rotations out // Based on: http://nghiaho.com/?page_id=846 val conv = 180 / Math.PI rotationDegrees[0] = roundTo3Places(-atan2(row[2][1], row[2][2]) * conv) rotationDegrees[1] = roundTo3Places( -atan2(-row[2][0], sqrt(row[2][1] * row[2][1] + row[2][2] * row[2][2])) * conv ) rotationDegrees[2] = roundTo3Places(-atan2(row[1][0], row[0][0]) * conv) } @JvmStatic public fun determinant(matrix: DoubleArray): Double { val m00 = matrix[0] val m01 = matrix[1] val m02 = matrix[2] val m03 = matrix[3] val m10 = matrix[4] val m11 = matrix[5] val m12 = matrix[6] val m13 = matrix[7] val m20 = matrix[8] val m21 = matrix[9] val m22 = matrix[10] val m23 = matrix[11] val m30 = matrix[12] val m31 = matrix[13] val m32 = matrix[14] val 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 */ @JvmStatic public fun inverse(matrix: DoubleArray): DoubleArray { val det = determinant(matrix) if (isZero(det)) { return matrix } val m00 = matrix[0] val m01 = matrix[1] val m02 = matrix[2] val m03 = matrix[3] val m10 = matrix[4] val m11 = matrix[5] val m12 = matrix[6] val m13 = matrix[7] val m20 = matrix[8] val m21 = matrix[9] val m22 = matrix[10] val m23 = matrix[11] val m30 = matrix[12] val m31 = matrix[13] val m32 = matrix[14] val m33 = matrix[15] return doubleArrayOf( ((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. */ @JvmStatic public fun transpose(m: DoubleArray): DoubleArray { return doubleArrayOf( 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 */ @JvmStatic public fun multiplyVectorByMatrix(v: DoubleArray, m: DoubleArray, result: DoubleArray) { val vx = v[0] val vy = v[1] val vz = v[2] val 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 */ @JvmStatic public fun v3Length(a: DoubleArray): Double { return 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 */ @JvmStatic public fun v3Normalize(vector: DoubleArray, norm: Double): DoubleArray { val im = 1 / if (isZero(norm)) v3Length(vector) else norm return doubleArrayOf(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 */ @JvmStatic public fun v3Dot(a: DoubleArray, b: DoubleArray): Double { 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 */ @JvmStatic public fun v3Combine( a: DoubleArray, b: DoubleArray, aScale: Double, bScale: Double, ): DoubleArray { return doubleArrayOf( 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 */ @JvmStatic public fun v3Cross(a: DoubleArray, b: DoubleArray): DoubleArray { return doubleArrayOf( a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], ) } @JvmStatic public fun roundTo3Places(n: Double): Double { return Math.round(n * 1000.0) * 0.001 } @JvmStatic public fun createIdentityMatrix(): DoubleArray { val res = DoubleArray(16) resetIdentityMatrix(res) return res } @JvmStatic public fun degreesToRadians(degrees: Double): Double { return degrees * Math.PI / 180 } @JvmStatic public fun resetIdentityMatrix(matrix: DoubleArray) { matrix[14] = 0.0 matrix[13] = matrix[14] matrix[12] = matrix[13] matrix[11] = matrix[12] matrix[9] = matrix[11] matrix[8] = matrix[9] matrix[7] = matrix[8] matrix[6] = matrix[7] matrix[4] = matrix[6] matrix[3] = matrix[4] matrix[2] = matrix[3] matrix[1] = matrix[2] matrix[15] = 1.0 matrix[10] = matrix[15] matrix[5] = matrix[10] matrix[0] = matrix[5] } @JvmStatic public fun applyPerspective(m: DoubleArray, perspective: Double) { m[11] = -1 / perspective } @JvmStatic public fun applyScaleX(m: DoubleArray, factor: Double) { m[0] = factor } @JvmStatic public fun applyScaleY(m: DoubleArray, factor: Double) { m[5] = factor } public fun applyScaleZ(m: DoubleArray, factor: Double) { m[10] = factor } @JvmStatic public fun applyTranslate2D(m: DoubleArray, x: Double, y: Double) { m[12] = x m[13] = y } @JvmStatic public fun applyTranslate3D(m: DoubleArray, x: Double, y: Double, z: Double) { m[12] = x m[13] = y m[14] = z } @JvmStatic public fun applySkewX(m: DoubleArray, radians: Double) { m[4] = tan(radians) } @JvmStatic public fun applySkewY(m: DoubleArray, radians: Double) { m[1] = tan(radians) } @JvmStatic public fun applyRotateX(m: DoubleArray, radians: Double) { m[5] = cos(radians) m[6] = sin(radians) m[9] = -sin(radians) m[10] = cos(radians) } @JvmStatic public fun applyRotateY(m: DoubleArray, radians: Double) { m[0] = cos(radians) m[2] = -sin(radians) m[8] = sin(radians) m[10] = cos(radians) } // http://www.w3.org/TR/css3-transforms/#recomposing-to-a-2d-matrix @JvmStatic public fun applyRotateZ(m: DoubleArray, radians: Double) { m[0] = cos(radians) m[1] = sin(radians) m[4] = -sin(radians) m[5] = cos(radians) } public open class MatrixDecompositionContext { @JvmField public var perspective: DoubleArray = DoubleArray(4) @JvmField public var scale: DoubleArray = DoubleArray(3) @JvmField public var skew: DoubleArray = DoubleArray(3) @JvmField public var translation: DoubleArray = DoubleArray(3) @JvmField public var rotationDegrees: DoubleArray = DoubleArray(3) public fun reset() { resetArray(perspective) resetArray(scale) resetArray(skew) resetArray(translation) resetArray(rotationDegrees) } private companion object { private fun resetArray(arr: DoubleArray) { for (i in arr.indices) { arr[i] = 0.0 } } } } }