1/* 2 * Copyright (c) 2022-2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { Array_from_number, float32 } from "@koalaui/compat" 17import { Matrix33 } from "./Matrix33" 18import { Point3 } from "./Point3" 19 20 21export interface RotateOptions { 22 angle?: float32 23 x?: float32 24 y?: float32 25 z?: float32 26 pivotX?: float32 27 pivotY?: float32 28 pivotZ?: float32 29} 30 31export interface ScaleOptions { 32 x?: float32 33 y?: float32 34 z?: float32 35 pivotX?: float32 36 pivotY?: float32 37 pivotZ?: float32 38} 39 40export interface TranslateOptions { 41 x?: float32 42 y?: float32 43 z?: float32 44} 45 46// TODO: this is because ArkTS doesn allow interface literal instances. 47class TranslateOptionsImpl implements TranslateOptions { 48 _x: float32 | undefined 49 _y: float32 | undefined 50 _z: float32 | undefined 51 52 get x(): float32 | undefined { return this._x } 53 get y(): float32 | undefined { return this._y } 54 get z(): float32 | undefined { return this._z } 55 56 set x(x: float32 | undefined) { this._x = x } 57 set y(y: float32 | undefined) { this._y = y } 58 set z(z: float32 | undefined) { this._z = z } 59 60 constructor( 61 x: float32 | undefined, 62 y: float32 | undefined, 63 z: float32 | undefined 64 ) { 65 this._x = x 66 this._y = y 67 this._z = z 68 } 69} 70 71export function mat44(array?: Float32Array): Matrix44 { 72 return (array == undefined)? new Matrix44() : new Matrix44(array) 73} 74/** 75 * 4x4 matrix with right-handed coordinate system: 76 * +x goes to the right 77 * +y goes down 78 * +z goes into the screen (away from the viewer) 79 */ 80export class Matrix44 { 81 public readonly array: Float32Array 82 constructor (array: Float32Array = new Float32Array(Array_from_number([ 83 1.0, 0.0, 0.0, 0.0, 84 0.0, 1.0, 0.0, 0.0, 85 0.0, 0.0, 1.0, 0.0, 86 0.0, 0.0, 0.0, 1.0 87 ]))) { 88 this.array = array.slice() 89 } 90 91 public static identity(): Matrix44 { 92 return mat44() 93 } 94 95 static zero(): Matrix44 { 96 return mat44(new Float32Array(Array_from_number([ 97 0.0, 0.0, 0.0, 0.0, 98 0.0, 0.0, 0.0, 0.0, 99 0.0, 0.0, 0.0, 0.0, 100 0.0, 0.0, 0.0, 0.0, 101 ]))) 102 } 103 104 public static lookAt(eye: Point3, center: Point3, up: Point3): Matrix44 { 105 const f = center.subtract(eye).normalize() 106 const u = up.normalize() 107 const s = f.cross(u).normalize() 108 const sf = s.cross(f) 109 return new Matrix44(new Float32Array(Array_from_number([ 110 s.x, sf.x, -f.x, eye.x, 111 s.y, sf.y, -f.y, eye.y, 112 s.z, sf.z, -f.z, eye.z, 113 0, 0, 0, 1, 114 ]))).invert() 115 } 116 117 public static perspective(depth: float32): Matrix44 { 118 return new Matrix44(new Float32Array(Array_from_number([ 119 1.0, 0.0, 0.0, 0.0, 120 0.0, 1.0, 0.0, 0.0, 121 0.0, 0.0, 1.0, 0.0, 122 0.0, 0.0, -1.0 / depth, 1.0, 123 ]))) 124 } 125 126 public static perspectiveFov(fov: float32, near: float32, far: float32): Matrix44 { 127 const denomInv = (far - near) 128 const halfAngle = fov * 0.5; 129 const cot = Math.cos(halfAngle) / Math.sin(halfAngle) 130 return new Matrix44(new Float32Array(Array_from_number([ 131 cot, 0.0, 0.0, 0.0, 132 0.0, cot, 0.0, 0.0, 133 0.0, 0.0, (far + near) * denomInv, 2 * far * near * denomInv, 134 0.0, 0.0, -1.0, 0.0, 135 ]))) 136 } 137 138 /** 139 * Returns new matrix, made from Matrix33. 140 * 141 * @param matrix - 3x3 matrix 142 * @returns the new instance of Matrix44 143 * 144 */ 145 public static makeFromMatrix33(matrix: Matrix33): Matrix44{ 146 return new Matrix44(new Float32Array(Array_from_number([ 147 matrix.array[0], matrix.array[1], 0.0, matrix.array[2], 148 matrix.array[3], matrix.array[4], 0.0, matrix.array[5], 149 0.0, 0.0, 1.0, 0.0, 150 matrix.array[6], matrix.array[7], 0.0, matrix.array[8] 151 ]))) 152 } 153 154 /** 155 * Returns new 3x3 matrix, made from this matrix by dropping the third row and the third column. 156 * 157 * @returns the new instance of Matrix33 158 * 159 */ 160 public asMatrix33(): Matrix33{ 161 return new Matrix33(new Float32Array(Array_from_number([ 162 this.array[0], this.array[1], this.array[3], 163 this.array[4], this.array[5], this.array[7], 164 this.array[12], this.array[13], this.array[15] 165 ]))) 166 } 167 168 public copy(): Matrix44 { 169 return new Matrix44(new Float32Array(Array_from_number([ 170 this.array[0], this.array[1], this.array[2], this.array[3], 171 this.array[4], this.array[5], this.array[6], this.array[7], 172 this.array[8], this.array[9], this.array[10], this.array[11], 173 this.array[12], this.array[13], this.array[14], this.array[15] 174 ]))) 175 } 176 177 concat(matrix: Matrix44): Matrix44 { 178 const result: Float32Array = new Float32Array(Array_from_number([ 179 1.0, 0.0, 0.0, 0.0, 180 0.0, 1.0, 0.0, 0.0, 181 0.0, 0.0, 1.0, 0.0, 182 0.0, 0.0, 0.0, 1.0, 183 ])) 184 for (let row = 0; row < 4; row++) { 185 for (let col = 0; col < 4; col++) { 186 let num: float32 = 0 187 for (let k = 0; k < 4; k++) { 188 num += this.array[row * 4 + k] * matrix.array[col + 4 * k] 189 } 190 result[row * 4 + col] = num 191 } 192 } 193 for (let i = 0; i < this.array.length; i++) { 194 this.array[i] = result[i] 195 } 196 return this 197 } 198 199 public scale(options: ScaleOptions): Matrix44 { 200 const scaled = new Matrix44() 201 scaled.array[0] = options.x ?? 1.0 as float32 202 scaled.array[5] = options.y ?? 1.0 as float32 203 scaled.array[10] = options.z ?? 1.0 as float32 204 205 this.translate(new TranslateOptionsImpl( 206 -(options.pivotX ?? 0.0 as float32) * (options.x ?? 1.0 as float32) + (options.pivotX ?? 0.0 as float32), 207 -(options.pivotY ?? 0.0 as float32) * (options.y ?? 1.0 as float32) + (options.pivotY ?? 0.0 as float32), 208 undefined 209 )).concat(scaled) 210 211 return this 212 } 213 214 public rotate(options: RotateOptions): Matrix44 { 215 const translationToPivot = mat44().translate(new TranslateOptionsImpl( 216 (options.pivotX ?? 0.0 as float32), 217 (options.pivotY ?? 0.0 as float32), 218 (options.pivotZ ?? 0.0 as float32), 219 )) 220 const translationToBack = mat44().translate(new TranslateOptionsImpl( 221 -(options.pivotX ?? 0.0 as float32), 222 -(options.pivotY ?? 0.0 as float32), 223 -(options.pivotZ ?? 0.0 as float32), 224 )) 225 226 const vec = new Point3(options.x ?? 0.0 as float32, options.y ?? 0.0 as float32, options.z ?? 0.0 as float32).normalize() 227 const rads = (options.angle ?? 0.0 as float32) * Math.PI / 180 228 let c = Math.cos(rads) 229 let s = Math.sin(rads) 230 const tolerance = (1.0 / (1 << 12)) 231 if (Math.abs(s) <= tolerance) s = 0.0 232 if (Math.abs(c) <= tolerance) c = 0.0 233 let t = 1 - c 234 const x = vec.x 235 const y = vec.y 236 const z = vec.z 237 238 const rotation = mat44() 239 rotation.array[0] = t * x * x + c 240 rotation.array[1] = t * x * y - s * z 241 rotation.array[2] = t * x * z + s * y 242 rotation.array[3] = 0 243 rotation.array[4] = t * x * y + s * z 244 rotation.array[5] = t * y * y + c 245 rotation.array[6] = t * y * z - s * x 246 rotation.array[7] = 0 247 rotation.array[8] = t * x * z - s * y 248 rotation.array[9] = t * y * z + s * x 249 rotation.array[10] = t * z * z + c 250 rotation.array[11] = 0 251 rotation.array[12] = 0 252 rotation.array[13] = 0 253 rotation.array[14] = 0 254 rotation.array[15] = 1 255 256 this.concat(translationToPivot).concat(rotation).concat(translationToBack) 257 258 return this 259 } 260 261 public translate(options: TranslateOptions): Matrix44 { 262 this.array[3] = options.x ?? 0.0 as float32 263 this.array[7] = options.y ?? 0.0 as float32 264 this.array[11] = options.z ?? 0.0 as float32 265 return this 266 } 267 268 public invert(): Matrix44 { 269 const result: Float32Array = new Float32Array(16) 270 271 let a00 = this.array[0] 272 let a01 = this.array[1] 273 let a02 = this.array[2] 274 let a03 = this.array[3] 275 let a10 = this.array[4] 276 let a11 = this.array[5] 277 let a12 = this.array[6] 278 let a13 = this.array[7] 279 let a20 = this.array[8] 280 let a21 = this.array[9] 281 let a22 = this.array[10] 282 let a23 = this.array[11] 283 let a30 = this.array[12] 284 let a31 = this.array[13] 285 let a32 = this.array[14] 286 let a33 = this.array[15] 287 288 let b00 = a00 * a11 - a01 * a10 289 let b01 = a00 * a12 - a02 * a10 290 let b02 = a00 * a13 - a03 * a10 291 let b03 = a01 * a12 - a02 * a11 292 let b04 = a01 * a13 - a03 * a11 293 let b05 = a02 * a13 - a03 * a12 294 let b06 = a20 * a31 - a21 * a30 295 let b07 = a20 * a32 - a22 * a30 296 let b08 = a20 * a33 - a23 * a30 297 let b09 = a21 * a32 - a22 * a31 298 let b10 = a21 * a33 - a23 * a31 299 let b11 = a22 * a33 - a23 * a32 300 301 let determinant = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06 302 let invdet = 1.0 / determinant 303 b00 *= invdet 304 b01 *= invdet 305 b02 *= invdet 306 b03 *= invdet 307 b04 *= invdet 308 b05 *= invdet 309 b06 *= invdet 310 b07 *= invdet 311 b08 *= invdet 312 b09 *= invdet 313 b10 *= invdet 314 b11 *= invdet 315 316 result[0] = a11 * b11 - a12 * b10 + a13 * b09 317 result[1] = a02 * b10 - a01 * b11 - a03 * b09 318 result[2] = a31 * b05 - a32 * b04 + a33 * b03 319 result[3] = a22 * b04 - a21 * b05 - a23 * b03 320 result[4] = a12 * b08 - a10 * b11 - a13 * b07 321 result[5] = a00 * b11 - a02 * b08 + a03 * b07 322 result[6] = a32 * b02 - a30 * b05 - a33 * b01 323 result[7] = a20 * b05 - a22 * b02 + a23 * b01 324 result[8] = a10 * b10 - a11 * b08 + a13 * b06 325 result[9] = a01 * b08 - a00 * b10 - a03 * b06 326 result[10] = a30 * b04 - a31 * b02 + a33 * b00 327 result[11] = a21 * b02 - a20 * b04 - a23 * b00 328 result[12] = a11 * b07 - a10 * b09 - a12 * b06 329 result[13] = a00 * b09 - a01 * b07 + a02 * b06 330 result[14] = a31 * b01 - a30 * b03 - a32 * b00 331 result[15] = a20 * b03 - a21 * b01 + a22 * b00 332 333 // If 1/det overflows to infinity (i.e. det is denormalized) or any of the inverted matrix 334 // values is non-finite, return zero to indicate a non-invertible matrix. 335 let prod = 0 336 for (let i = 0; i < result.length; ++i) { 337 prod *= result[i] 338 } 339 // At this point, prod will either be NaN or 0 340 // if prod is NaN, this check will return false 341 if (prod == 0) { 342 for (let i = 0; i < this.array.length; i++) { 343 this.array[i] = result[i] 344 } 345 } 346 return this 347 } 348 349 public transpose(): Matrix44 { 350 const result: Float32Array = new Float32Array(16) 351 352 result[0] = this.array[0] 353 result[1] = this.array[4] 354 result[2] = this.array[8] 355 result[3] = this.array[12] 356 result[4] = this.array[1] 357 result[5] = this.array[5] 358 result[6] = this.array[9] 359 result[7] = this.array[13] 360 result[8] = this.array[2] 361 result[9] = this.array[6] 362 result[10] = this.array[10] 363 result[11] = this.array[14] 364 result[12] = this.array[3] 365 result[13] = this.array[7] 366 result[14] = this.array[11] 367 result[15] = this.array[15] 368 369 for (let i = 0; i < this.array.length; i++) { 370 this.array[i] = result[i] 371 } 372 373 return this 374 } 375 376 public skew(x?: float32, y?: float32): Matrix44 { 377 this.array[1] += x ?? 0.0 as float32 378 this.array[4] += y ?? 0.0 as float32 379 return this 380 } 381} 382