1 /* 2 * Copyright (C) 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 com.google.android.torus.math 18 19 import android.opengl.Matrix 20 import java.util.* 21 22 /** An immutable 3D transformation in homogeneous coordinates. */ 23 open class AffineTransform @JvmOverloads constructor( 24 /** The position of the transform. */ 25 val position: Vector3 = Vector3(0f, 0f, 0f), 26 /** The rotation of the transform. */ 27 val rotation: RotationQuaternion = RotationQuaternion(), 28 /** The scale of the transform. */ 29 val scale: Vector3 = Vector3(1f, 1f, 1f) 30 ) : MatrixTransform { 31 constructor(transform: AffineTransform) : this( 32 transform.position, 33 transform.rotation, 34 transform.scale 35 ) 36 37 /** 38 * Creates a new [AffineTransform] with ([x], [y], [z]) as the new translation. 39 * 40 * @param x The X component of the translation. 41 * @param y The Y component of the translation. 42 * @param z The Z component of the translation. 43 * 44 * @return The new [AffineTransform] with the new translation. 45 */ withTranslationnull46 fun withTranslation(x: Float, y: Float, z: Float): AffineTransform { 47 return AffineTransform(Vector3(x, y, z), rotation, scale) 48 } 49 50 /** 51 * Creates a new [AffineTransform] with ([x], [y], [z]) as the new scale. 52 * 53 * @param x The scale in the X direction. 54 * @param y The scale in the Y direction. 55 * @param z The scale in the Z direction. 56 * 57 * @return The new [AffineTransform] with the new scale. 58 */ withScalenull59 fun withScale(x: Float, y: Float, z: Float): AffineTransform { 60 return AffineTransform(position, rotation, Vector3(x, y, z)) 61 } 62 63 /** 64 * Creates a new [AffineTransform] with ([scale], [scale], [scale]) as the new scale. 65 * 66 * @param scale The scale applied in the X,Y and Z directions. 67 * 68 * @return The new [AffineTransform] with the new scale. 69 */ withScalenull70 fun withScale(scale: Float): AffineTransform { 71 return withScale(scale, scale, scale) 72 } 73 74 /** 75 * Creates a new [AffineTransform] with [rotation] as the new rotation. 76 * 77 * @param rotation The new rotation. 78 * 79 * @return The new [AffineTransform] with the new rotation. 80 */ withRotationnull81 fun withRotation(rotation: RotationQuaternion): AffineTransform { 82 return AffineTransform(position, RotationQuaternion(rotation), scale) 83 } 84 85 /** 86 * Returns a new transform with a new rotation using Euler rotation angles (ZYX sequence). 87 * 88 * @param x The Euler rotation angle around X axis, in degrees. 89 * @param y The Euler rotation angle around Y axis, in degrees. 90 * @param z The Euler rotation angle around Z axis, in degrees. 91 */ withEulerRotationnull92 fun withEulerRotation(x: Float, y: Float, z: Float): AffineTransform { 93 return AffineTransform(position, RotationQuaternion.fromEuler(x, y, z), scale) 94 } 95 translateBynull96 fun translateBy(x: Float, y: Float, z: Float): AffineTransform { 97 return AffineTransform(position + Vector3(x, y, z), rotation, scale) 98 } 99 scaleBynull100 fun scaleBy(scale: Float): AffineTransform { 101 return AffineTransform(position, rotation, this.scale + Vector3(scale)) 102 } 103 scaleBynull104 fun scaleBy(x: Float, y: Float, z: Float): AffineTransform { 105 return AffineTransform(position, rotation, this.scale + Vector3(x, y, z)) 106 } 107 rotateBynull108 fun rotateBy(quaternion: RotationQuaternion): AffineTransform { 109 return AffineTransform(position, quaternion * rotation, scale) 110 } 111 112 /** 113 * Rotates the current rotation using some Euler rotation angles (ZYX sequence). 114 * 115 * @param x The Euler rotation angle around X axis, in degrees. 116 * @param y The Euler rotation angle around Y axis, in degrees. 117 * @param z The Euler rotation angle around Z axis, in degrees. 118 */ rotateByEulernull119 fun rotateByEuler(x: Float, y: Float, z: Float): AffineTransform { 120 return rotateBy(RotationQuaternion.fromEuler(x, y, z)) 121 } 122 123 /** 124 * Returns a 4x4 transform Matrix. The format of the matrix follows the OpenGL ES matrix format 125 * stored in float arrays. 126 * Matrices are 4 x 4 column-vector matrices stored in column-major order: 127 * 128 * <pre> 129 * m[0] m[4] m[8] m[12] 130 * m[1] m[5] m[9] m[13] 131 * m[2] m[6] m[10] m[14] 132 * m[3] m[7] m[11] m[15] 133 * </pre> 134 * 135 * @return a 16 value [FloatArray] representing the transform as a 4x4 matrix. 136 */ toMatrixnull137 override fun toMatrix(): FloatArray { 138 val transformMatrix = FloatArray(16) 139 Matrix.setIdentityM(transformMatrix, 0) 140 // The order of operations matter; we should follow the usual: Scale, Rotate and Translate. 141 Matrix.scaleM(transformMatrix, 0, scale.x, scale.y, scale.z) 142 Matrix.rotateM( 143 transformMatrix, 144 0, 145 rotation.angle.toFloat(), 146 rotation.direction.x, 147 rotation.direction.y, 148 rotation.direction.z 149 ) 150 Matrix.translateM(transformMatrix, 0, position.x, position.y, position.z) 151 return transformMatrix 152 } 153 equalsnull154 override fun equals(other: Any?): Boolean { 155 if (this === other) return true 156 if (javaClass != other?.javaClass) return false 157 158 other as AffineTransform 159 160 if (position != other.position) return false 161 if (rotation != other.rotation) return false 162 if (scale != other.scale) return false 163 164 return true 165 } 166 hashCodenull167 override fun hashCode(): Int { 168 var result = position.hashCode() 169 result = 31 * result + rotation.hashCode() 170 result = 31 * result + scale.hashCode() 171 return result 172 } 173 toStringnull174 override fun toString(): String { 175 return "Position: ${position}\nRotation: ${rotation}\nScale: $scale\n" 176 } 177 }