| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399 |
- /**
- * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
- * SPDX-License-Identifier: MIT
- */
- // nolint: cyclo_complexity,method_line
- import { describe, test, expect, it } from 'vitest';
- import { Transform } from './Transform';
- import { Matrix as M, Matrix } from './Matrix';
- import { PI } from './const';
- describe('Matrix', () => {
- test('Matrix', async () => {
- expect(new M()).toEqual(M.IDENTITY);
- expect(new M()).toEqual(M.TEMP_MATRIX);
- });
- test('fromArray', async () => {
- expect(new M().fromArray([])).toEqual(M.IDENTITY);
- expect(new M().fromArray([1, 2, 3])).toEqual(M.IDENTITY);
- expect(new M().fromArray([0, 1, 2, 3, 4, 5])).toEqual(new M(0, 1, 3, 4, 2, 5));
- expect(new M().fromArray([0, 1, 2, 3, 4, 5, 6])).toEqual(new M(0, 1, 3, 4, 2, 5));
- });
- test('set', async () => {
- expect(new M().set(0, 1, 2, 3, 4, 5)).toEqual(new M(0, 1, 2, 3, 4, 5));
- });
- test('toArray', async () => {
- expect(new M(0, 1, 2, 3, 4, 5).toArray(true)).toEqual(
- Float32Array.from([0, 1, 0, 2, 3, 0, 4, 5, 1]),
- );
- expect(new M(0, 1, 2, 3, 4, 5).toArray(false)).toEqual(
- Float32Array.from([0, 2, 4, 1, 3, 5, 0, 0, 1]),
- );
- const arr = new Float32Array(9);
- new M(0, 1, 2, 3, 4, 5).toArray(false, arr);
- expect(arr).toEqual(Float32Array.from([0, 2, 4, 1, 3, 5, 0, 0, 1]));
- });
- test('apply', async () => {
- expect(M.IDENTITY.apply({ x: 1, y: 2 })).toEqual({ x: 1, y: 2 });
- // translate only
- expect(new M(1, 0, 0, 1, 1, 1).apply({ x: 1, y: 2 })).toEqual({
- x: 2,
- y: 3,
- });
- // scale only
- expect(new M(2, 0, 0, 2).apply({ x: 1, y: 2 })).toEqual({ x: 2, y: 4 });
- // skew only
- expect(new M(1, 1, 1, 1).apply({ x: 1, y: 2 })).toEqual({ x: 3, y: 3 });
- expect(new M(1, 1, -1, 1).apply({ x: 1, y: 2 })).toEqual({ x: -1, y: 3 });
- });
- test('applyInverse', async () => {
- expect(M.IDENTITY.applyInverse({ x: 1, y: 2 })).toEqual({ x: 1, y: 2 });
- // translate only
- expect(new M(1, 0, 0, 1, 1, 1).applyInverse({ x: 1, y: 2 })).toEqual({
- x: 0,
- y: 1,
- });
- // scale only
- expect(new M(2, 0, 0, 2).applyInverse({ x: 1, y: 2 })).toEqual({
- x: 0.5,
- y: 1,
- });
- // skew only
- expect(new M(1, 1, -1, 1).applyInverse({ x: 1, y: 2 })).toEqual({
- x: 1.5,
- y: 0.5,
- });
- });
- test('translate', async () => {
- expect(M.IDENTITY.translate(1, -2).apply({ x: 0, y: 0 })).toEqual({
- x: 1,
- y: -2,
- });
- });
- test('scale', async () => {
- expect(M.IDENTITY.scale(1, -2).apply({ x: 1, y: 2 })).toEqual({
- x: 1,
- y: -4,
- });
- expect(M.IDENTITY.scale(0, 0).apply({ x: 1, y: 2 })).toEqual({
- x: 0,
- y: 0,
- });
- });
- test('rotate', async () => {
- const r1 = M.IDENTITY.rotate(PI / 2).apply({ x: 1, y: 2 });
- expect(r1.x).toBeCloseTo(-2);
- expect(r1.y).toBeCloseTo(1);
- expect(M.IDENTITY.rotate(PI / 2).apply({ x: 0, y: 0 })).toEqual({
- x: 0,
- y: 0,
- });
- });
- test('append', async () => {
- expect(M.IDENTITY.append(M.IDENTITY)).toEqual(M.IDENTITY);
- expect(M.IDENTITY.append(new M(0, 1, 2, 3, 4, 5))).toEqual(new M(0, 1, 2, 3, 4, 5));
- expect(new M(0, 1, 2, 3, 4, 5).append(M.IDENTITY)).toEqual(new M(0, 1, 2, 3, 4, 5));
- expect(new M(0, 1, 2, 3, 4, 5).append(new M(0, 1, 2, 3, 4, 5))).toEqual(
- new M(2, 3, 6, 11, 14, 24),
- );
- });
- test('prepend', async () => {
- expect(M.IDENTITY.prepend(M.IDENTITY)).toEqual(M.IDENTITY);
- expect(M.IDENTITY.prepend(new M(0, 1, 2, 3, 4, 5))).toEqual(new M(0, 1, 2, 3, 4, 5));
- expect(new M(0, 1, 2, 3, 4, 5).prepend(M.IDENTITY)).toEqual(new M(0, 1, 2, 3, 4, 5));
- expect(new M(0, 1, 2, 3, 4, 5).prepend(new M(0, 1, 2, 3, 1, 2))).toEqual(
- new M(2, 3, 6, 11, 11, 21),
- );
- });
- test('identity', async () => {
- expect(new M(0, 1, 2, 3, 4, 5).identity()).toEqual(M.IDENTITY);
- });
- test('invert', async () => {
- expect(new M(0, 1, 2, 3, 4, 5).invert()).toEqual(new M(-1.5, 0.5, 1, -0, 1, -2));
- expect(new M(-1.5, 0.5, 1, -0, 1, -2).invert()).toEqual(new M(0, 1, 2, 3, 4, 5));
- // expect(M.IDENTITY.invert()).toEqual(M.IDENTITY)
- });
- test('copyTo', async () => {
- expect(new M(0, 1, 2, 3, 4, 5).copyTo(M.TEMP_MATRIX)).toEqual(new M(0, 1, 2, 3, 4, 5));
- });
- test('copyFrom', async () => {
- expect(M.TEMP_MATRIX.copyFrom(new M(0, 1, 2, 3, 4, 5))).toEqual(new M(0, 1, 2, 3, 4, 5));
- });
- test('isSimple', async () => {
- expect(new M(1, 0, 0, 1, 0, 0).isSimple()).toBeTruthy();
- expect(new M(0, 1, 2, 3, 4, 5).isSimple()).toBeFalsy();
- });
- /**
- * @see https://github.com/pixijs/pixijs/blob/dev/packages/math/test/Matrix.tests.ts
- */
- it('should create a new matrix', () => {
- const matrix = new Matrix();
- expect(matrix.a).toEqual(1);
- expect(matrix.b).toEqual(0);
- expect(matrix.c).toEqual(0);
- expect(matrix.d).toEqual(1);
- expect(matrix.tx).toEqual(0);
- expect(matrix.ty).toEqual(0);
- const input = [0, 1, 2, 3, 4, 5];
- matrix.fromArray(input);
- expect(matrix.a).toEqual(0);
- expect(matrix.b).toEqual(1);
- expect(matrix.c).toEqual(3);
- expect(matrix.d).toEqual(4);
- expect(matrix.tx).toEqual(2);
- expect(matrix.ty).toEqual(5);
- let output = matrix.toArray(true);
- expect(output.length).toEqual(9);
- expect(output[0]).toEqual(0);
- expect(output[1]).toEqual(1);
- expect(output[3]).toEqual(3);
- expect(output[4]).toEqual(4);
- expect(output[6]).toEqual(2);
- expect(output[7]).toEqual(5);
- output = matrix.toArray(false);
- expect(output.length).toEqual(9);
- expect(output[0]).toEqual(0);
- expect(output[1]).toEqual(3);
- expect(output[2]).toEqual(2);
- expect(output[3]).toEqual(1);
- expect(output[4]).toEqual(4);
- expect(output[5]).toEqual(5);
- });
- it('should apply different transforms', () => {
- const matrix = new Matrix();
- matrix.translate(10, 20);
- matrix.translate(1, 2);
- expect(matrix.tx).toEqual(11);
- expect(matrix.ty).toEqual(22);
- matrix.scale(2, 4);
- expect(matrix.a).toEqual(2);
- expect(matrix.b).toEqual(0);
- expect(matrix.c).toEqual(0);
- expect(matrix.d).toEqual(4);
- expect(matrix.tx).toEqual(22);
- expect(matrix.ty).toEqual(88);
- const m2 = matrix.clone();
- expect(m2).not.toBe(matrix);
- expect(m2.a).toEqual(2);
- expect(m2.b).toEqual(0);
- expect(m2.c).toEqual(0);
- expect(m2.d).toEqual(4);
- expect(m2.tx).toEqual(22);
- expect(m2.ty).toEqual(88);
- matrix.setTransform(14, 15, 0, 0, 4, 2, 0, 0, 0);
- expect(matrix.a).toEqual(4);
- expect(matrix.b).toEqual(0);
- // Object.is cant distinguish between 0 and -0
- expect(Math.abs(matrix.c)).toEqual(0);
- expect(matrix.d).toEqual(2);
- expect(matrix.tx).toEqual(14);
- expect(matrix.ty).toEqual(15);
- });
- it('should allow rotatation', () => {
- const matrix = new Matrix();
- matrix.rotate(Math.PI);
- expect(matrix.a).toEqual(-1);
- expect(matrix.b).toEqual(Math.sin(Math.PI));
- expect(matrix.c).toEqual(-Math.sin(Math.PI));
- expect(matrix.d).toEqual(-1);
- });
- it('should append matrix', () => {
- const m1 = new Matrix();
- const m2 = new Matrix();
- m2.tx = 100;
- m2.ty = 200;
- m1.append(m2);
- expect(m1.tx).toEqual(m2.tx);
- expect(m1.ty).toEqual(m2.ty);
- });
- it('should prepend matrix', () => {
- const m1 = new Matrix();
- const m2 = new Matrix();
- m2.set(2, 3, 4, 5, 100, 200);
- m1.prepend(m2);
- expect(m1.a).toEqual(m2.a);
- expect(m1.b).toEqual(m2.b);
- expect(m1.c).toEqual(m2.c);
- expect(m1.d).toEqual(m2.d);
- expect(m1.tx).toEqual(m2.tx);
- expect(m1.ty).toEqual(m2.ty);
- const m3 = new Matrix();
- const m4 = new Matrix();
- m3.prepend(m4);
- expect(m3.a).toEqual(m4.a);
- expect(m3.b).toEqual(m4.b);
- expect(m3.c).toEqual(m4.c);
- expect(m3.d).toEqual(m4.d);
- expect(m3.tx).toEqual(m4.tx);
- expect(m3.ty).toEqual(m4.ty);
- });
- it('should get IDENTITY and TEMP_MATRIX', () => {
- expect(Matrix.IDENTITY instanceof Matrix).toBe(true);
- expect(Matrix.TEMP_MATRIX instanceof Matrix).toBe(true);
- });
- it('should reset matrix to default when identity() is called', () => {
- const matrix = new Matrix();
- matrix.set(2, 3, 4, 5, 100, 200);
- expect(matrix.a).toEqual(2);
- expect(matrix.b).toEqual(3);
- expect(matrix.c).toEqual(4);
- expect(matrix.d).toEqual(5);
- expect(matrix.tx).toEqual(100);
- expect(matrix.ty).toEqual(200);
- matrix.identity();
- expect(matrix.a).toEqual(1);
- expect(matrix.b).toEqual(0);
- expect(matrix.c).toEqual(0);
- expect(matrix.d).toEqual(1);
- expect(matrix.tx).toEqual(0);
- expect(matrix.ty).toEqual(0);
- });
- it('should have the same transform after decompose', () => {
- const matrix = new Matrix();
- const transformInitial = new Transform();
- const transformDecomposed = new Transform();
- for (let x = 0; x < 50; ++x) {
- transformInitial.position.x = Math.random() * 1000 - 2000;
- transformInitial.position.y = Math.random() * 1000 - 2000;
- transformInitial.scale.x = Math.random() * 5 - 10;
- transformInitial.scale.y = Math.random() * 5 - 10;
- transformInitial.rotation = (Math.random() - 2) * Math.PI;
- transformInitial.skew.x = (Math.random() - 2) * Math.PI;
- transformInitial.skew.y = (Math.random() - 2) * Math.PI;
- matrix.setTransform(
- transformInitial.position.x,
- transformInitial.position.y,
- 0,
- 0,
- transformInitial.scale.x,
- transformInitial.scale.y,
- transformInitial.rotation,
- transformInitial.skew.x,
- transformInitial.skew.y,
- );
- matrix.decompose(transformDecomposed);
- transformInitial.updateLocalTransform();
- transformDecomposed.updateLocalTransform();
- expect(transformInitial.localTransform.a).toBeCloseTo(
- transformDecomposed.localTransform.a,
- 0.0001,
- );
- expect(transformInitial.localTransform.b).toBeCloseTo(
- transformDecomposed.localTransform.b,
- 0.0001,
- );
- expect(transformInitial.localTransform.c).toBeCloseTo(
- transformDecomposed.localTransform.c,
- 0.0001,
- );
- expect(transformInitial.localTransform.d).toBeCloseTo(
- transformDecomposed.localTransform.d,
- 0.0001,
- );
- expect(transformInitial.localTransform.tx).toBeCloseTo(
- transformDecomposed.localTransform.tx,
- 0.0001,
- );
- expect(transformInitial.localTransform.ty).toBeCloseTo(
- transformDecomposed.localTransform.ty,
- 0.0001,
- );
- }
- });
- it('should decompose corner case', () => {
- const matrix = new Matrix();
- const transform = new Transform();
- const result = transform.localTransform;
- matrix.a = -0.00001;
- matrix.b = -1;
- matrix.c = 1;
- matrix.d = 0;
- matrix.decompose(transform);
- transform.updateLocalTransform();
- expect(result.a).toBeCloseTo(matrix.a, 0.001);
- expect(result.b).toBeCloseTo(matrix.b, 0.001);
- expect(result.c).toBeCloseTo(matrix.c, 0.001);
- expect(result.d).toBeCloseTo(matrix.d, 0.001);
- });
- describe('decompose', () => {
- it('should be the inverse of updateLocalTransform even when pivot is set', () => {
- const matrix = new Matrix(0.01, 0.04, 0.04, 0.1, 2, 2);
- const transform = new Transform();
- transform.pivot.set(40, 40);
- matrix.decompose(transform);
- transform.updateLocalTransform();
- const { localTransform } = transform;
- expect(localTransform.a).toBeCloseTo(matrix.a, 0.001);
- expect(localTransform.b).toBeCloseTo(matrix.b, 0.001);
- expect(localTransform.c).toBeCloseTo(matrix.c, 0.001);
- expect(localTransform.d).toBeCloseTo(matrix.d, 0.001);
- // FIXME expect(localTransform.tx).toBeCloseTo(matrix.tx, 0.001)
- // FIXME expect(localTransform.ty).toBeCloseTo(matrix.ty, 0.001)
- });
- });
- });
|