• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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