2
* Copyright (c) 2022-2023 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
7
* http://www.apache.org/licenses/LICENSE-2.0
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.
16
import { Array_from_number, float32 } from "@koalaui/compat"
17
import { Matrix33 } from "./Matrix33"
18
import { Point3 } from "./Point3"
21
export interface RotateOptions {
31
export interface ScaleOptions {
40
export interface TranslateOptions {
46
// TODO: this is because ArkTS doesn allow interface literal instances.
47
class TranslateOptionsImpl implements TranslateOptions {
48
_x: float32 | undefined
49
_y: float32 | undefined
50
_z: float32 | undefined
52
get x(): float32 | undefined { return this._x }
53
get y(): float32 | undefined { return this._y }
54
get z(): float32 | undefined { return this._z }
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 }
61
x: float32 | undefined,
62
y: float32 | undefined,
63
z: float32 | undefined
71
export function mat44(array?: Float32Array): Matrix44 {
72
return (array == undefined)? new Matrix44() : new Matrix44(array)
75
* 4x4 matrix with right-handed coordinate system:
76
* +x goes to the right
78
* +z goes into the screen (away from the viewer)
80
export class Matrix44 {
81
public readonly array: Float32Array
82
constructor (array: Float32Array = new Float32Array(Array_from_number([
88
this.array = array.slice()
91
public static identity(): Matrix44 {
95
static zero(): Matrix44 {
96
return mat44(new Float32Array(Array_from_number([
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,
117
public static perspective(depth: float32): Matrix44 {
118
return new Matrix44(new Float32Array(Array_from_number([
122
0.0, 0.0, -1.0 / depth, 1.0,
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([
133
0.0, 0.0, (far + near) * denomInv, 2 * far * near * denomInv,
139
* Returns new matrix, made from Matrix33.
141
* @param matrix - 3x3 matrix
142
* @returns the new instance of Matrix44
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],
150
matrix.array[6], matrix.array[7], 0.0, matrix.array[8]
155
* Returns new 3x3 matrix, made from this matrix by dropping the third row and the third column.
157
* @returns the new instance of Matrix33
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]
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]
177
concat(matrix: Matrix44): Matrix44 {
178
const result: Float32Array = new Float32Array(Array_from_number([
184
for (let row = 0; row < 4; row++) {
185
for (let col = 0; col < 4; col++) {
187
for (let k = 0; k < 4; k++) {
188
num += this.array[row * 4 + k] * matrix.array[col + 4 * k]
190
result[row * 4 + col] = num
193
for (let i = 0; i < this.array.length; i++) {
194
this.array[i] = result[i]
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
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),
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),
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),
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
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
256
this.concat(translationToPivot).concat(rotation).concat(translationToBack)
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
268
public invert(): Matrix44 {
269
const result: Float32Array = new Float32Array(16)
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]
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
301
let determinant = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06
302
let invdet = 1.0 / determinant
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
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.
336
for (let i = 0; i < result.length; ++i) {
339
// At this point, prod will either be NaN or 0
340
// if prod is NaN, this check will return false
342
for (let i = 0; i < this.array.length; i++) {
343
this.array[i] = result[i]
349
public transpose(): Matrix44 {
350
const result: Float32Array = new Float32Array(16)
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]
369
for (let i = 0; i < this.array.length; i++) {
370
this.array[i] = result[i]
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