, anchor: IPointData, out: IPointData): IPointData
38 | {
39 | out = out || new Point();
40 | const a1 = 1.0 - anchor.x; const
41 | a2 = 1.0 - a1;
42 | const b1 = 1.0 - anchor.y; const
43 | b2 = 1.0 - b1;
44 |
45 | out.x = (p[0].x * a1 + p[1].x * a2) * b1 + (p[3].x * a1 + p[2].x * a2) * b2;
46 | out.y = (p[0].y * a1 + p[1].y * a2) * b1 + (p[3].y * a1 + p[2].y * a2) * b2;
47 |
48 | return out;
49 | }
50 |
--------------------------------------------------------------------------------
/src/proj2d/tiling/TilingSprite2d.ts:
--------------------------------------------------------------------------------
1 | import { Renderer, Texture } from '@pixi/core';
2 | import { IPointData, Matrix, Point, Transform } from '@pixi/math';
3 | import { Projection2d } from '../Projection2d';
4 | import { DisplayObject } from '@pixi/display';
5 | import { TRANSFORM_STEP } from '../../base';
6 | import { container2dToLocal } from '../Container2d';
7 | import { TilingSprite } from '@pixi/sprite-tiling';
8 |
9 | const tempTransform = new Transform();
10 |
11 | export class TilingSprite2d extends TilingSprite
12 | {
13 | constructor(texture: Texture, width: number, height: number)
14 | {
15 | super(texture, width, height);
16 |
17 | this.tileProj = new Projection2d(this.tileTransform);
18 | this.tileProj.reverseLocalOrder = true;
19 | this.proj = new Projection2d(this.transform);
20 |
21 | this.pluginName = 'tilingSprite2d';
22 | this.uvRespectAnchor = true;
23 | }
24 |
25 | tileProj: Projection2d;
26 | proj: Projection2d;
27 |
28 | get worldTransform(): Matrix
29 | {
30 | return this.proj.affine ? this.transform.worldTransform : this.proj.world as any;
31 | }
32 |
33 | toLocal(position: IPointData, from?: DisplayObject, point?: P, skipUpdate?: boolean,
34 | step = TRANSFORM_STEP.ALL): P
35 | {
36 | return container2dToLocal.call(this, position, from, point, skipUpdate, step);
37 | }
38 |
39 | _render(renderer: Renderer): void
40 | {
41 | // tweak our texture temporarily..
42 | const texture = this._texture;
43 |
44 | if (!texture || !texture.valid)
45 | {
46 | return;
47 | }
48 |
49 | // changed
50 | this.tileTransform.updateTransform(tempTransform);
51 | this.uvMatrix.update();
52 |
53 | renderer.batch.setObjectRenderer((renderer.plugins as any)[this.pluginName]);
54 | (renderer.plugins as any)[this.pluginName].render(this);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/proj3d/Point3d.ts:
--------------------------------------------------------------------------------
1 | import { IPoint, IPointData, ObservablePoint, Point } from '@pixi/math';
2 |
3 | export class Point3d extends Point
4 | {
5 | // TODO: pixi 6.1.0 global mixin
6 | z: number;
7 | constructor(x?: number, y?: number, z?: number)
8 | {
9 | super(x, y);
10 | this.z = z;
11 | }
12 |
13 | set(x?: number, y?: number, z?: number): this
14 | {
15 | this.x = x || 0;
16 | this.y = (y === undefined) ? this.x : (y || 0);
17 | this.z = (y === undefined) ? this.x : (z || 0);
18 |
19 | return this;
20 | }
21 |
22 | copyFrom(p: IPointData): this
23 | {
24 | // TODO: pixi 6.1.0 global mixin
25 | this.set(p.x, p.y, (p as any).z || 0);
26 |
27 | return this;
28 | }
29 |
30 | copyTo(p: T): T
31 | {
32 | (p as any).set(this.x, this.y, this.z);
33 |
34 | return p;
35 | }
36 | }
37 |
38 | export class ObservablePoint3d extends ObservablePoint
39 | {
40 | _z = 0;
41 |
42 | get z(): number
43 | {
44 | return this._z;
45 | }
46 |
47 | set z(value: number)
48 | {
49 | if (this._z !== value)
50 | {
51 | this._z = value;
52 | this.cb.call(this.scope);
53 | }
54 | }
55 |
56 | set(x?: number, y?: number, z?: number): this
57 | {
58 | const _x = x || 0;
59 | const _y = (y === undefined) ? _x : (y || 0);
60 | const _z = (y === undefined) ? _x : (z || 0);
61 |
62 | if (this._x !== _x || this._y !== _y || this._z !== _z)
63 | {
64 | this._x = _x;
65 | this._y = _y;
66 | this._z = _z;
67 | this.cb.call(this.scope);
68 | }
69 |
70 | return this;
71 | }
72 |
73 | copyFrom(p: IPointData): this
74 | {
75 | // TODO: pixi 6.1.0 global mixin
76 | this.set(p.x, p.y, (p as any).z || 0);
77 |
78 | return this;
79 | }
80 |
81 | copyTo(p: T): T
82 | {
83 | (p as any).set(this._x, this._y, this._z);
84 |
85 | return p;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/proj2d/Container2d.ts:
--------------------------------------------------------------------------------
1 | import { Projection2d } from './Projection2d';
2 | import { Container, DisplayObject } from '@pixi/display';
3 | import { IPointData, Matrix, Point } from '@pixi/math';
4 | import { TRANSFORM_STEP } from '../base';
5 |
6 | export function container2dWorldTransform(): Matrix
7 | {
8 | return this.proj.affine ? this.transform.worldTransform : this.proj.world as any;
9 | }
10 |
11 | export class Container2d extends Container
12 | {
13 | constructor()
14 | {
15 | super();
16 | this.proj = new Projection2d(this.transform);
17 | }
18 |
19 | proj: Projection2d;
20 |
21 | toLocal(position: IPointData, from?: DisplayObject, point?: P, skipUpdate?: boolean,
22 | step = TRANSFORM_STEP.ALL): P
23 | {
24 | if (from)
25 | {
26 | position = from.toGlobal(position, point, skipUpdate);
27 | }
28 |
29 | if (!skipUpdate)
30 | {
31 | this._recursivePostUpdateTransform();
32 | }
33 |
34 | if (step >= TRANSFORM_STEP.PROJ)
35 | {
36 | if (!skipUpdate)
37 | {
38 | this.displayObjectUpdateTransform();
39 | }
40 | if (this.proj.affine)
41 | {
42 | return this.transform.worldTransform.applyInverse(position, point) as any;
43 | }
44 |
45 | return this.proj.world.applyInverse(position, point) as any;
46 | }
47 |
48 | if (this.parent)
49 | {
50 | point = this.parent.worldTransform.applyInverse(position, point) as any;
51 | }
52 | else
53 | {
54 | point.x = position.x;
55 | point.y = position.y;
56 | }
57 | if (step === TRANSFORM_STEP.NONE)
58 | {
59 | return point;
60 | }
61 |
62 | return this.transform.localTransform.applyInverse(point, point) as any;
63 | }
64 |
65 | get worldTransform(): Matrix
66 | {
67 | return this.proj.affine ? this.transform.worldTransform : this.proj.world as any;
68 | }
69 | }
70 |
71 | export const container2dToLocal = Container2d.prototype.toLocal;
72 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pixi-projection",
3 | "version": "1.0.0",
4 | "description": "Projections (2.5d, 3d and bilinear) for pixi v^7",
5 | "author": "Ivan Popelyshev",
6 | "contributors": [
7 | "Ivan Popelyshev "
8 | ],
9 | "main": "./lib/index.js",
10 | "module": "./lib/index.mjs",
11 | "types": "./lib/index.d.ts",
12 | "exports": {
13 | ".": {
14 | "import": "./lib/index.mjs",
15 | "require": "./lib/index.js",
16 | "types": "./lib/index.d.ts"
17 | }
18 | },
19 | "extensionConfig": {
20 | "lint": [
21 | "src"
22 | ],
23 | "namespace": "PIXI.projection",
24 | "docsName": "PixiJS 2d-3d projections",
25 | "docsCopyright": "Copyright © 2015 - 2023 Ivan Popelyshev",
26 | "docsTitle": "PixiJS 2d-3d projection API Documentation",
27 | "docsDescription": "Documentation for PixiJS 2.5D projection library",
28 | "docsKeywords": "docs, documentation, pixi, pixijs, rendering, 3d, projection, javascript"
29 | },
30 | "homepage": "http://www.pixijs.com/",
31 | "bugs": "https://github.com/pixijs/projection/issues",
32 | "license": "MIT",
33 | "repository": {
34 | "type": "git",
35 | "url": "https://github.com/pixijs/projection.git"
36 | },
37 | "scripts": {
38 | "clean": "xs clean",
39 | "start": "xs serve",
40 | "watch": "xs watch",
41 | "build": "xs build",
42 | "lint": "xs lint",
43 | "lint:fix": "xs lint --fix",
44 | "types": "xs types",
45 | "release": "xs release",
46 | "docs": "xs docs",
47 | "deploy": "xs deploy",
48 | "test": "xs build,docs"
49 | },
50 | "publishConfig": {
51 | "access": "public"
52 | },
53 | "engines": {
54 | "node": ">=16",
55 | "npm": ">=8"
56 | },
57 | "files": [
58 | "dist/",
59 | "lib/",
60 | "global.d.ts"
61 | ],
62 | "peerDependencies": {
63 | "@pixi-spine/base": "^4.0.3",
64 | "@pixi/core": "^7.2.0",
65 | "@pixi/display": "^7.2.0",
66 | "@pixi/graphics": "^7.2.0",
67 | "@pixi/mesh": "^7.2.0",
68 | "@pixi/mesh-extras": "^7.2.0",
69 | "@pixi/sprite": "^7.2.0",
70 | "@pixi/sprite-tiling": "^7.2.0",
71 | "@pixi/text": "^7.2.0"
72 | },
73 | "devDependencies": {
74 | "@pixi/core": "^7.2.0",
75 | "@pixi/display": "^7.2.0",
76 | "@pixi/extension-scripts": "^1.8.1",
77 | "@pixi/graphics": "^7.2.0"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/proj3d/sprites/Text3d.ts:
--------------------------------------------------------------------------------
1 | import { Text, TextStyle } from '@pixi/text';
2 | import { Projection3d } from '../Projection3d';
3 | import { IPointData, Matrix } from '@pixi/math';
4 | import { container3dGetDepth, container3dIsFrontFace, container3dToLocal } from '../Container3d';
5 | import { DisplayObject } from '@pixi/display';
6 | import { TRANSFORM_STEP } from '../../base';
7 | import { Sprite3d } from './Sprite3d';
8 | import { Euler } from '../Euler';
9 |
10 | export class Text3d extends Text
11 | {
12 | constructor(text?: string, style?: TextStyle, canvas?: HTMLCanvasElement)
13 | {
14 | super(text, style, canvas);
15 | this.proj = new Projection3d(this.transform);
16 | this.pluginName = 'batch2d';
17 | }
18 |
19 | proj: Projection3d;
20 | vertexData2d: Float32Array = null;
21 |
22 | get worldTransform(): Matrix
23 | {
24 | return this.proj.affine ? this.transform.worldTransform : this.proj.world as any;
25 | }
26 |
27 | toLocal(position: IPointData, from?: DisplayObject,
28 | point?: T, skipUpdate?: boolean,
29 | step = TRANSFORM_STEP.ALL): T
30 | {
31 | return container3dToLocal.call(this, position, from, point, skipUpdate, step);
32 | }
33 |
34 | isFrontFace(forceUpdate?: boolean): boolean
35 | {
36 | return container3dIsFrontFace.call(this, forceUpdate);
37 | }
38 |
39 | getDepth(forceUpdate?: boolean): boolean
40 | {
41 | return container3dGetDepth.call(this, forceUpdate);
42 | }
43 |
44 | get position3d(): IPointData
45 | {
46 | return this.proj.position;
47 | }
48 | set position3d(value: IPointData)
49 | {
50 | this.proj.position.copyFrom(value);
51 | }
52 | get scale3d(): IPointData
53 | {
54 | return this.proj.scale;
55 | }
56 | set scale3d(value: IPointData)
57 | {
58 | this.proj.scale.copyFrom(value);
59 | }
60 | get euler(): Euler
61 | {
62 | return this.proj.euler;
63 | }
64 | set euler(value: Euler)
65 | {
66 | this.proj.euler.copyFrom(value);
67 | }
68 | get pivot3d(): IPointData
69 | {
70 | return this.proj.pivot;
71 | }
72 | set pivot3d(value: IPointData)
73 | {
74 | this.proj.pivot.copyFrom(value);
75 | }
76 | }
77 |
78 | Text3d.prototype.calculateVertices = Sprite3d.prototype.calculateVertices;
79 | (Text3d.prototype as any).calculateTrimmedVertices = Sprite3d.prototype.calculateTrimmedVertices;
80 | (Text3d.prototype as any)._calculateBounds = Sprite3d.prototype._calculateBounds;
81 | Text3d.prototype.containsPoint = Sprite3d.prototype.containsPoint;
82 | (Text3d.prototype as any)._render = Sprite3d.prototype._render;
83 |
--------------------------------------------------------------------------------
/src/proj2d/sprites/convert.ts:
--------------------------------------------------------------------------------
1 | import { Projection2d } from '../Projection2d';
2 | import { Container2d, container2dWorldTransform } from '../Container2d';
3 | import { TilingSprite } from '@pixi/sprite-tiling';
4 | import { Program } from '@pixi/core';
5 | import { MeshMaterial } from '@pixi/mesh';
6 | import { Mesh2d } from '../mesh/Mesh2d';
7 | import { SimpleMesh, SimpleRope } from '@pixi/mesh-extras';
8 | import { Container } from '@pixi/display';
9 | import { Sprite } from '@pixi/sprite';
10 | import { Sprite2d } from './Sprite2d';
11 | import { TilingSprite2d } from '../tiling/TilingSprite2d';
12 |
13 | function convertTo2d()
14 | {
15 | if (this.proj) return;
16 | this.proj = new Projection2d(this.transform);
17 | this.toLocal = Container2d.prototype.toLocal;
18 | Object.defineProperty(this, 'worldTransform', {
19 | get: container2dWorldTransform,
20 | enumerable: true,
21 | configurable: true
22 | });
23 | }
24 |
25 | Container.prototype.convertTo2d = convertTo2d;
26 |
27 | Sprite.prototype.convertTo2d = function spriteConvertTo2d()
28 | {
29 | if (this.proj) return;
30 | this.calculateVertices = Sprite2d.prototype.calculateVertices;
31 | this.calculateTrimmedVertices = Sprite2d.prototype.calculateTrimmedVertices;
32 | this._calculateBounds = Sprite2d.prototype._calculateBounds;
33 | this.pluginName = 'batch2d';
34 | convertTo2d.call(this);
35 | };
36 |
37 | Container.prototype.convertSubtreeTo2d = function convertSubtreeTo2d()
38 | {
39 | this.convertTo2d();
40 | for (let i = 0; i < this.children.length; i++)
41 | {
42 | this.children[i].convertSubtreeTo2d();
43 | }
44 | };
45 |
46 | SimpleMesh.prototype.convertTo2d
47 | = SimpleRope.prototype.convertTo2d
48 | = function meshConvertTo2d()
49 | {
50 | if (this.proj) return;
51 | this.calculateVertices = Mesh2d.prototype.calculateVertices;
52 | this._renderDefault = Mesh2d.prototype._renderDefault;
53 | if (this.material.pluginName !== 'batch2d')
54 | {
55 | this.material = new MeshMaterial(this.material.texture, {
56 | program: Program.from(Mesh2d.defaultVertexShader, Mesh2d.defaultFragmentShader),
57 | pluginName: 'batch2d'
58 | });
59 | }
60 | convertTo2d.call(this);
61 | };
62 |
63 | TilingSprite.prototype.convertTo2d = function tilingConvertTo2d()
64 | {
65 | if (this.proj) return;
66 |
67 | this.tileProj = new Projection2d(this.tileTransform);
68 | this.tileProj.reverseLocalOrder = true;
69 | this.uvRespectAnchor = true;
70 |
71 | this.calculateTrimmedVertices = Sprite2d.prototype.calculateTrimmedVertices;
72 | this._calculateBounds = Sprite2d.prototype._calculateBounds;
73 | this._render = TilingSprite2d.prototype._render;
74 |
75 | this.pluginName = 'tilingSprite2d';
76 | convertTo2d.call(this);
77 | };
78 |
--------------------------------------------------------------------------------
/src/proj3d/sprites/convert.ts:
--------------------------------------------------------------------------------
1 | import { Container3d, container3dWorldTransform } from '../Container3d';
2 | import { Projection3d } from '../Projection3d';
3 | import { Mesh3d2d } from '../mesh/Mesh3d2d';
4 | import { MeshMaterial } from '@pixi/mesh';
5 | import { Mesh2d } from '../../proj2d';
6 | import { Program } from '@pixi/core';
7 | import { SimpleMesh, SimpleRope } from '@pixi/mesh-extras';
8 | import { Container } from '@pixi/display';
9 | import { Sprite } from '@pixi/sprite';
10 | import { Sprite3d } from './Sprite3d';
11 |
12 | const containerProps: any = {
13 | worldTransform: {
14 | get: container3dWorldTransform,
15 | enumerable: true,
16 | configurable: true
17 | },
18 | position3d: {
19 | get() { return this.proj.position; },
20 | set(value: any) { this.proj.position.copy(value); }
21 | },
22 | scale3d: {
23 | get() { return this.proj.scale; },
24 | set(value: any) { this.proj.scale.copy(value); }
25 | },
26 | pivot3d: {
27 | get() { return this.proj.pivot; },
28 | set(value: any) { this.proj.pivot.copy(value); }
29 | },
30 | euler: {
31 | get() { return this.proj.euler; },
32 | set(value: any) { this.proj.euler.copy(value); }
33 | }
34 | };
35 |
36 | function convertTo3d()
37 | {
38 | if (this.proj) return;
39 | this.proj = new Projection3d(this.transform);
40 | this.toLocal = Container3d.prototype.toLocal;
41 | this.isFrontFace = Container3d.prototype.isFrontFace;
42 | this.getDepth = Container3d.prototype.getDepth;
43 | Object.defineProperties(this, containerProps);
44 | }
45 |
46 | Container.prototype.convertTo3d = convertTo3d;
47 |
48 | Sprite.prototype.convertTo3d = function spriteConvertTo3d()
49 | {
50 | if (this.proj) return;
51 | this.calculateVertices = Sprite3d.prototype.calculateVertices;
52 | this.calculateTrimmedVertices = Sprite3d.prototype.calculateTrimmedVertices;
53 | this._calculateBounds = Sprite3d.prototype._calculateBounds;
54 | this.containsPoint = Sprite3d.prototype.containsPoint;
55 | this.pluginName = 'batch2d';
56 | convertTo3d.call(this);
57 | };
58 |
59 | Container.prototype.convertSubtreeTo3d = function convertSubtreeTo3d()
60 | {
61 | this.convertTo3d();
62 | for (let i = 0; i < this.children.length; i++)
63 | {
64 | this.children[i].convertSubtreeTo3d();
65 | }
66 | };
67 |
68 | SimpleMesh.prototype.convertTo3d
69 | = SimpleRope.prototype.convertTo3d
70 | = function meshConvert3d()
71 | {
72 | if (this.proj) return;
73 | this.calculateVertices = Mesh3d2d.prototype.calculateVertices;
74 | this._renderDefault = (Mesh3d2d.prototype as any)._renderDefault;
75 | if (this.material.pluginName !== 'batch2d')
76 | {
77 | this.material = new MeshMaterial(this.material.texture, {
78 | program: Program.from(Mesh2d.defaultVertexShader, Mesh2d.defaultFragmentShader),
79 | pluginName: 'batch2d'
80 | });
81 | }
82 | convertTo3d.call(this);
83 | };
84 |
--------------------------------------------------------------------------------
/src/proj3d/Projection3d.ts:
--------------------------------------------------------------------------------
1 | import { LinearProjection } from '../base';
2 | import { ObservablePoint3d } from './Point3d';
3 | import { Matrix, Transform } from '@pixi/math';
4 | import { Matrix3d } from './Matrix3d';
5 | import { ObservableEuler } from './ObservableEuler';
6 |
7 | const tempMat = new Matrix3d();
8 |
9 | export class Projection3d extends LinearProjection
10 | {
11 | constructor(legacy: Transform, enable?: boolean)
12 | {
13 | super(legacy, enable);
14 | this.local = new Matrix3d();
15 | this.world = new Matrix3d();
16 |
17 | this.local.cacheInverse = true;
18 | this.world.cacheInverse = true;
19 |
20 | this.position._z = 0;
21 | this.scale._z = 1;
22 | this.pivot._z = 0;
23 | }
24 |
25 | cameraMatrix: Matrix3d = null;
26 |
27 | _cameraMode = false;
28 |
29 | get cameraMode(): boolean
30 | {
31 | return this._cameraMode;
32 | }
33 |
34 | set cameraMode(value: boolean)
35 | {
36 | if (this._cameraMode === value)
37 | {
38 | return;
39 | }
40 | this._cameraMode = value;
41 |
42 | this.euler._sign = this._cameraMode ? -1 : 1;
43 | this.euler._quatDirtyId++;
44 |
45 | if (value)
46 | {
47 | this.cameraMatrix = new Matrix3d();
48 | }
49 | }
50 |
51 | position = new ObservablePoint3d(this.onChange, this, 0, 0);
52 | scale = new ObservablePoint3d(this.onChange, this, 1, 1);
53 | euler = new ObservableEuler(this.onChange, this, 0, 0, 0);
54 | pivot = new ObservablePoint3d(this.onChange, this, 0, 0);
55 |
56 | onChange(): void
57 | {
58 | this._projID++;
59 | }
60 |
61 | clear(): void
62 | {
63 | if (this.cameraMatrix)
64 | {
65 | this.cameraMatrix.identity();
66 | }
67 | this.position.set(0, 0, 0);
68 | this.scale.set(1, 1, 1);
69 | this.euler.set(0, 0, 0);
70 | this.pivot.set(0, 0, 0);
71 | super.clear();
72 | }
73 |
74 | updateLocalTransform(lt: Matrix): void
75 | {
76 | if (this._projID === 0)
77 | {
78 | this.local.copyFrom(lt);
79 |
80 | return;
81 | }
82 | const matrix = this.local;
83 | const euler = this.euler;
84 | const pos = this.position;
85 | const scale = this.scale;
86 | const pivot = this.pivot;
87 |
88 | euler.update();
89 |
90 | if (!this.cameraMode)
91 | {
92 | matrix.setToRotationTranslationScale(euler.quaternion, pos._x, pos._y, pos._z, scale._x, scale._y, scale._z);
93 | matrix.translate(-pivot._x, -pivot._y, -pivot._z);
94 | matrix.setToMultLegacy(lt, matrix);
95 |
96 | return;
97 | }
98 |
99 | matrix.setToMultLegacy(lt, this.cameraMatrix);
100 | matrix.translate(pivot._x, pivot._y, pivot._z);
101 | matrix.scale(1.0 / scale._x, 1.0 / scale._y, 1.0 / scale._z);
102 | tempMat.setToRotationTranslationScale(euler.quaternion, 0, 0, 0, 1, 1, 1);
103 | matrix.setToMult(matrix, tempMat);
104 | matrix.translate(-pos._x, -pos._y, -pos._z);
105 |
106 | this.local._dirtyId++;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/proj2d/z_masks/SpriteMaskFilter.ts:
--------------------------------------------------------------------------------
1 | import { Sprite } from '@pixi/sprite';
2 | import { Matrix2d } from '../Matrix2d';
3 | import { Filter, FilterSystem, RenderTexture, TextureMatrix } from '@pixi/core';
4 | import { Projection2d } from '../Projection2d';
5 |
6 | const spriteMaskVert = `
7 | attribute vec2 aVertexPosition;
8 | attribute vec2 aTextureCoord;
9 |
10 | uniform mat3 projectionMatrix;
11 | uniform mat3 otherMatrix;
12 |
13 | varying vec3 vMaskCoord;
14 | varying vec2 vTextureCoord;
15 |
16 | void main(void)
17 | {
18 | gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
19 |
20 | vTextureCoord = aTextureCoord;
21 | vMaskCoord = otherMatrix * vec3( aTextureCoord, 1.0);
22 | }
23 | `;
24 | const spriteMaskFrag = `
25 | varying vec3 vMaskCoord;
26 | varying vec2 vTextureCoord;
27 |
28 | uniform sampler2D uSampler;
29 | uniform sampler2D mask;
30 | uniform float alpha;
31 | uniform vec4 maskClamp;
32 |
33 | void main(void)
34 | {
35 | vec2 uv = vMaskCoord.xy / vMaskCoord.z;
36 |
37 | float clip = step(3.5,
38 | step(maskClamp.x, uv.x) +
39 | step(maskClamp.y, uv.y) +
40 | step(uv.x, maskClamp.z) +
41 | step(uv.y, maskClamp.w));
42 |
43 | vec4 original = texture2D(uSampler, vTextureCoord);
44 | vec4 masky = texture2D(mask, uv);
45 |
46 | original *= (masky.r * masky.a * alpha * clip);
47 |
48 | gl_FragColor = original;
49 | }
50 | `;
51 |
52 | const tempMat = new Matrix2d();
53 |
54 | export class SpriteMaskFilter2d extends Filter
55 | {
56 | constructor(sprite: Sprite)
57 | {
58 | super(spriteMaskVert, spriteMaskFrag);
59 |
60 | sprite.renderable = false;
61 |
62 | this.maskSprite = sprite;
63 | }
64 |
65 | maskSprite: Sprite;
66 | maskMatrix = new Matrix2d();
67 |
68 | apply(filterManager: FilterSystem, input: RenderTexture, output: RenderTexture,
69 | clearMode?: number): void
70 | {
71 | const maskSprite = this.maskSprite;
72 | const tex = this.maskSprite.texture;
73 |
74 | if (!tex.valid)
75 | {
76 | return;
77 | }
78 | if (!tex.uvMatrix)
79 | {
80 | // margin = 0.0, let it bleed a bit, shader code becomes easier
81 | // assuming that atlas textures were made with 1-pixel padding
82 | tex.uvMatrix = new TextureMatrix(tex, 0.0);
83 | }
84 | tex.uvMatrix.update();
85 |
86 | this.uniforms.npmAlpha = tex.baseTexture.alphaMode ? 0.0 : 1.0;
87 | this.uniforms.mask = maskSprite.texture;
88 | this.uniforms.otherMatrix = SpriteMaskFilter2d.calculateSpriteMatrix(input, this.maskMatrix, maskSprite)
89 | .prepend(tex.uvMatrix.mapCoord);
90 | this.uniforms.alpha = maskSprite.worldAlpha;
91 | this.uniforms.maskClamp = tex.uvMatrix.uClampFrame;
92 |
93 | filterManager.applyFilter(this, input, output, clearMode);
94 | }
95 |
96 | static calculateSpriteMatrix(input: RenderTexture, mappedMatrix: Matrix2d, sprite: Sprite): Matrix2d
97 | {
98 | const proj = (sprite as any).proj as Projection2d;
99 |
100 | const filterArea = (input as any).filterFrame;
101 |
102 | // eslint-disable-next-line max-len
103 | const worldTransform = proj && !proj._affine ? proj.world.copyTo2dOr3d(tempMat) : tempMat.copyFrom(sprite.transform.worldTransform);
104 | const texture = sprite.texture.orig;
105 |
106 | mappedMatrix.set(input.width, 0, 0, input.height, filterArea.x, filterArea.y);
107 | worldTransform.invert();
108 | mappedMatrix.setToMult(worldTransform, mappedMatrix);
109 | mappedMatrix.scaleAndTranslate(1.0 / texture.width, 1.0 / texture.height,
110 | sprite.anchor.x, sprite.anchor.y);
111 |
112 | return mappedMatrix;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/curve/BaseSurface.ts:
--------------------------------------------------------------------------------
1 | import { IPointData, Matrix, Point } from '@pixi/math';
2 | import { IWorldTransform } from './ProjectionSurface';
3 | import { Dict } from '@pixi/utils';
4 |
5 | const p = [new Point(), new Point(), new Point(), new Point()];
6 | const a = [0, 0, 0, 0];
7 |
8 | export abstract class Surface implements IWorldTransform
9 | {
10 | surfaceID = 'default';
11 |
12 | _updateID = 0;
13 |
14 | vertexSrc = '';
15 | fragmentSrc = '';
16 |
17 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
18 | fillUniforms(uniforms: Dict): void
19 | // eslint-disable-next-line @typescript-eslint/no-empty-function
20 | {
21 |
22 | }
23 |
24 | clear(): void
25 | // eslint-disable-next-line @typescript-eslint/no-empty-function
26 | {
27 |
28 | }
29 |
30 | /**
31 | * made for bilinear, other things will need adjustments, like test if (0) is inside
32 | * @param {ArrayLike} v
33 | * @param out
34 | * @param {Matrix} after
35 | */
36 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
37 | boundsQuad(v: ArrayLike, out: any, after?: Matrix): void
38 | {
39 | let minX = out[0]; let
40 | minY = out[1];
41 | let maxX = out[0]; let
42 | maxY = out[1];
43 |
44 | for (let i = 2; i < 8; i += 2)
45 | {
46 | if (minX > out[i]) minX = out[i];
47 | if (maxX < out[i]) maxX = out[i];
48 | if (minY > out[i + 1]) minY = out[i + 1];
49 | if (maxY < out[i + 1]) maxY = out[i + 1];
50 | }
51 |
52 | p[0].set(minX, minY);
53 | this.apply(p[0], p[0]);
54 | p[1].set(maxX, minY);
55 | this.apply(p[1], p[1]);
56 | p[2].set(maxX, maxY);
57 | this.apply(p[2], p[2]);
58 | p[3].set(minX, maxY);
59 | this.apply(p[3], p[3]);
60 |
61 | if (after)
62 | {
63 | after.apply(p[0], p[0]);
64 | after.apply(p[1], p[1]);
65 | after.apply(p[2], p[2]);
66 | after.apply(p[3], p[3]);
67 | out[0] = p[0].x;
68 | out[1] = p[0].y;
69 | out[2] = p[1].x;
70 | out[3] = p[1].y;
71 | out[4] = p[2].x;
72 | out[5] = p[2].y;
73 | out[6] = p[3].x;
74 | out[7] = p[3].y;
75 | }
76 | else
77 | {
78 | for (let i = 1; i <= 3; i++)
79 | {
80 | if (p[i].y < p[0].y || (p[i].y === p[0].y && p[i].x < p[0].x))
81 | {
82 | const t = p[0];
83 |
84 | p[0] = p[i];
85 | p[i] = t;
86 | }
87 | }
88 |
89 | for (let i = 1; i <= 3; i++)
90 | {
91 | a[i] = Math.atan2(p[i].y - p[0].y, p[i].x - p[0].x);
92 | }
93 | for (let i = 1; i <= 3; i++)
94 | {
95 | for (let j = i + 1; j <= 3; j++)
96 | {
97 | if (a[i] > a[j])
98 | {
99 | const t = p[i];
100 |
101 | p[i] = p[j];
102 | p[j] = t;
103 | const t2 = a[i];
104 |
105 | a[i] = a[j];
106 | a[j] = t2;
107 | }
108 | }
109 | }
110 |
111 | out[0] = p[0].x;
112 | out[1] = p[0].y;
113 | out[2] = p[1].x;
114 | out[3] = p[1].y;
115 | out[4] = p[2].x;
116 | out[5] = p[2].y;
117 | out[6] = p[3].x;
118 | out[7] = p[3].y;
119 |
120 | if (((p[3].x - p[2].x) * (p[1].y - p[2].y)) - ((p[1].x - p[2].x) * (p[3].y - p[2].y)) < 0)
121 | {
122 | // triangle!!!
123 | out[4] = p[3].x;
124 | out[5] = p[3].y;
125 |
126 | return;
127 | }
128 | }
129 | }
130 |
131 | abstract apply(pos: IPointData, newPos: IPointData): IPointData;
132 |
133 | // TODO: remove props
134 | abstract applyInverse(pos: IPointData, newPos: IPointData): IPointData;
135 | }
136 |
--------------------------------------------------------------------------------
/src/base/webgl/Sprite2dRenderer.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BatchRenderer,
3 | BatchShaderGenerator,
4 | ViewableBuffer,
5 | Buffer,
6 | Geometry,
7 | ExtensionType, Color, Renderer
8 | } from '@pixi/core';
9 | import { TYPES } from '@pixi/constants';
10 |
11 | const shaderVert
12 | = `precision highp float;
13 | attribute vec3 aVertexPosition;
14 | attribute vec2 aTextureCoord;
15 | attribute vec4 aColor;
16 | attribute float aTextureId;
17 |
18 | uniform mat3 projectionMatrix;
19 |
20 | varying vec2 vTextureCoord;
21 | varying vec4 vColor;
22 | varying float vTextureId;
23 |
24 | void main(void){
25 | gl_Position.xyw = projectionMatrix * aVertexPosition;
26 | gl_Position.z = 0.0;
27 |
28 | vTextureCoord = aTextureCoord;
29 | vTextureId = aTextureId;
30 | vColor = aColor;
31 | }
32 | `;
33 | const shaderFrag = `
34 | varying vec2 vTextureCoord;
35 | varying vec4 vColor;
36 | varying float vTextureId;
37 | uniform sampler2D uSamplers[%count%];
38 |
39 | void main(void){
40 | vec4 color;
41 | %forloop%
42 | gl_FragColor = color * vColor;
43 | }`;
44 |
45 | export class Batch3dGeometry extends Geometry
46 | {
47 | _buffer: Buffer;
48 | _indexBuffer : Buffer;
49 |
50 | constructor(_static = false)
51 | {
52 | super();
53 |
54 | this._buffer = new Buffer(null, _static, false);
55 |
56 | this._indexBuffer = new Buffer(null, _static, true);
57 |
58 | this.addAttribute('aVertexPosition', this._buffer, 3, false, TYPES.FLOAT)
59 | .addAttribute('aTextureCoord', this._buffer, 2, false, TYPES.FLOAT)
60 | .addAttribute('aColor', this._buffer, 4, true, TYPES.UNSIGNED_BYTE)
61 | .addAttribute('aTextureId', this._buffer, 1, true, TYPES.FLOAT)
62 | .addIndex(this._indexBuffer);
63 | }
64 | }
65 |
66 | export class Batch2dRenderer extends BatchRenderer
67 | {
68 | static extension = {
69 | name: 'batch2d',
70 | type: ExtensionType.RendererPlugin
71 | };
72 |
73 | constructor(renderer: Renderer)
74 | {
75 | super(renderer);
76 | this.geometryClass = Batch3dGeometry;
77 | this.vertexSize = 7;
78 | }
79 |
80 | setShaderGenerator()
81 | {
82 | this.shaderGenerator = new BatchShaderGenerator(
83 | shaderVert,
84 | shaderFrag
85 | );
86 | }
87 |
88 | // eslint-disable-next-line max-len
89 | packInterleavedGeometry(element: any, attributeBuffer: ViewableBuffer, indexBuffer: Uint16Array, aIndex: number, iIndex: number)
90 | {
91 | const {
92 | uint32View,
93 | float32View,
94 | } = attributeBuffer;
95 |
96 | const p = aIndex / this.vertexSize;// float32View.length / 6 / 2;
97 | const uvs = element.uvs;
98 | const indices = element.indices;// geometry.getIndex().data;// indicies;
99 | const vertexData = element.vertexData;
100 | const vertexData2d = element.vertexData2d;
101 | const textureId = element._texture.baseTexture._batchLocation;
102 |
103 | const alpha = Math.min(element.worldAlpha, 1.0);
104 |
105 | const argb = Color.shared
106 | .setValue(element._tintRGB)
107 | .toPremultiplied(alpha, element._texture.baseTexture.alphaMode > 0);
108 |
109 | if (vertexData2d)
110 | {
111 | let j = 0;
112 |
113 | for (let i = 0; i < vertexData2d.length; i += 3, j += 2)
114 | {
115 | float32View[aIndex++] = vertexData2d[i];
116 | float32View[aIndex++] = vertexData2d[i + 1];
117 | float32View[aIndex++] = vertexData2d[i + 2];
118 | float32View[aIndex++] = uvs[j];
119 | float32View[aIndex++] = uvs[j + 1];
120 | uint32View[aIndex++] = argb;
121 | float32View[aIndex++] = textureId;
122 | }
123 | }
124 | else
125 | {
126 | for (let i = 0; i < vertexData.length; i += 2)
127 | {
128 | float32View[aIndex++] = vertexData[i];
129 | float32View[aIndex++] = vertexData[i + 1];
130 | float32View[aIndex++] = 1.0;
131 | float32View[aIndex++] = uvs[i];
132 | float32View[aIndex++] = uvs[i + 1];
133 | uint32View[aIndex++] = argb;
134 | float32View[aIndex++] = textureId;
135 | }
136 | }
137 |
138 | for (let i = 0; i < indices.length; i++)
139 | {
140 | indexBuffer[iIndex++] = p + indices[i];
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/base/LinearProjection.ts:
--------------------------------------------------------------------------------
1 | import { AbstractProjection } from './AbstractProjection';
2 | import { Matrix, Transform } from '@pixi/math';
3 | import type { Projection2d } from '../proj2d';
4 |
5 | export enum AFFINE
6 | {
7 | NONE = 0,
8 | FREE = 1,
9 | AXIS_X = 2,
10 | AXIS_Y = 3,
11 | POINT = 4,
12 | AXIS_XR = 5
13 | }
14 |
15 | export function transformHack(this: Transform, parentTransform: Transform): void
16 | {
17 | // implementation here
18 | // TODO: pixi 6.1.0 global mixin
19 | const proj = (this as any).proj as LinearProjection;
20 | const ta = this as any;
21 | const pwid = (parentTransform as any)._worldID;
22 |
23 | const lt = ta.localTransform;
24 | const scaleAfterAffine = proj.scaleAfterAffine && proj.affine >= 2;
25 |
26 | // this part is copied from
27 | if (ta._localID !== ta._currentLocalID)
28 | {
29 | // get the matrix values of the displayobject based on its transform properties..
30 | if (scaleAfterAffine)
31 | {
32 | lt.a = ta._cx;
33 | lt.b = ta._sx;
34 | lt.c = ta._cy;
35 | lt.d = ta._sy;
36 |
37 | lt.tx = ta.position._x;
38 | lt.ty = ta.position._y;
39 | }
40 | else
41 | {
42 | lt.a = ta._cx * ta.scale._x;
43 | lt.b = ta._sx * ta.scale._x;
44 | lt.c = ta._cy * ta.scale._y;
45 | lt.d = ta._sy * ta.scale._y;
46 |
47 | lt.tx = ta.position._x - ((ta.pivot._x * lt.a) + (ta.pivot._y * lt.c));
48 | lt.ty = ta.position._y - ((ta.pivot._x * lt.b) + (ta.pivot._y * lt.d));
49 | }
50 |
51 | ta._currentLocalID = ta._localID;
52 |
53 | // force an update..
54 | proj._currentProjID = -1;
55 | }
56 |
57 | const _matrixID = proj._projID;
58 |
59 | if (proj._currentProjID !== _matrixID)
60 | {
61 | proj._currentProjID = _matrixID;
62 | proj.updateLocalTransform(lt);
63 | ta._parentID = -1;
64 | }
65 |
66 | if (ta._parentID !== pwid)
67 | {
68 | // TODO: pixi 6.1.0 global mixin
69 | const pp = (parentTransform as any).proj as Projection2d;
70 |
71 | if (pp && !pp._affine)
72 | {
73 | proj.world.setToMult(pp.world, proj.local);
74 | }
75 | else
76 | {
77 | proj.world.setToMultLegacy(parentTransform.worldTransform, proj.local);
78 | }
79 |
80 | const wa = ta.worldTransform;
81 |
82 | proj.world.copyTo(wa, proj._affine, proj.affinePreserveOrientation);
83 |
84 | if (scaleAfterAffine)
85 | {
86 | wa.a *= ta.scale._x;
87 | wa.b *= ta.scale._x;
88 | wa.c *= ta.scale._y;
89 | wa.d *= ta.scale._y;
90 |
91 | wa.tx -= ((ta.pivot._x * wa.a) + (ta.pivot._y * wa.c));
92 | wa.ty -= ((ta.pivot._x * wa.b) + (ta.pivot._y * wa.d));
93 | }
94 | ta._parentID = pwid;
95 | ta._worldID++;
96 | }
97 | }
98 |
99 | export class LinearProjection extends AbstractProjection
100 | {
101 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
102 | updateLocalTransform(lt: Matrix): void
103 | // eslint-disable-next-line @typescript-eslint/no-empty-function
104 | {
105 | }
106 |
107 | _projID = 0;
108 | _currentProjID = -1;
109 | _affine = AFFINE.NONE;
110 | affinePreserveOrientation = false;
111 | scaleAfterAffine = true;
112 |
113 | set affine(value: AFFINE)
114 | {
115 | if (this._affine === value) return;
116 | this._affine = value;
117 | this._currentProjID = -1;
118 | // this is because scaleAfterAffine
119 | (this.legacy as any)._currentLocalID = -1;
120 | }
121 |
122 | get affine(): AFFINE
123 | {
124 | return this._affine;
125 | }
126 |
127 | local: T;
128 | world: T;
129 |
130 | // eslint-disable-next-line accessor-pairs
131 | set enabled(value: boolean)
132 | {
133 | if (value === this._enabled)
134 | {
135 | return;
136 | }
137 | this._enabled = value;
138 | if (value)
139 | {
140 | this.legacy.updateTransform = transformHack;
141 | (this.legacy as any)._parentID = -1;
142 | }
143 | else
144 | {
145 | this.legacy.updateTransform = Transform.prototype.updateTransform;
146 | (this.legacy as any)._parentID = -1;
147 | }
148 | }
149 |
150 | clear(): void
151 | {
152 | this._currentProjID = -1;
153 | this._projID = 0;
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/proj3d/Euler.ts:
--------------------------------------------------------------------------------
1 | import { IEuler } from './ObservableEuler';
2 |
3 | export class Euler
4 | {
5 | constructor(x?: number, y?: number, z?: number)
6 | {
7 | /**
8 | * @member {number}
9 | * @default 0
10 | */
11 | this._x = x || 0;
12 |
13 | /**
14 | * @member {number}
15 | * @default 0
16 | */
17 | this._y = y || 0;
18 |
19 | /**
20 | * @member {number}
21 | * @default 0
22 | */
23 | this._z = z || 0;
24 |
25 | this.quaternion = new Float64Array(4);
26 | this.quaternion[3] = 1;
27 |
28 | this.update();
29 | }
30 |
31 | _quatUpdateId = -1;
32 | _quatDirtyId = 0;
33 |
34 | quaternion: Float64Array;
35 |
36 | _x: number;
37 | _y: number;
38 | _z: number;
39 | _sign = 1;
40 |
41 | get x(): number
42 | {
43 | return this._x;
44 | }
45 |
46 | set x(value: number)
47 | {
48 | if (this._x !== value)
49 | {
50 | this._x = value;
51 | this._quatDirtyId++;
52 | }
53 | }
54 |
55 | get y(): number
56 | {
57 | return this._y;
58 | }
59 |
60 | set y(value: number)
61 | {
62 | if (this._y !== value)
63 | {
64 | this._y = value;
65 | this._quatDirtyId++;
66 | }
67 | }
68 |
69 | get z(): number
70 | {
71 | return this._z;
72 | }
73 |
74 | set z(value: number)
75 | {
76 | if (this._z !== value)
77 | {
78 | this._z = value;
79 | this._quatDirtyId++;
80 | }
81 | }
82 |
83 | get pitch(): number
84 | {
85 | return this._x;
86 | }
87 |
88 | set pitch(value: number)
89 | {
90 | if (this._x !== value)
91 | {
92 | this._x = value;
93 | this._quatDirtyId++;
94 | }
95 | }
96 |
97 | get yaw(): number
98 | {
99 | return this._y;
100 | }
101 |
102 | set yaw(value: number)
103 | {
104 | if (this._y !== value)
105 | {
106 | this._y = value;
107 | this._quatDirtyId++;
108 | }
109 | }
110 |
111 | get roll(): number
112 | {
113 | return this._z;
114 | }
115 |
116 | set roll(value: number)
117 | {
118 | if (this._z !== value)
119 | {
120 | this._z = value;
121 | this._quatDirtyId++;
122 | }
123 | }
124 |
125 | set(x?: number, y?: number, z?: number): void
126 | {
127 | const _x = x || 0;
128 | const _y = y || 0;
129 | const _z = z || 0;
130 |
131 | if (this._x !== _x || this._y !== _y || this._z !== _z)
132 | {
133 | this._x = _x;
134 | this._y = _y;
135 | this._z = _z;
136 | this._quatDirtyId++;
137 | }
138 | }
139 |
140 | copyFrom(euler: IEuler): this
141 | {
142 | const _x = euler.x;
143 | const _y = euler.y;
144 | const _z = euler.z;
145 |
146 | if (this._x !== _x || this._y !== _y || this._z !== _z)
147 | {
148 | this._x = _x;
149 | this._y = _y;
150 | this._z = _z;
151 | this._quatDirtyId++;
152 | }
153 |
154 | return this;
155 | }
156 |
157 | copyTo(p: IEuler): IEuler
158 | {
159 | p.set(this._x, this._y, this._z);
160 |
161 | return p;
162 | }
163 |
164 | equals(euler: IEuler): boolean
165 | {
166 | return this._x === euler.x
167 | && this._y === euler.y
168 | && this._z === euler.z;
169 | }
170 |
171 | clone(): Euler
172 | {
173 | return new Euler(this._x, this._y, this._z);
174 | }
175 |
176 | update(): boolean
177 | {
178 | if (this._quatUpdateId === this._quatDirtyId)
179 | {
180 | return false;
181 | }
182 | this._quatUpdateId = this._quatDirtyId;
183 |
184 | const c1 = Math.cos(this._x / 2);
185 | const c2 = Math.cos(this._y / 2);
186 | const c3 = Math.cos(this._z / 2);
187 |
188 | const s = this._sign;
189 | const s1 = s * Math.sin(this._x / 2);
190 | const s2 = s * Math.sin(this._y / 2);
191 | const s3 = s * Math.sin(this._z / 2);
192 |
193 | const q = this.quaternion;
194 |
195 | q[0] = (s1 * c2 * c3) + (c1 * s2 * s3);
196 | q[1] = (c1 * s2 * c3) - (s1 * c2 * s3);
197 | q[2] = (c1 * c2 * s3) + (s1 * s2 * c3);
198 | q[3] = (c1 * c2 * c3) - (s1 * s2 * s3);
199 |
200 | return true;
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/src/curve/BilinearSurface.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-mixed-operators */
2 | import { IPoint, IPointData, Matrix, Point, Rectangle, Transform } from '@pixi/math';
3 | import { Sprite } from '@pixi/sprite';
4 | import { Surface } from './BaseSurface';
5 | import { Dict } from '@pixi/utils';
6 |
7 | const tempMat = new Matrix();
8 | const tempRect = new Rectangle();
9 | const tempPoint = new Point();
10 |
11 | export class BilinearSurface extends Surface
12 | {
13 | distortion = new Point();
14 |
15 | clear(): void
16 | {
17 | this.distortion.set(0, 0);
18 | }
19 |
20 | apply(pos: IPointData, newPos?: IPointData): IPointData
21 | {
22 | newPos = newPos || new Point();
23 | const d = this.distortion;
24 | const m = pos.x * pos.y;
25 |
26 | newPos.x = pos.x + d.x * m;
27 | newPos.y = pos.y + d.y * m;
28 |
29 | return newPos;
30 | }
31 |
32 | applyInverse(pos: IPointData, newPos: IPoint): IPointData
33 | {
34 | newPos = newPos || new Point();
35 | const vx = pos.x; const
36 | vy = pos.y;
37 | const dx = this.distortion.x; const
38 | dy = this.distortion.y;
39 |
40 | if (dx === 0.0)
41 | {
42 | newPos.x = vx;
43 | newPos.y = vy / (1.0 + dy * vx);
44 | }
45 | else
46 | if (dy === 0.0)
47 | {
48 | newPos.y = vy;
49 | newPos.x = vx / (1.0 + dx * vy);
50 | }
51 | else
52 | {
53 | const b = (vy * dx - vx * dy + 1.0) * 0.5 / dy;
54 | const d = b * b + vx / dy;
55 |
56 | if (d <= 0.00001)
57 | {
58 | newPos.set(NaN, NaN);
59 |
60 | return newPos;
61 | }
62 | if (dy > 0.0)
63 | {
64 | newPos.x = -b + Math.sqrt(d);
65 | }
66 | else
67 | {
68 | newPos.x = -b - Math.sqrt(d);
69 | }
70 | newPos.y = (vx / newPos.x - 1.0) / dx;
71 | }
72 |
73 | return newPos;
74 | }
75 |
76 | mapSprite(sprite: Sprite, quad: Array, outTransform?: Transform): this
77 | {
78 | const tex = sprite.texture;
79 |
80 | tempRect.x = -sprite.anchor.x * tex.orig.width;
81 | tempRect.y = -sprite.anchor.y * tex.orig.height;
82 | tempRect.width = tex.orig.width;
83 | tempRect.height = tex.orig.height;
84 |
85 | return this.mapQuad(tempRect, quad, outTransform || sprite.transform as Transform);
86 | }
87 |
88 | mapQuad(rect: Rectangle, quad: Array, outTransform: Transform): this
89 | {
90 | const ax = -rect.x / rect.width;
91 | const ay = -rect.y / rect.height;
92 |
93 | const ax2 = (1.0 - rect.x) / rect.width;
94 | const ay2 = (1.0 - rect.y) / rect.height;
95 |
96 | const up1x = (quad[0].x * (1.0 - ax) + quad[1].x * ax);
97 | const up1y = (quad[0].y * (1.0 - ax) + quad[1].y * ax);
98 | const up2x = (quad[0].x * (1.0 - ax2) + quad[1].x * ax2);
99 | const up2y = (quad[0].y * (1.0 - ax2) + quad[1].y * ax2);
100 |
101 | const down1x = (quad[3].x * (1.0 - ax) + quad[2].x * ax);
102 | const down1y = (quad[3].y * (1.0 - ax) + quad[2].y * ax);
103 | const down2x = (quad[3].x * (1.0 - ax2) + quad[2].x * ax2);
104 | const down2y = (quad[3].y * (1.0 - ax2) + quad[2].y * ax2);
105 |
106 | const x00 = up1x * (1.0 - ay) + down1x * ay;
107 | const y00 = up1y * (1.0 - ay) + down1y * ay;
108 |
109 | const x10 = up2x * (1.0 - ay) + down2x * ay;
110 | const y10 = up2y * (1.0 - ay) + down2y * ay;
111 |
112 | const x01 = up1x * (1.0 - ay2) + down1x * ay2;
113 | const y01 = up1y * (1.0 - ay2) + down1y * ay2;
114 |
115 | const x11 = up2x * (1.0 - ay2) + down2x * ay2;
116 | const y11 = up2y * (1.0 - ay2) + down2y * ay2;
117 |
118 | const mat = tempMat;
119 |
120 | mat.tx = x00;
121 | mat.ty = y00;
122 | mat.a = x10 - x00;
123 | mat.b = y10 - y00;
124 | mat.c = x01 - x00;
125 | mat.d = y01 - y00;
126 | tempPoint.set(x11, y11);
127 | mat.applyInverse(tempPoint, tempPoint);
128 | this.distortion.set(tempPoint.x - 1, tempPoint.y - 1);
129 |
130 | outTransform.setFromMatrix(mat);
131 |
132 | return this;
133 | }
134 |
135 | fillUniforms(uniforms: Dict): void
136 | {
137 | uniforms.distortion = uniforms.distortion || new Float32Array([0, 0, 0, 0]);
138 | const ax = Math.abs(this.distortion.x);
139 | const ay = Math.abs(this.distortion.y);
140 |
141 | uniforms.distortion[0] = ax * 10000 <= ay ? 0 : this.distortion.x;
142 | uniforms.distortion[1] = ay * 10000 <= ax ? 0 : this.distortion.y;
143 | uniforms.distortion[2] = 1.0 / uniforms.distortion[0];
144 | uniforms.distortion[3] = 1.0 / uniforms.distortion[1];
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/proj3d/Container3d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-mixed-operators */
2 | import { Projection3d } from './Projection3d';
3 | import { Container, DisplayObject } from '@pixi/display';
4 | import { IPointData, Matrix, Point } from '@pixi/math';
5 | import { TRANSFORM_STEP } from '../base';
6 | import { IEuler } from './ObservableEuler';
7 |
8 | export function container3dWorldTransform(): Matrix
9 | {
10 | return this.proj.affine ? this.transform.worldTransform : this.proj.world as any;
11 | }
12 |
13 | export interface IDisplayObject3d
14 | {
15 | isFrontFace(forceUpdate?: boolean): boolean;
16 | getDepth(forceUpdate?: boolean): number;
17 | // eslint-disable-next-line max-len
18 | toLocal(position: IPointData, from?: DisplayObject, point?: P, skipUpdate?: boolean, step?: TRANSFORM_STEP): P;
19 | position3d: IPointData;
20 | scale3d: IPointData;
21 | euler: IEuler;
22 | pivot3d: IPointData;
23 | }
24 |
25 | export class Container3d extends Container implements IDisplayObject3d
26 | {
27 | constructor()
28 | {
29 | super();
30 | this.proj = new Projection3d(this.transform);
31 | }
32 |
33 | proj: Projection3d;
34 |
35 | isFrontFace(forceUpdate = false): boolean
36 | {
37 | if (forceUpdate)
38 | {
39 | this._recursivePostUpdateTransform();
40 | this.displayObjectUpdateTransform();
41 | }
42 |
43 | const mat = this.proj.world.mat4;
44 | const dx1 = mat[0] * mat[15] - mat[3] * mat[12];
45 | const dy1 = mat[1] * mat[15] - mat[3] * mat[13];
46 | const dx2 = mat[4] * mat[15] - mat[7] * mat[12];
47 | const dy2 = mat[5] * mat[15] - mat[7] * mat[13];
48 |
49 | return dx1 * dy2 - dx2 * dy1 > 0;
50 | }
51 |
52 | /**
53 | * returns depth from 0 to 1
54 | *
55 | * @param {boolean} forceUpdate whether to force matrix updates
56 | * @returns {number} depth
57 | */
58 | getDepth(forceUpdate = false): number
59 | {
60 | if (forceUpdate)
61 | {
62 | this._recursivePostUpdateTransform();
63 | this.displayObjectUpdateTransform();
64 | }
65 |
66 | const mat4 = this.proj.world.mat4;
67 |
68 | return mat4[14] / mat4[15];
69 | }
70 |
71 | toLocal
(position: IPointData, from?: DisplayObject, point?: P, skipUpdate?: boolean,
72 | step = TRANSFORM_STEP.ALL): P
73 | {
74 | if (from)
75 | {
76 | position = from.toGlobal(position, point, skipUpdate);
77 | }
78 |
79 | if (!skipUpdate)
80 | {
81 | this._recursivePostUpdateTransform();
82 | }
83 |
84 | if (step === TRANSFORM_STEP.ALL)
85 | {
86 | if (!skipUpdate)
87 | {
88 | this.displayObjectUpdateTransform();
89 | }
90 | if (this.proj.affine)
91 | {
92 | return this.transform.worldTransform.applyInverse(position, point) as any;
93 | }
94 |
95 | return this.proj.world.applyInverse(position, point) as any;
96 | }
97 |
98 | if (this.parent)
99 | {
100 | point = this.parent.worldTransform.applyInverse(position, point) as any;
101 | }
102 | else
103 | {
104 | point.x = position.x;
105 | point.y = position.y;
106 | // TODO: pixi 6.1.0 global mixin
107 | (point as any).z = (position as any).z;
108 | }
109 | if (step === TRANSFORM_STEP.NONE)
110 | {
111 | return point;
112 | }
113 |
114 | point = this.transform.localTransform.applyInverse(point, point) as any;
115 | if (step === TRANSFORM_STEP.PROJ && this.proj.cameraMode)
116 | {
117 | point = this.proj.cameraMatrix.applyInverse(point, point) as any;
118 | }
119 |
120 | return point;
121 | }
122 |
123 | get worldTransform(): Matrix
124 | {
125 | return this.proj.affine ? this.transform.worldTransform : this.proj.world as any;
126 | }
127 |
128 | get position3d(): IPointData
129 | {
130 | return this.proj.position;
131 | }
132 | set position3d(value: IPointData)
133 | {
134 | this.proj.position.copyFrom(value);
135 | }
136 | get scale3d(): IPointData
137 | {
138 | return this.proj.scale;
139 | }
140 | set scale3d(value: IPointData)
141 | {
142 | this.proj.scale.copyFrom(value);
143 | }
144 | get euler(): IEuler
145 | {
146 | return this.proj.euler;
147 | }
148 | set euler(value: IEuler)
149 | {
150 | this.proj.euler.copyFrom(value);
151 | }
152 | get pivot3d(): IPointData
153 | {
154 | return this.proj.pivot;
155 | }
156 | set pivot3d(value: IPointData)
157 | {
158 | this.proj.pivot.copyFrom(value);
159 | }
160 | }
161 |
162 | export const container3dToLocal = Container3d.prototype.toLocal;
163 | export const container3dGetDepth = Container3d.prototype.getDepth;
164 | export const container3dIsFrontFace = Container3d.prototype.isFrontFace;
165 |
--------------------------------------------------------------------------------
/src/base/webgl/UniformBatchRenderer.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BatchRenderer,
3 | ViewableBuffer,
4 | BatchTextureArray
5 | } from '@pixi/core';
6 | import { Dict, premultiplyBlendMode } from '@pixi/utils';
7 | import { Sprite } from '@pixi/sprite';
8 |
9 | export class UniformBatchRenderer extends BatchRenderer
10 | {
11 | _iIndex: number;
12 | _aIndex: number;
13 | _dcIndex: number;
14 | _bufferedElements: Array;
15 | _attributeBuffer: ViewableBuffer;
16 | _indexBuffer: Uint16Array;
17 | vertexSize: number;
18 | forceMaxTextures = 0;
19 |
20 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
21 | getUniforms(sprite: Sprite): any
22 | {
23 | return this.defUniforms;
24 | }
25 |
26 | syncUniforms(obj: Dict): void
27 | {
28 | if (!obj) return;
29 | const sh = this._shader;
30 |
31 | for (const key in obj)
32 | {
33 | sh.uniforms[key] = obj[key];
34 | }
35 | }
36 |
37 | defUniforms = {};
38 |
39 | buildDrawCalls(texArray: BatchTextureArray, start: number, finish: number): void
40 | {
41 | const thisAny = this as any;
42 |
43 | const {
44 | _bufferedElements: elements,
45 | _attributeBuffer,
46 | _indexBuffer,
47 | vertexSize,
48 | } = this;
49 | const drawCalls = BatchRenderer._drawCallPool;
50 |
51 | let dcIndex: number = this._dcIndex;
52 | let aIndex: number = this._aIndex;
53 | let iIndex: number = this._iIndex;
54 |
55 | let drawCall = drawCalls[dcIndex] as any;
56 |
57 | drawCall.start = this._iIndex;
58 | drawCall.texArray = texArray;
59 |
60 | for (let i = start; i < finish; ++i)
61 | {
62 | const sprite = elements[i];
63 | const tex = sprite._texture.baseTexture;
64 | const spriteBlendMode = premultiplyBlendMode[
65 | tex.alphaMode ? 1 : 0][sprite.blendMode];
66 | const uniforms = this.getUniforms(sprite);
67 |
68 | elements[i] = null;
69 |
70 | // here is the difference
71 | if (start < i && (drawCall.blend !== spriteBlendMode || drawCall.uniforms !== uniforms))
72 | {
73 | drawCall.size = iIndex - drawCall.start;
74 | start = i;
75 | drawCall = drawCalls[++dcIndex];
76 | drawCall.texArray = texArray;
77 | drawCall.start = iIndex;
78 | }
79 |
80 | this.packInterleavedGeometry(sprite, _attributeBuffer, _indexBuffer, aIndex, iIndex);
81 | aIndex += sprite.vertexData.length / 2 * vertexSize;
82 | iIndex += sprite.indices.length;
83 |
84 | drawCall.blend = spriteBlendMode;
85 | // here is the difference
86 | drawCall.uniforms = uniforms;
87 | }
88 |
89 | if (start < finish)
90 | {
91 | drawCall.size = iIndex - drawCall.start;
92 | ++dcIndex;
93 | }
94 |
95 | thisAny._dcIndex = dcIndex;
96 | thisAny._aIndex = aIndex;
97 | thisAny._iIndex = iIndex;
98 | }
99 |
100 | drawBatches(): void
101 | {
102 | const dcCount = this._dcIndex;
103 | const { gl, state: stateSystem, shader: shaderSystem } = this.renderer;
104 | const drawCalls = BatchRenderer._drawCallPool;
105 | let curUniforms: any = null;
106 | let curTexArray: BatchTextureArray = null;
107 |
108 | for (let i = 0; i < dcCount; i++)
109 | {
110 | const { texArray, type, size, start, blend, uniforms } = drawCalls[i] as any;
111 |
112 | if (curTexArray !== texArray)
113 | {
114 | curTexArray = texArray;
115 | this.bindAndClearTexArray(texArray);
116 | }
117 | // here is the difference
118 | if (curUniforms !== uniforms)
119 | {
120 | curUniforms = uniforms;
121 | this.syncUniforms(uniforms);
122 | (shaderSystem as any).syncUniformGroup((this._shader as any).uniformGroup);
123 | }
124 |
125 | this.state.blendMode = blend;
126 | stateSystem.set(this.state);
127 | gl.drawElements(type, size, gl.UNSIGNED_SHORT, start * 2);
128 | }
129 | }
130 |
131 | contextChange(): void
132 | {
133 | if (!this.forceMaxTextures)
134 | {
135 | super.contextChange();
136 | this.syncUniforms(this.defUniforms);
137 |
138 | return;
139 | }
140 |
141 | // we can override maxTextures with this hack
142 |
143 | const thisAny = this as any;
144 |
145 | thisAny.maxTextures = this.forceMaxTextures;
146 | this._shader = thisAny.shaderGenerator.generateShader(this.maxTextures);
147 | this.syncUniforms(this.defUniforms);
148 | for (let i = 0; i < thisAny._packedGeometryPoolSize; i++)
149 | {
150 | /* eslint-disable max-len */
151 | thisAny._packedGeometries[i] = new (this.geometryClass)();
152 | }
153 | this.initFlushBuffers();
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/proj2d/tiling/TilingSprite2dRenderer.ts:
--------------------------------------------------------------------------------
1 | import { Matrix2d } from '../Matrix2d';
2 | import { ExtensionMetadata, ExtensionType, ObjectRenderer, QuadUv, Renderer, Shader } from '@pixi/core';
3 | import { DRAW_MODES, WRAP_MODES } from '@pixi/constants';
4 | import { correctBlendMode, premultiplyTintToRgba } from '@pixi/utils';
5 |
6 | const shaderVert
7 | = `attribute vec2 aVertexPosition;
8 | attribute vec2 aTextureCoord;
9 |
10 | uniform mat3 projectionMatrix;
11 | uniform mat3 translationMatrix;
12 | uniform mat3 uTransform;
13 |
14 | varying vec3 vTextureCoord;
15 |
16 | void main(void)
17 | {
18 | gl_Position.xyw = projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0);
19 |
20 | vTextureCoord = uTransform * vec3(aTextureCoord, 1.0);
21 | }
22 | `;
23 | const shaderFrag = `
24 | varying vec3 vTextureCoord;
25 |
26 | uniform sampler2D uSampler;
27 | uniform vec4 uColor;
28 | uniform mat3 uMapCoord;
29 | uniform vec4 uClampFrame;
30 | uniform vec2 uClampOffset;
31 |
32 | void main(void)
33 | {
34 | vec2 coord = mod(vTextureCoord.xy / vTextureCoord.z - uClampOffset, vec2(1.0, 1.0)) + uClampOffset;
35 | coord = (uMapCoord * vec3(coord, 1.0)).xy;
36 | coord = clamp(coord, uClampFrame.xy, uClampFrame.zw);
37 |
38 | vec4 sample = texture2D(uSampler, coord);
39 | gl_FragColor = sample * uColor;
40 | }
41 | `;
42 | const shaderSimpleFrag = `
43 | varying vec3 vTextureCoord;
44 |
45 | uniform sampler2D uSampler;
46 | uniform vec4 uColor;
47 |
48 | void main(void)
49 | {
50 | vec4 sample = texture2D(uSampler, vTextureCoord.xy / vTextureCoord.z);
51 | gl_FragColor = sample * uColor;
52 | }
53 | `;
54 |
55 | // changed
56 | const tempMat = new Matrix2d();
57 |
58 | export class TilingSprite2dRenderer extends ObjectRenderer
59 | {
60 | static extension: ExtensionMetadata = {
61 | name: 'tilingSprite2d',
62 | type: ExtensionType.RendererPlugin,
63 | };
64 |
65 | constructor(renderer: Renderer)
66 | {
67 | super(renderer);
68 |
69 | const uniforms = { globals: this.renderer.globalUniforms };
70 |
71 | this.shader = Shader.from(shaderVert, shaderFrag, uniforms);
72 |
73 | this.simpleShader = Shader.from(shaderVert, shaderSimpleFrag, uniforms);
74 | }
75 |
76 | shader: Shader;
77 | simpleShader: Shader;
78 | quad = new QuadUv();
79 |
80 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
81 | render(ts: any): void
82 | {
83 | const renderer = this.renderer;
84 | const quad = this.quad;
85 |
86 | let vertices = quad.vertices;
87 |
88 | vertices[0] = vertices[6] = (ts._width) * -ts.anchor.x;
89 | vertices[1] = vertices[3] = ts._height * -ts.anchor.y;
90 |
91 | vertices[2] = vertices[4] = (ts._width) * (1.0 - ts.anchor.x);
92 | vertices[5] = vertices[7] = ts._height * (1.0 - ts.anchor.y);
93 |
94 | if (ts.uvRespectAnchor)
95 | {
96 | vertices = quad.uvs;
97 |
98 | vertices[0] = vertices[6] = -ts.anchor.x;
99 | vertices[1] = vertices[3] = -ts.anchor.y;
100 |
101 | vertices[2] = vertices[4] = 1.0 - ts.anchor.x;
102 | vertices[5] = vertices[7] = 1.0 - ts.anchor.y;
103 | }
104 |
105 | quad.invalidate();
106 |
107 | const tex = ts._texture;
108 | const baseTex = tex.baseTexture;
109 | const lt = ts.tileProj.world;
110 | const uv = ts.uvMatrix;
111 | let isSimple = baseTex.isPowerOfTwo
112 | && tex.frame.width === baseTex.width && tex.frame.height === baseTex.height;
113 |
114 | // auto, force repeat wrapMode for big tiling textures
115 | if (isSimple)
116 | {
117 | if (!baseTex._glTextures[(renderer as any).CONTEXT_UID])
118 | {
119 | if (baseTex.wrapMode === WRAP_MODES.CLAMP)
120 | {
121 | baseTex.wrapMode = WRAP_MODES.REPEAT;
122 | }
123 | }
124 | else
125 | {
126 | isSimple = baseTex.wrapMode !== WRAP_MODES.CLAMP;
127 | }
128 | }
129 |
130 | const shader = isSimple ? this.simpleShader : this.shader;
131 |
132 | // changed
133 | tempMat.identity();
134 | tempMat.scale(tex.width, tex.height);
135 | tempMat.prepend(lt);
136 | tempMat.scale(1.0 / ts._width, 1.0 / ts._height);
137 |
138 | tempMat.invert();
139 | if (isSimple)
140 | {
141 | tempMat.prepend(uv.mapCoord);
142 | }
143 | else
144 | {
145 | shader.uniforms.uMapCoord = uv.mapCoord.toArray(true);
146 | shader.uniforms.uClampFrame = uv.uClampFrame;
147 | shader.uniforms.uClampOffset = uv.uClampOffset;
148 | }
149 |
150 | shader.uniforms.uTransform = tempMat.toArray(true);
151 | shader.uniforms.uColor = premultiplyTintToRgba(ts.tint, ts.worldAlpha,
152 | shader.uniforms.uColor, baseTex.premultiplyAlpha);
153 | shader.uniforms.translationMatrix = ts.worldTransform.toArray(true);
154 | shader.uniforms.uSampler = tex;
155 |
156 | renderer.shader.bind(shader, false);
157 | renderer.geometry.bind(quad as any, undefined);// , renderer.shader.getGLShader());
158 |
159 | renderer.state.setBlendMode(correctBlendMode(ts.blendMode, baseTex.premultiplyAlpha));
160 | renderer.geometry.draw(DRAW_MODES.TRIANGLES, 6, 0);
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/proj3d/ObservableEuler.ts:
--------------------------------------------------------------------------------
1 | import { Euler } from './Euler';
2 |
3 | export type IEuler = Euler | ObservableEuler;
4 |
5 | /**
6 | * The Euler angles, order is YZX. Except for projections (camera.lookEuler), its reversed XZY
7 | * @class
8 | * @namespace PIXI.projection
9 | * @param x pitch
10 | * @param y yaw
11 | * @param z roll
12 | * @constructor
13 | */
14 |
15 | export class ObservableEuler
16 | {
17 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
18 | constructor(public cb: any, public scope: any, x?: number, y?: number, z?: number)
19 | {
20 | /**
21 | * @member {number}
22 | * @default 0
23 | */
24 | this._x = x || 0;
25 |
26 | /**
27 | * @member {number}
28 | * @default 0
29 | */
30 | this._y = y || 0;
31 |
32 | /**
33 | * @member {number}
34 | * @default 0
35 | */
36 | this._z = z || 0;
37 |
38 | this.quaternion = new Float64Array(4);
39 | this.quaternion[3] = 1;
40 |
41 | this.update();
42 | }
43 |
44 | _quatUpdateId = -1;
45 | _quatDirtyId = 0;
46 |
47 | quaternion: Float64Array;
48 |
49 | _x: number;
50 | _y: number;
51 | _z: number;
52 | _sign = 1;
53 |
54 | get x(): number
55 | {
56 | return this._x;
57 | }
58 |
59 | set x(value: number)
60 | {
61 | if (this._x !== value)
62 | {
63 | this._x = value;
64 | this._quatDirtyId++;
65 | this.cb.call(this.scope);
66 | }
67 | }
68 |
69 | get y(): number
70 | {
71 | return this._y;
72 | }
73 |
74 | set y(value: number)
75 | {
76 | if (this._y !== value)
77 | {
78 | this._y = value;
79 | this._quatDirtyId++;
80 | this.cb.call(this.scope);
81 | }
82 | }
83 |
84 | get z(): number
85 | {
86 | return this._z;
87 | }
88 |
89 | set z(value: number)
90 | {
91 | if (this._z !== value)
92 | {
93 | this._z = value;
94 | this._quatDirtyId++;
95 | this.cb.call(this.scope);
96 | }
97 | }
98 |
99 | get pitch(): number
100 | {
101 | return this._x;
102 | }
103 |
104 | set pitch(value: number)
105 | {
106 | if (this._x !== value)
107 | {
108 | this._x = value;
109 | this._quatDirtyId++;
110 | this.cb.call(this.scope);
111 | }
112 | }
113 |
114 | get yaw(): number
115 | {
116 | return this._y;
117 | }
118 |
119 | set yaw(value: number)
120 | {
121 | if (this._y !== value)
122 | {
123 | this._y = value;
124 | this._quatDirtyId++;
125 | this.cb.call(this.scope);
126 | }
127 | }
128 |
129 | get roll(): number
130 | {
131 | return this._z;
132 | }
133 |
134 | set roll(value: number)
135 | {
136 | if (this._z !== value)
137 | {
138 | this._z = value;
139 | this._quatDirtyId++;
140 | this.cb.call(this.scope);
141 | }
142 | }
143 |
144 | set(x?: number, y?: number, z?: number): this
145 | {
146 | const _x = x || 0;
147 | const _y = y || 0;
148 | const _z = z || 0;
149 |
150 | if (this._x !== _x || this._y !== _y || this._z !== _z)
151 | {
152 | this._x = _x;
153 | this._y = _y;
154 | this._z = _z;
155 | this._quatDirtyId++;
156 | this.cb.call(this.scope);
157 | }
158 |
159 | return this;
160 | }
161 |
162 | copyFrom(euler: IEuler): this
163 | {
164 | const _x = euler.x;
165 | const _y = euler.y;
166 | const _z = euler.z;
167 |
168 | if (this._x !== _x || this._y !== _y || this._z !== _z)
169 | {
170 | this._x = _x;
171 | this._y = _y;
172 | this._z = _z;
173 | this._quatDirtyId++;
174 | this.cb.call(this.scope);
175 | }
176 |
177 | return this;
178 | }
179 |
180 | copyTo(p: IEuler): IEuler
181 | {
182 | p.set(this._x, this._y, this._z);
183 |
184 | return p;
185 | }
186 |
187 | equals(euler: IEuler): boolean
188 | {
189 | return this._x === euler.x
190 | && this._y === euler.y
191 | && this._z === euler.z;
192 | }
193 |
194 | clone(): Euler
195 | {
196 | return new Euler(this._x, this._y, this._z);
197 | }
198 |
199 | update(): boolean
200 | {
201 | if (this._quatUpdateId === this._quatDirtyId)
202 | {
203 | return false;
204 | }
205 | this._quatUpdateId = this._quatDirtyId;
206 |
207 | const c1 = Math.cos(this._x / 2);
208 | const c2 = Math.cos(this._y / 2);
209 | const c3 = Math.cos(this._z / 2);
210 |
211 | const s = this._sign;
212 | const s1 = s * Math.sin(this._x / 2);
213 | const s2 = s * Math.sin(this._y / 2);
214 | const s3 = s * Math.sin(this._z / 2);
215 |
216 | const q = this.quaternion;
217 |
218 | q[0] = (s1 * c2 * c3) + (c1 * s2 * s3);
219 | q[1] = (c1 * s2 * c3) - (s1 * c2 * s3);
220 | q[2] = (c1 * c2 * s3) + (s1 * s2 * c3);
221 | q[3] = (c1 * c2 * c3) - (s1 * s2 * s3);
222 |
223 | return true;
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/src/proj3d/mesh/Mesh3d2d.ts:
--------------------------------------------------------------------------------
1 | import { Mesh, MeshGeometry, MeshMaterial } from '@pixi/mesh';
2 | import { Geometry, Program, Renderer, State, Texture } from '@pixi/core';
3 | import { Projection3d } from '../Projection3d';
4 | import { IPointData, Matrix } from '@pixi/math';
5 | import { DisplayObject } from '@pixi/display';
6 | import { TRANSFORM_STEP } from '../../base';
7 | import { container3dGetDepth, container3dIsFrontFace, container3dToLocal } from '../Container3d';
8 | import { Euler } from '../Euler';
9 | import { Mesh2d } from '../../proj2d';
10 |
11 | export class Mesh3d2d extends Mesh
12 | {
13 | constructor(geometry: Geometry, shader: MeshMaterial, state: State, drawMode?: number)
14 | {
15 | super(geometry, shader, state, drawMode);
16 | this.proj = new Projection3d(this.transform);
17 | }
18 |
19 | vertexData2d: Float32Array = null;
20 | proj: Projection3d;
21 |
22 | calculateVertices(): void
23 | {
24 | if (this.proj._affine)
25 | {
26 | this.vertexData2d = null;
27 | super.calculateVertices();
28 |
29 | return;
30 | }
31 |
32 | const geometry = this.geometry as any;
33 | const vertices = geometry.buffers[0].data;
34 | const thisAny = this as any;
35 |
36 | if (geometry.vertexDirtyId === thisAny.vertexDirty && thisAny._transformID === thisAny.transform._worldID)
37 | {
38 | return;
39 | }
40 |
41 | thisAny._transformID = thisAny.transform._worldID;
42 |
43 | if (thisAny.vertexData.length !== vertices.length)
44 | {
45 | thisAny.vertexData = new Float32Array(vertices.length);
46 | }
47 | if (!this.vertexData2d || this.vertexData2d.length !== vertices.length * 3 / 2)
48 | {
49 | this.vertexData2d = new Float32Array(vertices.length * 3);
50 | }
51 |
52 | const wt = this.proj.world.mat4;
53 |
54 | const vertexData2d = this.vertexData2d;
55 | const vertexData = thisAny.vertexData;
56 |
57 | for (let i = 0; i < vertexData.length / 2; i++)
58 | {
59 | const x = vertices[(i * 2)];
60 | const y = vertices[(i * 2) + 1];
61 |
62 | const xx = (wt[0] * x) + (wt[4] * y) + wt[12];
63 | const yy = (wt[1] * x) + (wt[5] * y) + wt[13];
64 | const ww = (wt[3] * x) + (wt[7] * y) + wt[15];
65 |
66 | vertexData2d[i * 3] = xx;
67 | vertexData2d[(i * 3) + 1] = yy;
68 | vertexData2d[(i * 3) + 2] = ww;
69 |
70 | vertexData[(i * 2)] = xx / ww;
71 | vertexData[(i * 2) + 1] = yy / ww;
72 | }
73 |
74 | thisAny.vertexDirty = geometry.vertexDirtyId;
75 | }
76 |
77 | get worldTransform(): Matrix
78 | {
79 | return this.proj.affine ? this.transform.worldTransform : this.proj.world as any;
80 | }
81 |
82 | toLocal(position: IPointData, from?: DisplayObject,
83 | point?: T, skipUpdate?: boolean,
84 | step = TRANSFORM_STEP.ALL): T
85 | {
86 | return container3dToLocal.call(this, position, from, point, skipUpdate, step);
87 | }
88 |
89 | isFrontFace(forceUpdate?: boolean): boolean
90 | {
91 | return container3dIsFrontFace.call(this, forceUpdate);
92 | }
93 |
94 | getDepth(forceUpdate?: boolean): boolean
95 | {
96 | return container3dGetDepth.call(this, forceUpdate);
97 | }
98 |
99 | get position3d(): IPointData
100 | {
101 | return this.proj.position;
102 | }
103 | set position3d(value: IPointData)
104 | {
105 | this.proj.position.copyFrom(value);
106 | }
107 | get scale3d(): IPointData
108 | {
109 | return this.proj.scale;
110 | }
111 | set scale3d(value: IPointData)
112 | {
113 | this.proj.scale.copyFrom(value);
114 | }
115 | get euler(): Euler
116 | {
117 | return this.proj.euler;
118 | }
119 | set euler(value: Euler)
120 | {
121 | this.proj.euler.copyFrom(value);
122 | }
123 | get pivot3d(): IPointData
124 | {
125 | return this.proj.pivot;
126 | }
127 | set pivot3d(value: IPointData)
128 | {
129 | this.proj.pivot.copyFrom(value);
130 | }
131 | }
132 |
133 | (Mesh3d2d.prototype as any)._renderDefault = Mesh2d.prototype._renderDefault;
134 |
135 | export class SimpleMesh3d2d extends Mesh3d2d
136 | {
137 | constructor(texture: Texture, vertices?: Float32Array, uvs?: Float32Array,
138 | indices?: Uint16Array, drawMode?: number)
139 | {
140 | super(new MeshGeometry(vertices, uvs, indices),
141 | new MeshMaterial(texture, {
142 | program: Program.from(Mesh2d.defaultVertexShader, Mesh2d.defaultFragmentShader),
143 | pluginName: 'batch2d'
144 | }),
145 | null,
146 | drawMode);
147 |
148 | (this.geometry.getBuffer('aVertexPosition') as any).static = false;
149 | }
150 |
151 | autoUpdate = true;
152 |
153 | get vertices(): Float32Array
154 | {
155 | return this.geometry.getBuffer('aVertexPosition').data as Float32Array;
156 | }
157 | set vertices(value: Float32Array)
158 | {
159 | this.geometry.getBuffer('aVertexPosition').data = value;
160 | }
161 |
162 | protected _render(renderer?: Renderer): void
163 | {
164 | if (this.autoUpdate)
165 | {
166 | this.geometry.getBuffer('aVertexPosition').update();
167 | }
168 |
169 | (super._render as any)(renderer);
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/src/curve/ProjectionSurface.ts:
--------------------------------------------------------------------------------
1 | import { IPointData, Transform } from '@pixi/math';
2 | import { AbstractProjection } from '../base';
3 | import { Surface } from './BaseSurface';
4 | import { BilinearSurface } from './BilinearSurface';
5 | import { Sprite } from '@pixi/sprite';
6 |
7 | const fun = Transform.prototype.updateTransform;
8 |
9 | export interface IWorldTransform
10 | {
11 | apply(pos: IPointData, newPos: IPointData): IPointData;
12 |
13 | // TODO: remove props
14 | applyInverse(pos: IPointData, newPos: IPointData): IPointData;
15 | }
16 |
17 | function transformHack(this: Transform, parentTransform: Transform): IWorldTransform
18 | {
19 | // TODO: pixi 6.1.0 global mixin
20 | const proj = (this as any).proj as ProjectionSurface;
21 |
22 | const pp = (parentTransform as any).proj as ProjectionSurface;
23 | const ta = this as any;
24 |
25 | if (!pp)
26 | {
27 | fun.call(this, parentTransform);
28 | proj._activeProjection = null;
29 |
30 | return;
31 | }
32 |
33 | if (pp._surface)
34 | {
35 | proj._activeProjection = pp;
36 | this.updateLocalTransform();
37 | this.localTransform.copyTo(this.worldTransform);
38 | if (ta._parentID < 0)
39 | {
40 | ++ta._worldID;
41 | }
42 |
43 | return;
44 | }
45 |
46 | fun.call(this, parentTransform);
47 | proj._activeProjection = pp._activeProjection;
48 | }
49 |
50 | export class ProjectionSurface extends AbstractProjection
51 | {
52 | _surface: Surface = null;
53 | _activeProjection: ProjectionSurface = null;
54 |
55 | // eslint-disable-next-line accessor-pairs
56 | set enabled(value: boolean)
57 | {
58 | if (value === this._enabled)
59 | {
60 | return;
61 | }
62 | this._enabled = value;
63 | if (value)
64 | {
65 | this.legacy.updateTransform = transformHack;
66 | (this.legacy as any)._parentID = -1;
67 | }
68 | else
69 | {
70 | this.legacy.updateTransform = Transform.prototype.updateTransform;
71 | (this.legacy as any)._parentID = -1;
72 | }
73 | }
74 |
75 | get surface(): Surface
76 | {
77 | return this._surface;
78 | }
79 |
80 | set surface(value: Surface)
81 | {
82 | if (this._surface === value)
83 | {
84 | return;
85 | }
86 | this._surface = value || null;
87 | (this.legacy as any)._parentID = -1;
88 | }
89 |
90 | applyPartial(pos: IPointData, newPos?: IPointData): IPointData
91 | {
92 | if (this._activeProjection !== null)
93 | {
94 | newPos = this.legacy.worldTransform.apply(pos, newPos);
95 |
96 | return this._activeProjection.surface.apply(newPos, newPos);
97 | }
98 | if (this._surface !== null)
99 | {
100 | return this.surface.apply(pos, newPos);
101 | }
102 |
103 | return this.legacy.worldTransform.apply(pos, newPos);
104 | }
105 |
106 | apply(pos: IPointData, newPos?: IPointData): IPointData
107 | {
108 | if (this._activeProjection !== null)
109 | {
110 | newPos = this.legacy.worldTransform.apply(pos, newPos);
111 | this._activeProjection.surface.apply(newPos, newPos);
112 |
113 | return this._activeProjection.legacy.worldTransform.apply(newPos, newPos);
114 | }
115 | if (this._surface !== null)
116 | {
117 | newPos = this.surface.apply(pos, newPos);
118 |
119 | return this.legacy.worldTransform.apply(newPos, newPos);
120 | }
121 |
122 | return this.legacy.worldTransform.apply(pos, newPos);
123 | }
124 |
125 | applyInverse(pos: IPointData, newPos: IPointData): IPointData
126 | {
127 | if (this._activeProjection !== null)
128 | {
129 | newPos = this._activeProjection.legacy.worldTransform.applyInverse(pos, newPos);
130 | this._activeProjection._surface.applyInverse(newPos, newPos);
131 |
132 | return this.legacy.worldTransform.applyInverse(newPos, newPos);
133 | }
134 | if (this._surface !== null)
135 | {
136 | newPos = this.legacy.worldTransform.applyInverse(pos, newPos);
137 |
138 | return this._surface.applyInverse(newPos, newPos);
139 | }
140 |
141 | return this.legacy.worldTransform.applyInverse(pos, newPos);
142 | }
143 |
144 | mapBilinearSprite(sprite: Sprite, quad: Array): void
145 | {
146 | if (!(this._surface instanceof BilinearSurface))
147 | {
148 | this.surface = new BilinearSurface();
149 | }
150 | (this.surface as BilinearSurface).mapSprite(sprite, quad, this.legacy);
151 | }
152 |
153 | _currentSurfaceID = -1;
154 | _currentLegacyID = -1;
155 | _lastUniforms : any = null;
156 |
157 | clear(): void
158 | {
159 | if (this.surface)
160 | {
161 | this.surface.clear();
162 | }
163 | }
164 |
165 | get uniforms(): any
166 | {
167 | if (this._currentLegacyID === (this.legacy as any)._worldID
168 | && this._currentSurfaceID === this.surface._updateID)
169 | {
170 | return this._lastUniforms;
171 | }
172 |
173 | this._lastUniforms = this._lastUniforms || {};
174 | this._lastUniforms.translationMatrix = this.legacy.worldTransform;
175 | this._surface.fillUniforms(this._lastUniforms);
176 |
177 | return this._lastUniforms;
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/proj2d/Projection2d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-mixed-operators */
2 | import { Matrix2d } from './Matrix2d';
3 | import { IPointData, Matrix, ObservablePoint, Point, Rectangle, Transform } from '@pixi/math';
4 | import { Sprite } from '@pixi/sprite';
5 | import { LinearProjection } from '../base';
6 | import { getIntersectionFactor } from '../utils';
7 |
8 | const t0 = new Point();
9 | const tt = [new Point(), new Point(), new Point(), new Point()];
10 | const tempRect = new Rectangle();
11 | const tempMat = new Matrix2d();
12 |
13 | export class Projection2d extends LinearProjection
14 | {
15 | constructor(legacy: Transform, enable?: boolean)
16 | {
17 | super(legacy, enable);
18 | this.local = new Matrix2d();
19 | this.world = new Matrix2d();
20 | }
21 |
22 | matrix = new Matrix2d();
23 | pivot = new ObservablePoint(this.onChange, this, 0, 0);
24 |
25 | reverseLocalOrder = false;
26 |
27 | onChange(): void
28 | {
29 | const pivot = this.pivot;
30 | const mat3 = this.matrix.mat3;
31 |
32 | mat3[6] = -(pivot._x * mat3[0] + pivot._y * mat3[3]);
33 | mat3[7] = -(pivot._x * mat3[1] + pivot._y * mat3[4]);
34 |
35 | this._projID++;
36 | }
37 |
38 | setAxisX(p: IPointData, factor = 1): void
39 | {
40 | const x = p.x; const
41 | y = p.y;
42 | const d = Math.sqrt(x * x + y * y);
43 | const mat3 = this.matrix.mat3;
44 |
45 | mat3[0] = x / d;
46 | mat3[1] = y / d;
47 | mat3[2] = factor / d;
48 |
49 | this.onChange();
50 | }
51 |
52 | setAxisY(p: IPointData, factor = 1): void
53 | {
54 | const x = p.x; const
55 | y = p.y;
56 | const d = Math.sqrt(x * x + y * y);
57 | const mat3 = this.matrix.mat3;
58 |
59 | mat3[3] = x / d;
60 | mat3[4] = y / d;
61 | mat3[5] = factor / d;
62 | this.onChange();
63 | }
64 |
65 | mapSprite(sprite: Sprite, quad: Array): void
66 | {
67 | const tex = sprite.texture;
68 |
69 | tempRect.x = -sprite.anchor.x * tex.orig.width;
70 | tempRect.y = -sprite.anchor.y * tex.orig.height;
71 | tempRect.width = tex.orig.width;
72 | tempRect.height = tex.orig.height;
73 |
74 | this.mapQuad(tempRect, quad);
75 | }
76 |
77 | mapQuad(rect: Rectangle, p: Array): void
78 | {
79 | // utils.getPositionFromQuad(p, anchor, t0);
80 | tt[0].set(rect.x, rect.y);
81 | tt[1].set(rect.x + rect.width, rect.y);
82 | tt[2].set(rect.x + rect.width, rect.y + rect.height);
83 | tt[3].set(rect.x, rect.y + rect.height);
84 |
85 | let k1 = 1; let k2 = 2;
86 | let k3 = 3;
87 | const f = getIntersectionFactor(p[0], p[2], p[1], p[3], t0);
88 |
89 | if (f !== 0)
90 | {
91 | k1 = 1;
92 | k2 = 3;
93 | k3 = 2;
94 | }
95 | else
96 | {
97 | return;
98 | /* f = utils.getIntersectionFactor(p[0], p[1], p[2], p[3], t0);
99 | if (f > 0) {
100 | k1 = 2;
101 | k2 = 3;
102 | k3 = 1;
103 | } else {
104 | f = utils.getIntersectionFactor(p[0], p[3], p[1], p[2], t0);
105 | if (f > 0) {
106 | // cant find it :(
107 | k1 = 1;
108 | k2 = 2;
109 | k3 = 3;
110 | } else {
111 | return;
112 | }
113 | }*/
114 | }
115 | const d0 = Math.sqrt((p[0].x - t0.x) * (p[0].x - t0.x) + (p[0].y - t0.y) * (p[0].y - t0.y));
116 | const d1 = Math.sqrt((p[k1].x - t0.x) * (p[k1].x - t0.x) + (p[k1].y - t0.y) * (p[k1].y - t0.y));
117 | const d2 = Math.sqrt((p[k2].x - t0.x) * (p[k2].x - t0.x) + (p[k2].y - t0.y) * (p[k2].y - t0.y));
118 | const d3 = Math.sqrt((p[k3].x - t0.x) * (p[k3].x - t0.x) + (p[k3].y - t0.y) * (p[k3].y - t0.y));
119 |
120 | const q0 = (d0 + d3) / d3;
121 | const q1 = (d1 + d2) / d2;
122 | const q2 = (d1 + d2) / d1;
123 |
124 | let mat3 = this.matrix.mat3;
125 |
126 | mat3[0] = tt[0].x * q0;
127 | mat3[1] = tt[0].y * q0;
128 | mat3[2] = q0;
129 | mat3[3] = tt[k1].x * q1;
130 | mat3[4] = tt[k1].y * q1;
131 | mat3[5] = q1;
132 | mat3[6] = tt[k2].x * q2;
133 | mat3[7] = tt[k2].y * q2;
134 | mat3[8] = q2;
135 | this.matrix.invert();
136 |
137 | mat3 = tempMat.mat3;
138 | mat3[0] = p[0].x;
139 | mat3[1] = p[0].y;
140 | mat3[2] = 1;
141 | mat3[3] = p[k1].x;
142 | mat3[4] = p[k1].y;
143 | mat3[5] = 1;
144 | mat3[6] = p[k2].x;
145 | mat3[7] = p[k2].y;
146 | mat3[8] = 1;
147 |
148 | this.matrix.setToMult(tempMat, this.matrix);
149 | this._projID++;
150 | }
151 |
152 | updateLocalTransform(lt: Matrix): void
153 | {
154 | if (this._projID !== 0)
155 | {
156 | if (this.reverseLocalOrder)
157 | {
158 | // tilingSprite inside order
159 | this.local.setToMultLegacy2(this.matrix, lt);
160 | }
161 | else
162 | {
163 | // good order
164 | this.local.setToMultLegacy(lt, this.matrix);
165 | }
166 | }
167 | else
168 | {
169 | this.local.copyFrom(lt);
170 | }
171 | }
172 |
173 | clear(): void
174 | {
175 | super.clear();
176 | this.matrix.identity();
177 | this.pivot.set(0, 0);
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/proj2d/mesh/Mesh2d.ts:
--------------------------------------------------------------------------------
1 | import { Mesh, MeshGeometry, MeshMaterial } from '@pixi/mesh';
2 | import { Geometry, Program, Renderer, State, Texture } from '@pixi/core';
3 | import { Projection2d } from '../Projection2d';
4 | import { IPointData, Matrix } from '@pixi/math';
5 | import { DisplayObject } from '@pixi/display';
6 | import { TRANSFORM_STEP } from '../../base';
7 | import { container2dToLocal } from '../Container2d';
8 |
9 | export class Mesh2d extends Mesh
10 | {
11 | static defaultVertexShader
12 | = `precision highp float;
13 | attribute vec2 aVertexPosition;
14 | attribute vec2 aTextureCoord;
15 |
16 | uniform mat3 projectionMatrix;
17 | uniform mat3 translationMatrix;
18 | uniform mat3 uTextureMatrix;
19 |
20 | varying vec2 vTextureCoord;
21 |
22 | void main(void)
23 | {
24 | gl_Position.xyw = projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0);
25 | gl_Position.z = 0.0;
26 |
27 | vTextureCoord = (uTextureMatrix * vec3(aTextureCoord, 1.0)).xy;
28 | }
29 | `;
30 | static defaultFragmentShader = `
31 | varying vec2 vTextureCoord;
32 | uniform vec4 uColor;
33 |
34 | uniform sampler2D uSampler;
35 |
36 | void main(void)
37 | {
38 | gl_FragColor = texture2D(uSampler, vTextureCoord) * uColor;
39 | }`;
40 | constructor(geometry: Geometry, shader: MeshMaterial, state: State, drawMode?: number)
41 | {
42 | super(geometry, shader, state, drawMode);
43 | this.proj = new Projection2d(this.transform);
44 | }
45 |
46 | vertexData2d: Float32Array = null;
47 | proj: Projection2d;
48 |
49 | calculateVertices(): void
50 | {
51 | if (this.proj._affine)
52 | {
53 | this.vertexData2d = null;
54 | super.calculateVertices();
55 |
56 | return;
57 | }
58 |
59 | const geometry = this.geometry as any;
60 | const vertices = geometry.buffers[0].data;
61 | const thisAny = this as any;
62 |
63 | if (geometry.vertexDirtyId === thisAny.vertexDirty && thisAny._transformID === thisAny.transform._worldID)
64 | {
65 | return;
66 | }
67 |
68 | thisAny._transformID = thisAny.transform._worldID;
69 |
70 | if (thisAny.vertexData.length !== vertices.length)
71 | {
72 | thisAny.vertexData = new Float32Array(vertices.length);
73 | }
74 |
75 | if (!this.vertexData2d || this.vertexData2d.length !== vertices.length * 3 / 2)
76 | {
77 | this.vertexData2d = new Float32Array(vertices.length * 3);
78 | }
79 |
80 | const wt = this.proj.world.mat3;
81 |
82 | const vertexData2d = this.vertexData2d;
83 | const vertexData = thisAny.vertexData;
84 |
85 | for (let i = 0; i < vertexData.length / 2; i++)
86 | {
87 | const x = vertices[(i * 2)];
88 | const y = vertices[(i * 2) + 1];
89 |
90 | const xx = (wt[0] * x) + (wt[3] * y) + wt[6];
91 | const yy = (wt[1] * x) + (wt[4] * y) + wt[7];
92 | const ww = (wt[2] * x) + (wt[5] * y) + wt[8];
93 |
94 | vertexData2d[i * 3] = xx;
95 | vertexData2d[(i * 3) + 1] = yy;
96 | vertexData2d[(i * 3) + 2] = ww;
97 |
98 | vertexData[(i * 2)] = xx / ww;
99 | vertexData[(i * 2) + 1] = yy / ww;
100 | }
101 |
102 | thisAny.vertexDirty = geometry.vertexDirtyId;
103 | }
104 |
105 | _renderDefault(renderer: Renderer): void
106 | {
107 | const shader = this.shader as MeshMaterial;
108 |
109 | shader.alpha = this.worldAlpha;
110 | if (shader.update)
111 | {
112 | shader.update();
113 | }
114 |
115 | renderer.batch.flush();
116 |
117 | if ((shader as any).program.uniformData?.translationMatrix)
118 | {
119 | shader.uniforms.translationMatrix = this.worldTransform.toArray(true);
120 | }
121 |
122 | // bind and sync uniforms..
123 | renderer.shader.bind(shader, false);
124 |
125 | // set state..
126 | renderer.state.set(this.state);
127 |
128 | // bind the geometry...
129 | renderer.geometry.bind(this.geometry, shader);
130 |
131 | // then render it
132 | renderer.geometry.draw(this.drawMode, this.size, this.start, (this.geometry as any).instanceCount);
133 | }
134 |
135 | toLocal(position: IPointData, from?: DisplayObject,
136 | point?: T, skipUpdate?: boolean,
137 | step = TRANSFORM_STEP.ALL): T
138 | {
139 | return container2dToLocal.call(this, position, from, point, skipUpdate, step);
140 | }
141 |
142 | get worldTransform(): Matrix
143 | {
144 | return this.proj.affine ? this.transform.worldTransform : this.proj.world as any;
145 | }
146 | }
147 |
148 | export class SimpleMesh2d extends Mesh2d
149 | {
150 | constructor(texture: Texture, vertices?: Float32Array, uvs?: Float32Array,
151 | indices?: Uint16Array, drawMode?: number)
152 | {
153 | super(new MeshGeometry(vertices, uvs, indices),
154 | new MeshMaterial(texture, {
155 | program: Program.from(Mesh2d.defaultVertexShader, Mesh2d.defaultFragmentShader),
156 | pluginName: 'batch2d'
157 | }),
158 | null,
159 | drawMode);
160 |
161 | (this.geometry.getBuffer('aVertexPosition') as any).static = false;
162 | }
163 |
164 | autoUpdate = true;
165 |
166 | get vertices(): Float32Array
167 | {
168 | return this.geometry.getBuffer('aVertexPosition').data as Float32Array;
169 | }
170 | set vertices(value: Float32Array)
171 | {
172 | this.geometry.getBuffer('aVertexPosition').data = value;
173 | }
174 |
175 | protected _render(renderer?: Renderer): void
176 | {
177 | if (this.autoUpdate)
178 | {
179 | this.geometry.getBuffer('aVertexPosition').update();
180 | }
181 |
182 | (super._render as any)(renderer);
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/src/curve/sprites/Sprite2s.ts:
--------------------------------------------------------------------------------
1 | import { Matrix } from '@pixi/math';
2 | import { Texture, TextureMatrix } from '@pixi/core';
3 | import { Sprite } from '@pixi/sprite';
4 | import { ProjectionSurface } from '../ProjectionSurface';
5 |
6 | export class Sprite2s extends Sprite
7 | {
8 | constructor(texture: Texture)
9 | {
10 | super(texture);
11 | this.proj = new ProjectionSurface(this.transform);
12 | this.pluginName = 'batch_bilinear';
13 | }
14 |
15 | proj: ProjectionSurface;
16 | aTrans = new Matrix();
17 |
18 | _calculateBounds(): void
19 | {
20 | this.calculateTrimmedVertices();
21 | this._bounds.addQuad((this as any).vertexTrimmedData as any);
22 | }
23 |
24 | calculateVertices(): void
25 | {
26 | const wid = (this.transform as any)._worldID;
27 | const tuid = (this._texture as any)._updateID;
28 | const thisAny = this as any;
29 |
30 | if (thisAny._transformID === wid && this._textureID === tuid)
31 | {
32 | return;
33 | }
34 |
35 | thisAny._transformID = wid;
36 | this._textureID = tuid;
37 |
38 | const texture = this._texture;
39 | const vertexData = this.vertexData;
40 | const trim = texture.trim;
41 | const orig = texture.orig;
42 | const anchor = this._anchor;
43 |
44 | let w0: number;
45 | let w1: number;
46 | let h0: number;
47 | let h1: number;
48 |
49 | if (trim)
50 | {
51 | w1 = trim.x - (anchor._x * orig.width);
52 | w0 = w1 + trim.width;
53 |
54 | h1 = trim.y - (anchor._y * orig.height);
55 | h0 = h1 + trim.height;
56 | }
57 | else
58 | {
59 | w1 = -anchor._x * orig.width;
60 | w0 = w1 + orig.width;
61 |
62 | h1 = -anchor._y * orig.height;
63 | h0 = h1 + orig.height;
64 | }
65 |
66 | if (this.proj._surface)
67 | {
68 | vertexData[0] = w1;
69 | vertexData[1] = h1;
70 | vertexData[2] = w0;
71 | vertexData[3] = h1;
72 | vertexData[4] = w0;
73 | vertexData[5] = h0;
74 | vertexData[6] = w1;
75 | vertexData[7] = h0;
76 | this.proj._surface.boundsQuad(vertexData, vertexData);
77 | }
78 | else
79 | {
80 | const wt = this.transform.worldTransform;
81 | const a = wt.a;
82 | const b = wt.b;
83 | const c = wt.c;
84 | const d = wt.d;
85 | const tx = wt.tx;
86 | const ty = wt.ty;
87 |
88 | vertexData[0] = (a * w1) + (c * h1) + tx;
89 | vertexData[1] = (d * h1) + (b * w1) + ty;
90 | vertexData[2] = (a * w0) + (c * h1) + tx;
91 | vertexData[3] = (d * h1) + (b * w0) + ty;
92 | vertexData[4] = (a * w0) + (c * h0) + tx;
93 | vertexData[5] = (d * h0) + (b * w0) + ty;
94 | vertexData[6] = (a * w1) + (c * h0) + tx;
95 | vertexData[7] = (d * h0) + (b * w1) + ty;
96 | if (this.proj._activeProjection)
97 | {
98 | this.proj._activeProjection.surface.boundsQuad(vertexData, vertexData);
99 | }
100 | }
101 |
102 | if (!texture.uvMatrix)
103 | {
104 | texture.uvMatrix = new TextureMatrix(texture);
105 | }
106 | texture.uvMatrix.update();
107 |
108 | const aTrans = this.aTrans;
109 |
110 | aTrans.set(orig.width, 0, 0, orig.height, w1, h1);
111 | if (this.proj._surface === null)
112 | {
113 | aTrans.prepend(this.transform.worldTransform);
114 | }
115 | aTrans.invert();
116 | aTrans.prepend((texture.uvMatrix as any).mapCoord);
117 | }
118 |
119 | calculateTrimmedVertices(): void
120 | {
121 | const wid = (this.transform as any)._worldID;
122 | const tuid = (this._texture as any)._updateID;
123 | const thisAny = this as any;
124 |
125 | if (!thisAny.vertexTrimmedData)
126 | {
127 | thisAny.vertexTrimmedData = new Float32Array(8);
128 | }
129 | else if (thisAny._transformTrimmedID === wid && this._textureTrimmedID === tuid)
130 | {
131 | return;
132 | }
133 |
134 | thisAny._transformTrimmedID = wid;
135 | this._textureTrimmedID = tuid;
136 |
137 | // lets do some special trim code!
138 | const texture = this._texture;
139 | const vertexData = thisAny.vertexTrimmedData;
140 | const orig = texture.orig;
141 | const anchor = this._anchor;
142 |
143 | // lets calculate the new untrimmed bounds..
144 |
145 | const w1 = -anchor._x * orig.width;
146 | const w0 = w1 + orig.width;
147 |
148 | const h1 = -anchor._y * orig.height;
149 | const h0 = h1 + orig.height;
150 |
151 | // TODO: take rotations into account! form temporary bounds
152 |
153 | if (this.proj._surface)
154 | {
155 | vertexData[0] = w1;
156 | vertexData[1] = h1;
157 | vertexData[2] = w0;
158 | vertexData[3] = h1;
159 | vertexData[4] = w0;
160 | vertexData[5] = h0;
161 | vertexData[6] = w1;
162 | vertexData[7] = h0;
163 | this.proj._surface.boundsQuad(vertexData, vertexData, this.transform.worldTransform);
164 | }
165 | else
166 | {
167 | const wt = this.transform.worldTransform;
168 | const a = wt.a;
169 | const b = wt.b;
170 | const c = wt.c;
171 | const d = wt.d;
172 | const tx = wt.tx;
173 | const ty = wt.ty;
174 |
175 | vertexData[0] = (a * w1) + (c * h1) + tx;
176 | vertexData[1] = (d * h1) + (b * w1) + ty;
177 | vertexData[2] = (a * w0) + (c * h1) + tx;
178 | vertexData[3] = (d * h1) + (b * w0) + ty;
179 | vertexData[4] = (a * w0) + (c * h0) + tx;
180 | vertexData[5] = (d * h0) + (b * w0) + ty;
181 | vertexData[6] = (a * w1) + (c * h0) + tx;
182 | vertexData[7] = (d * h0) + (b * w1) + ty;
183 | if (this.proj._activeProjection)
184 | {
185 | this.proj._activeProjection.surface.boundsQuad(vertexData, vertexData,
186 | this.proj._activeProjection.legacy.worldTransform);
187 | }
188 | }
189 | }
190 |
191 | get worldTransform(): Matrix
192 | {
193 | return this.proj as any;
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/proj2d/sprites/Sprite2d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-mixed-operators */
2 | import { Sprite } from '@pixi/sprite';
3 | import { Texture } from '@pixi/core';
4 | import { Projection2d } from '../Projection2d';
5 | import { IPointData, Matrix, Point } from '@pixi/math';
6 | import { DisplayObject } from '@pixi/display';
7 | import { TRANSFORM_STEP } from '../../base';
8 | import { container2dToLocal } from '../Container2d';
9 |
10 | export class Sprite2d extends Sprite
11 | {
12 | constructor(texture: Texture)
13 | {
14 | super(texture);
15 | this.proj = new Projection2d(this.transform);
16 | this.pluginName = 'batch2d';
17 | }
18 |
19 | vertexData2d: Float32Array = null;
20 | proj: Projection2d;
21 |
22 | _calculateBounds(): void
23 | {
24 | this.calculateTrimmedVertices();
25 | this._bounds.addQuad((this as any).vertexTrimmedData);
26 | }
27 |
28 | calculateVertices(): void
29 | {
30 | const texture = this._texture;
31 | const thisAny = this as any;
32 |
33 | if (this.proj._affine)
34 | {
35 | this.vertexData2d = null;
36 | super.calculateVertices();
37 |
38 | return;
39 | }
40 | if (!this.vertexData2d)
41 | {
42 | this.vertexData2d = new Float32Array(12);
43 | }
44 |
45 | const wid = (this.transform as any)._worldID;
46 | const tuid = (texture as any)._updateID;
47 |
48 | if (thisAny._transformID === wid && this._textureID === tuid)
49 | {
50 | return;
51 | }
52 | // update texture UV here, because base texture can be changed without calling `_onTextureUpdate`
53 | if (this._textureID !== tuid)
54 | {
55 | (this as any).uvs = (texture as any)._uvs.uvsFloat32;
56 | }
57 |
58 | thisAny._transformID = wid;
59 | this._textureID = tuid;
60 |
61 | const wt = this.proj.world.mat3;
62 | const vertexData2d = this.vertexData2d;
63 | const vertexData = this.vertexData;
64 | const trim = texture.trim;
65 | const orig = texture.orig;
66 | const anchor = this._anchor;
67 |
68 | let w0: number;
69 | let w1: number;
70 | let h0: number;
71 | let h1: number;
72 |
73 | if (trim)
74 | {
75 | w1 = trim.x - (anchor._x * orig.width);
76 | w0 = w1 + trim.width;
77 |
78 | h1 = trim.y - (anchor._y * orig.height);
79 | h0 = h1 + trim.height;
80 | }
81 | else
82 | {
83 | w1 = -anchor._x * orig.width;
84 | w0 = w1 + orig.width;
85 |
86 | h1 = -anchor._y * orig.height;
87 | h0 = h1 + orig.height;
88 | }
89 |
90 | vertexData2d[0] = (wt[0] * w1) + (wt[3] * h1) + wt[6];
91 | vertexData2d[1] = (wt[1] * w1) + (wt[4] * h1) + wt[7];
92 | vertexData2d[2] = (wt[2] * w1) + (wt[5] * h1) + wt[8];
93 |
94 | vertexData2d[3] = (wt[0] * w0) + (wt[3] * h1) + wt[6];
95 | vertexData2d[4] = (wt[1] * w0) + (wt[4] * h1) + wt[7];
96 | vertexData2d[5] = (wt[2] * w0) + (wt[5] * h1) + wt[8];
97 |
98 | vertexData2d[6] = (wt[0] * w0) + (wt[3] * h0) + wt[6];
99 | vertexData2d[7] = (wt[1] * w0) + (wt[4] * h0) + wt[7];
100 | vertexData2d[8] = (wt[2] * w0) + (wt[5] * h0) + wt[8];
101 |
102 | vertexData2d[9] = (wt[0] * w1) + (wt[3] * h0) + wt[6];
103 | vertexData2d[10] = (wt[1] * w1) + (wt[4] * h0) + wt[7];
104 | vertexData2d[11] = (wt[2] * w1) + (wt[5] * h0) + wt[8];
105 |
106 | vertexData[0] = vertexData2d[0] / vertexData2d[2];
107 | vertexData[1] = vertexData2d[1] / vertexData2d[2];
108 |
109 | vertexData[2] = vertexData2d[3] / vertexData2d[5];
110 | vertexData[3] = vertexData2d[4] / vertexData2d[5];
111 |
112 | vertexData[4] = vertexData2d[6] / vertexData2d[8];
113 | vertexData[5] = vertexData2d[7] / vertexData2d[8];
114 |
115 | vertexData[6] = vertexData2d[9] / vertexData2d[11];
116 | vertexData[7] = vertexData2d[10] / vertexData2d[11];
117 | }
118 |
119 | calculateTrimmedVertices(): void
120 | {
121 | if (this.proj._affine)
122 | {
123 | super.calculateTrimmedVertices();
124 |
125 | return;
126 | }
127 |
128 | const wid = (this.transform as any)._worldID;
129 | const tuid = (this._texture as any)._updateID;
130 | const thisAny = this as any;
131 |
132 | if (!thisAny.vertexTrimmedData)
133 | {
134 | thisAny.vertexTrimmedData = new Float32Array(8);
135 | }
136 | else if (thisAny._transformTrimmedID === wid && this._textureTrimmedID === tuid)
137 | {
138 | return;
139 | }
140 |
141 | thisAny._transformTrimmedID = wid;
142 | this._textureTrimmedID = tuid;
143 |
144 | // lets do some special trim code!
145 | const texture = this._texture;
146 | const vertexData = thisAny.vertexTrimmedData;
147 | const orig = texture.orig;
148 | const w = (this as any).tileProj ? this._width : orig.width;
149 | const h = (this as any).tileProj ? this._height : orig.height;
150 | const anchor = this._anchor;
151 |
152 | // lets calculate the new untrimmed bounds..
153 | const wt = this.proj.world.mat3;
154 |
155 | const w1 = -anchor._x * w;
156 | const w0 = w1 + w;
157 |
158 | const h1 = -anchor._y * h;
159 | const h0 = h1 + h;
160 |
161 | let z = 1.0 / (wt[2] * w1 + wt[5] * h1 + wt[8]);
162 |
163 | vertexData[0] = z * ((wt[0] * w1) + (wt[3] * h1) + wt[6]);
164 | vertexData[1] = z * ((wt[1] * w1) + (wt[4] * h1) + wt[7]);
165 |
166 | z = 1.0 / (wt[2] * w0 + wt[5] * h1 + wt[8]);
167 | vertexData[2] = z * ((wt[0] * w0) + (wt[3] * h1) + wt[6]);
168 | vertexData[3] = z * ((wt[1] * w0) + (wt[4] * h1) + wt[7]);
169 |
170 | z = 1.0 / (wt[2] * w0 + wt[5] * h0 + wt[8]);
171 | vertexData[4] = z * ((wt[0] * w0) + (wt[3] * h0) + wt[6]);
172 | vertexData[5] = z * ((wt[1] * w0) + (wt[4] * h0) + wt[7]);
173 |
174 | z = 1.0 / (wt[2] * w1 + wt[5] * h0 + wt[8]);
175 | vertexData[6] = z * ((wt[0] * w1) + (wt[3] * h0) + wt[6]);
176 | vertexData[7] = z * ((wt[1] * w1) + (wt[4] * h0) + wt[7]);
177 | }
178 |
179 | toLocal(position: IPointData, from?: DisplayObject, point?: P, skipUpdate?: boolean,
180 | step = TRANSFORM_STEP.ALL): P
181 | {
182 | return container2dToLocal.call(this, position, from, point, skipUpdate, step);
183 | }
184 |
185 | get worldTransform(): Matrix
186 | {
187 | return this.proj.affine ? this.transform.worldTransform : this.proj.world as any;
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/src/curve/SpriteBilinearRenderer.ts:
--------------------------------------------------------------------------------
1 | import { BatchShaderGenerator, Buffer, Color, ExtensionType, Geometry, Renderer, ViewableBuffer } from '@pixi/core';
2 | import { TYPES } from '@pixi/constants';
3 | import { Sprite } from '@pixi/sprite';
4 | import { Sprite2s } from './sprites/Sprite2s';
5 | import { Matrix } from '@pixi/math';
6 | import { UniformBatchRenderer } from '../base';
7 |
8 | const shaderVert = `precision highp float;
9 | attribute vec2 aVertexPosition;
10 | attribute vec3 aTrans1;
11 | attribute vec3 aTrans2;
12 | attribute vec2 aSamplerSize;
13 | attribute vec4 aFrame;
14 | attribute vec4 aColor;
15 | attribute float aTextureId;
16 |
17 | uniform mat3 projectionMatrix;
18 | uniform mat3 translationMatrix;
19 |
20 | varying vec2 vertexPosition;
21 | varying vec3 vTrans1;
22 | varying vec3 vTrans2;
23 | varying vec2 vSamplerSize;
24 | varying vec4 vFrame;
25 | varying vec4 vColor;
26 | varying float vTextureId;
27 |
28 | void main(void){
29 | gl_Position.xyw = projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0);
30 | gl_Position.z = 0.0;
31 |
32 | vertexPosition = aVertexPosition;
33 | vTrans1 = aTrans1;
34 | vTrans2 = aTrans2;
35 | vTextureId = aTextureId;
36 | vColor = aColor;
37 | vSamplerSize = aSamplerSize;
38 | vFrame = aFrame;
39 | }
40 | `;
41 |
42 | const shaderFrag = `precision highp float;
43 | varying vec2 vertexPosition;
44 | varying vec3 vTrans1;
45 | varying vec3 vTrans2;
46 | varying vec2 vSamplerSize;
47 | varying vec4 vFrame;
48 | varying vec4 vColor;
49 | varying float vTextureId;
50 |
51 | uniform sampler2D uSamplers[%count%];
52 | uniform vec4 distortion;
53 |
54 | void main(void){
55 | vec2 surface;
56 | vec2 surface2;
57 |
58 | float vx = vertexPosition.x;
59 | float vy = vertexPosition.y;
60 | float dx = distortion.x;
61 | float dy = distortion.y;
62 | float revx = distortion.z;
63 | float revy = distortion.w;
64 |
65 | if (distortion.x == 0.0) {
66 | surface.x = vx;
67 | surface.y = vy / (1.0 + dy * vx);
68 | surface2 = surface;
69 | } else
70 | if (distortion.y == 0.0) {
71 | surface.y = vy;
72 | surface.x = vx / (1.0 + dx * vy);
73 | surface2 = surface;
74 | } else {
75 | float c = vy * dx - vx * dy;
76 | float b = (c + 1.0) * 0.5;
77 | float b2 = (-c + 1.0) * 0.5;
78 | float d = b * b + vx * dy;
79 | if (d < -0.00001) {
80 | discard;
81 | }
82 | d = sqrt(max(d, 0.0));
83 | surface.x = (- b + d) * revy;
84 | surface2.x = (- b - d) * revy;
85 | surface.y = (- b2 + d) * revx;
86 | surface2.y = (- b2 - d) * revx;
87 | }
88 |
89 | vec2 uv;
90 | uv.x = vTrans1.x * surface.x + vTrans1.y * surface.y + vTrans1.z;
91 | uv.y = vTrans2.x * surface.x + vTrans2.y * surface.y + vTrans2.z;
92 |
93 | vec2 pixels = uv * vSamplerSize;
94 |
95 | if (pixels.x < vFrame.x || pixels.x > vFrame.z ||
96 | pixels.y < vFrame.y || pixels.y > vFrame.w) {
97 | uv.x = vTrans1.x * surface2.x + vTrans1.y * surface2.y + vTrans1.z;
98 | uv.y = vTrans2.x * surface2.x + vTrans2.y * surface2.y + vTrans2.z;
99 | pixels = uv * vSamplerSize;
100 |
101 | if (pixels.x < vFrame.x || pixels.x > vFrame.z ||
102 | pixels.y < vFrame.y || pixels.y > vFrame.w) {
103 | discard;
104 | }
105 | }
106 |
107 | vec4 edge;
108 | edge.xy = clamp(pixels - vFrame.xy + 0.5, vec2(0.0, 0.0), vec2(1.0, 1.0));
109 | edge.zw = clamp(vFrame.zw - pixels + 0.5, vec2(0.0, 0.0), vec2(1.0, 1.0));
110 |
111 | float alpha = 1.0; //edge.x * edge.y * edge.z * edge.w;
112 | vec4 rColor = vColor * alpha;
113 |
114 | float textureId = floor(vTextureId+0.5);
115 | vec2 vTextureCoord = uv;
116 | vec4 color;
117 | %forloop%
118 | gl_FragColor = color * rColor;
119 | }`;
120 |
121 | export class BatchBilinearGeometry extends Geometry
122 | {
123 | _buffer: Buffer;
124 | _indexBuffer : Buffer;
125 |
126 | constructor(_static = false)
127 | {
128 | super();
129 |
130 | this._buffer = new Buffer(null, _static, false);
131 |
132 | this._indexBuffer = new Buffer(null, _static, true);
133 |
134 | this.addAttribute('aVertexPosition', this._buffer, 2, false, TYPES.FLOAT)
135 | .addAttribute('aTrans1', this._buffer, 3, false, TYPES.FLOAT)
136 | .addAttribute('aTrans2', this._buffer, 3, false, TYPES.FLOAT)
137 | .addAttribute('aSamplerSize', this._buffer, 2, false, TYPES.FLOAT)
138 | .addAttribute('aFrame', this._buffer, 4, false, TYPES.FLOAT)
139 | .addAttribute('aColor', this._buffer, 4, true, TYPES.UNSIGNED_BYTE)
140 | .addAttribute('aTextureId', this._buffer, 1, true, TYPES.FLOAT)
141 | .addIndex(this._indexBuffer);
142 | }
143 | }
144 |
145 | export class BatchBilinearRenderer extends UniformBatchRenderer
146 | {
147 | constructor(renderer: Renderer)
148 | {
149 | super(renderer);
150 | this.vertexSize = 16;
151 | this.geometryClass = BatchBilinearGeometry;
152 | }
153 |
154 | static extension = {
155 | name: 'batch_bilinear',
156 | type: ExtensionType.RendererPlugin
157 | };
158 |
159 | setShaderGenerator()
160 | {
161 | this.shaderGenerator = new BatchShaderGenerator(
162 | shaderVert,
163 | shaderFrag
164 | );
165 | }
166 |
167 | defUniforms = {
168 | translationMatrix: new Matrix(),
169 | distortion: new Float32Array([0, 0, Infinity, Infinity])
170 | };
171 | size = 1000;
172 | forceMaxTextures = 1;
173 |
174 | getUniforms(sprite: Sprite)
175 | {
176 | const { proj } = sprite as Sprite2s;
177 |
178 | if (proj.surface !== null)
179 | {
180 | return proj.uniforms;
181 | }
182 | if (proj._activeProjection !== null)
183 | {
184 | return proj._activeProjection.uniforms;
185 | }
186 |
187 | return this.defUniforms;
188 | }
189 |
190 | // eslint-disable-next-line max-len
191 | packInterleavedGeometry(element: any, attributeBuffer: ViewableBuffer, indexBuffer: Uint16Array, aIndex: number, iIndex: number)
192 | {
193 | const {
194 | uint32View,
195 | float32View,
196 | } = attributeBuffer;
197 | const p = aIndex / this.vertexSize;
198 | const indices = element.indices;
199 | const vertexData = element.vertexData;
200 | const tex = element._texture;
201 | const frame = tex._frame;
202 | const aTrans = element.aTrans;
203 | const { _batchLocation, realWidth, realHeight, resolution } = element._texture.baseTexture;
204 |
205 | const alpha = Math.min(element.worldAlpha, 1.0);
206 | const argb = Color.shared
207 | .setValue(element._tintRGB)
208 | .toPremultiplied(alpha);
209 |
210 | for (let i = 0; i < vertexData.length; i += 2)
211 | {
212 | float32View[aIndex] = vertexData[i];
213 | float32View[aIndex + 1] = vertexData[i + 1];
214 |
215 | float32View[aIndex + 2] = aTrans.a;
216 | float32View[aIndex + 3] = aTrans.c;
217 | float32View[aIndex + 4] = aTrans.tx;
218 | float32View[aIndex + 5] = aTrans.b;
219 | float32View[aIndex + 6] = aTrans.d;
220 | float32View[aIndex + 7] = aTrans.ty;
221 |
222 | float32View[aIndex + 8] = realWidth;
223 | float32View[aIndex + 9] = realHeight;
224 | float32View[aIndex + 10] = frame.x * resolution;
225 | float32View[aIndex + 11] = frame.y * resolution;
226 | float32View[aIndex + 12] = (frame.x + frame.width) * resolution;
227 | float32View[aIndex + 13] = (frame.y + frame.height) * resolution;
228 |
229 | uint32View[aIndex + 14] = argb;
230 | float32View[aIndex + 15] = _batchLocation;
231 | aIndex += 16;
232 | }
233 |
234 | for (let i = 0; i < indices.length; i++)
235 | {
236 | indexBuffer[iIndex++] = p + indices[i];
237 | }
238 | }
239 | }
240 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pixi-projection
2 |
3 | [](https://badge.fury.io/js/pixi-projection)
4 |
5 | Collection of projections, both 2d and 3d.
6 |
7 | To-do:
8 |
9 | - Docs
10 | - Graphics support
11 |
12 | ## Compatibility
13 |
14 | Works with PixiJS v6. Compatibility with v5 is not guaranteed.
15 |
16 | For v4 please see [v4.x branch](https://github.com/pixijs/pixi-projection/tree/v4.x), npm version `0.2.8`
17 |
18 | For v5.1 please use npm version `0.3.5`
19 |
20 | For >= v5.2 please see [v5.x branch](https://github.com/pixijs/pixi-projection/tree/v5.x), npm version `0.3.15`
21 |
22 | For >= v6 please see [v6.x branch](https://github.com/pixijs/pixi-projection/tree/v6.x), npm version `0.4.3`
23 |
24 | It even works with CanvasRenderer, though results can be strange.
25 |
26 | ## Examples
27 |
28 | 3d Projection (Yummy!)
29 |
30 | [Cards](http://pixijs.github.io/examples/#/plugin-projection/cards.js)
31 |
32 | [Runner](http://pixijs.github.io/examples/#/plugin-projection/runner.js)
33 |
34 | Projective sprites: Container2d, Sprite2d, Text2d
35 |
36 | [Two-point projection](http://pixijs.github.io/examples/#/plugin-projection/basic.js)
37 |
38 | [One-point with return to affine](http://pixijs.github.io/examples/#/plugin-projection/plane.js)
39 |
40 | [Projective transform of quad](http://pixijs.github.io/examples/#/plugin-projection/quad-homo.js)
41 |
42 | ## Bilinear projection
43 |
44 | There are many ways to define projections even when we use only 2 dimensions.
45 |
46 | Surface sprites: Container2s, Sprite2s, Text2s for now only bilinear.
47 |
48 | [Bilinear transform of quad](http://pixijs.github.io/examples/#/plugin-projection/quad-bi.js)
49 |
50 | ## Usage
51 |
52 | ### Special classes
53 |
54 | For every projective way, there are corresponding classes:
55 |
56 | * Container2d, Sprite2d, Text2d, TilingSprite2d, Mesh2d, Spine2d
57 | * Sprite3d, Text3d, Mesh3d2d, Spine3d, Camera3d
58 | * Container2s, Sprite2s **WORK IN PROGRESS*
59 |
60 | We dont support Graphics yet :(
61 |
62 | ### Conversion of regular pixi objects
63 |
64 | Bear in mind that if you dont use at least one class from `pixi-projection`, it might be tree-shaken away.
65 |
66 | Here's how to use regular pixi projects to `3d` projection:
67 |
68 | ```js
69 | import {Sprite, Container} from 'pixi.js';
70 | var sprite = new Sprite();
71 | sprite.convertTo3d();
72 | sprite.position3d.set(0, 0, 1); //available now!
73 |
74 | var container = new Container();
75 | container.convertTo3d();
76 | sprite.position3d.set(0, 0, 1); //available now!
77 | ```
78 |
79 | You can also convert whole subtree:
80 |
81 | ```js
82 | import {Container} from 'pixi.js';
83 | var tree = new Container();
84 | var child = new Container();
85 | tree.addChild(child);
86 | tree.convertSubtreeTo2d(tree);
87 | child.position3d.set(0, 0, 1); //available now!
88 | ```
89 |
90 | (`2d` projection in this example)
91 |
92 | ### 3D transforms
93 |
94 | The most useful thing is 3D transforms.
95 |
96 | It all starts from a camera, dont use 3d elements outside of it - it doesn't make sense.
97 |
98 | You can create several cameras if you want each element to have its own perspective parameters.
99 |
100 | ```js
101 | import {Camera3d} from 'pixi-projection';
102 | var camera = new Camera3d();
103 | camera.setPlanes(400, 10, 10000, false); // true if you want orthographics projection
104 | // I assume you have an app or renderer already
105 | camera.position.set(app.screen.width / 2, app.screen.height / 2);
106 | ```
107 |
108 | In this case, 400 is focus distance. If the width of the screen is 800, that means 90 degrees horizontal FOV.
109 | Everything that's behind `z < -390` will be cut by near plane, everything that's too far away `z > 9600` will be cut too.
110 |
111 | We position the camera at the center of the screen, so an element with `position3d=(0,0,0)` will appear right in the center.
112 | However, the camera can look at something else - a character, or just the point with same coords as center of the screen.
113 |
114 | ```js
115 | camera.position3d.set(app.screen.width/2, app.screen.height/2);
116 | ```
117 |
118 | With this snippet, every element in the camera that does not use extra 3d fields (`z`, `euler`) will appear exactly like in pixi stage.
119 | That's how awesome our Camera implementation is!
120 |
121 | Camera transform differs from other elements:
122 |
123 | ```js
124 | //camera follows player
125 | camera.position3d.copy(player.position3d);
126 | // player is two times smaller now
127 | player.scale3d.set(0.5);
128 | // but camera follows it too, now everything except player is two times bigger on screen :)
129 | camera.scale3d.set(0.5);
130 | ```
131 |
132 | Containers and Sprites have extra fields for positioning inside 3d space.
133 |
134 | PixiJS gives only `position`, `scale`, `rotation`, `pivot`,
135 | and projection plugin adds `position3d`, `euler`, `scale3d`, `pivot3d`. Those fields applied in transforms after vanilla pixi fields.
136 |
137 | The only exception is a `Camera3d`, that applies `projection` just after pixi fields, and then applies 3d fields in **reversed** order.
138 | That's why it can follow elements - its transform negates the element transform.
139 |
140 | ### Spine
141 |
142 | You can apply mixin from `@pixi-spine/projection` to force spine objects to spawn 2d or 3d instances of sprites and meshes.
143 |
144 | ```js
145 | import {applySpine3dMixin} from 'pixi-projection';
146 | import {SpineBase} from '@pixi-spine/base';
147 |
148 | applySpine3dMixin(SpineBase.prototype);
149 | // now all spine instances can be put in 3d projective space
150 | ```
151 |
152 | If you apply only mixin for `2d`, dont expect fields like `position3d` to be accessible.
153 |
154 | If your spine instance always exists in screen spcae, you can use it as it is, like in [Runner example](http://pixijs.github.io/examples/#/plugin-projection/runner.js)
155 |
156 | Typing are injected in `SpineBase` class of `@pixi-spine/base` package. This package is usually tree-shaken away, hope its not a problem to see it in your `node_modules` even if you dont use spine.
157 |
158 | For UMD version, you should use
159 |
160 | ```js
161 | PIXI.projection.applySpine3dMixin(PIXI.spine.Spine.prototype);
162 | ```
163 |
164 | ### Heaven
165 |
166 | No, we dont support `pixi-heaven` sprites yet.
167 |
168 | ### What if element is not supported by library?
169 |
170 | For complex objects that are not supported by library, there is a way to add them inside the camera **If their plane is perpendicular to the camera**.
171 |
172 | Create `Container3d` that returns all children to 2d space: `container3d.affine = PIXI.projection.AFFINE.AXIS_X;`
173 | Any 2d elements added to that container will think of it as a simple 2d container, and custom renderers will work with it just fine.
174 |
175 | This way is also **more performant** because **Sprite works faster than Sprite3d. 4x4 matrices ARE VERY SLOW**.
176 |
177 | ### Sorting
178 |
179 | `pixi-projection` provides extra fields to handle sorting.
180 |
181 | * `getDepth` returns the distance from near plane to the object local (0,0,0), you can pass it to zIndex or zOrder as `element.zIndex = -element.getDepth()`
182 | * `isFrontFace` detects the face of the object plane
183 |
184 | Those fields can be used with custom sorting solution or with [pixi-layers](https://github.com/pixijs/pixi-display/tree/layers/)
185 |
186 | ### Culling
187 |
188 | Will be available after we add it to `@pixi/layers`
189 |
190 | ## Vanilla JS, UMD build
191 |
192 | All pixiJS v6 plugins has special `umd` build suited for vanilla.
193 | Navigate `pixi-projection` npm package, take `dist/pixi-projection.umd.js` file.
194 |
195 | ```html
196 |
197 |
198 | ```
199 |
200 | all classes can be accessed through `PIXI.projection` package.
201 |
202 | ## Building
203 |
204 | You will need to have [node][node] setup on your machine.
205 |
206 | Then you can install dependencies and build:
207 |
208 | ```bash
209 | npm i
210 | npm run build
211 | ```
212 |
213 | That will output the built distributables to `./dist`.
214 |
215 | [node]: https://nodejs.org/
216 | [typescript]: https://www.typescriptlang.org/
217 |
--------------------------------------------------------------------------------
/src/proj3d/sprites/Sprite3d.ts:
--------------------------------------------------------------------------------
1 | import { Sprite } from '@pixi/sprite';
2 | import { Renderer, Texture } from '@pixi/core';
3 | import { Projection3d } from '../Projection3d';
4 | import { IPointData, Matrix } from '@pixi/math';
5 | import { DisplayObject } from '@pixi/display';
6 | import { TRANSFORM_STEP } from '../../base';
7 | import { container3dGetDepth, container3dIsFrontFace, container3dToLocal } from '../Container3d';
8 | import { Euler } from '../Euler';
9 | /**
10 | * Same as Sprite2d, but
11 | * 1. uses Matrix3d in proj
12 | * 2. does not render if at least one vertex is behind camera
13 | */
14 | export class Sprite3d extends Sprite
15 | {
16 | constructor(texture: Texture)
17 | {
18 | super(texture);
19 | this.proj = new Projection3d(this.transform);
20 | this.pluginName = 'batch2d';
21 | }
22 |
23 | vertexData2d: Float32Array = null;
24 | proj: Projection3d;
25 | culledByFrustrum = false;
26 | trimmedCulledByFrustrum = false;
27 |
28 | calculateVertices(): void
29 | {
30 | const texture = this._texture;
31 |
32 | if (this.proj._affine)
33 | {
34 | this.vertexData2d = null;
35 | super.calculateVertices();
36 |
37 | return;
38 | }
39 | if (!this.vertexData2d)
40 | {
41 | this.vertexData2d = new Float32Array(12);
42 | }
43 |
44 | const wid = (this.transform as any)._worldID;
45 | const tuid = (texture as any)._updateID;
46 | const thisAny = this as any;
47 |
48 | if (thisAny._transformID === wid && this._textureID === tuid)
49 | {
50 | return;
51 | }
52 | // update texture UV here, because base texture can be changed without calling `_onTextureUpdate`
53 | if (this._textureID !== tuid)
54 | {
55 | (this as any).uvs = (texture as any)._uvs.uvsFloat32;
56 | }
57 |
58 | thisAny._transformID = wid;
59 | this._textureID = tuid;
60 |
61 | const wt = this.proj.world.mat4;
62 | const vertexData2d = this.vertexData2d;
63 | const vertexData = this.vertexData;
64 | const trim = texture.trim;
65 | const orig = texture.orig;
66 | const anchor = this._anchor;
67 |
68 | let w0: number;
69 | let w1: number;
70 | let h0: number;
71 | let h1: number;
72 |
73 | if (trim)
74 | {
75 | w1 = trim.x - (anchor._x * orig.width);
76 | w0 = w1 + trim.width;
77 |
78 | h1 = trim.y - (anchor._y * orig.height);
79 | h0 = h1 + trim.height;
80 | }
81 | else
82 | {
83 | w1 = -anchor._x * orig.width;
84 | w0 = w1 + orig.width;
85 |
86 | h1 = -anchor._y * orig.height;
87 | h0 = h1 + orig.height;
88 | }
89 |
90 | let culled = false;
91 |
92 | let z;
93 |
94 | vertexData2d[0] = (wt[0] * w1) + (wt[4] * h1) + wt[12];
95 | vertexData2d[1] = (wt[1] * w1) + (wt[5] * h1) + wt[13];
96 | z = (wt[2] * w1) + (wt[6] * h1) + wt[14];
97 | vertexData2d[2] = (wt[3] * w1) + (wt[7] * h1) + wt[15];
98 | culled = culled || z < 0;
99 |
100 | vertexData2d[3] = (wt[0] * w0) + (wt[4] * h1) + wt[12];
101 | vertexData2d[4] = (wt[1] * w0) + (wt[5] * h1) + wt[13];
102 | z = (wt[2] * w0) + (wt[6] * h1) + wt[14];
103 | vertexData2d[5] = (wt[3] * w0) + (wt[7] * h1) + wt[15];
104 | culled = culled || z < 0;
105 |
106 | vertexData2d[6] = (wt[0] * w0) + (wt[4] * h0) + wt[12];
107 | vertexData2d[7] = (wt[1] * w0) + (wt[5] * h0) + wt[13];
108 | z = (wt[2] * w0) + (wt[6] * h0) + wt[14];
109 | vertexData2d[8] = (wt[3] * w0) + (wt[7] * h0) + wt[15];
110 | culled = culled || z < 0;
111 |
112 | vertexData2d[9] = (wt[0] * w1) + (wt[4] * h0) + wt[12];
113 | vertexData2d[10] = (wt[1] * w1) + (wt[5] * h0) + wt[13];
114 | z = (wt[2] * w1) + (wt[6] * h0) + wt[14];
115 | vertexData2d[11] = (wt[3] * w1) + (wt[7] * h0) + wt[15];
116 | culled = culled || z < 0;
117 |
118 | this.culledByFrustrum = culled;
119 |
120 | vertexData[0] = vertexData2d[0] / vertexData2d[2];
121 | vertexData[1] = vertexData2d[1] / vertexData2d[2];
122 |
123 | vertexData[2] = vertexData2d[3] / vertexData2d[5];
124 | vertexData[3] = vertexData2d[4] / vertexData2d[5];
125 |
126 | vertexData[4] = vertexData2d[6] / vertexData2d[8];
127 | vertexData[5] = vertexData2d[7] / vertexData2d[8];
128 |
129 | vertexData[6] = vertexData2d[9] / vertexData2d[11];
130 | vertexData[7] = vertexData2d[10] / vertexData2d[11];
131 | }
132 |
133 | calculateTrimmedVertices(): void
134 | {
135 | if (this.proj._affine)
136 | {
137 | super.calculateTrimmedVertices();
138 |
139 | return;
140 | }
141 |
142 | const wid = (this.transform as any)._worldID;
143 | const tuid = (this._texture as any)._updateID;
144 | const thisAny = this as any;
145 |
146 | if (!thisAny.vertexTrimmedData)
147 | {
148 | thisAny.vertexTrimmedData = new Float32Array(8);
149 | }
150 | else if (thisAny._transformTrimmedID === wid && this._textureTrimmedID === tuid)
151 | {
152 | return;
153 | }
154 |
155 | thisAny._transformTrimmedID = wid;
156 | this._textureTrimmedID = tuid;
157 |
158 | // lets do some special trim code!
159 | const texture = this._texture;
160 | const vertexData = thisAny.vertexTrimmedData;
161 | const orig = texture.orig;
162 | const anchor = this._anchor;
163 |
164 | // lets calculate the new untrimmed bounds..
165 | const wt = this.proj.world.mat4;
166 |
167 | const w1 = -anchor._x * orig.width;
168 | const w0 = w1 + orig.width;
169 |
170 | const h1 = -anchor._y * orig.height;
171 | const h0 = h1 + orig.height;
172 |
173 | let culled = false;
174 |
175 | let z;
176 |
177 | let w = 1.0 / ((wt[3] * w1) + (wt[7] * h1) + wt[15]);
178 |
179 | vertexData[0] = w * ((wt[0] * w1) + (wt[4] * h1) + wt[12]);
180 | vertexData[1] = w * ((wt[1] * w1) + (wt[5] * h1) + wt[13]);
181 | z = (wt[2] * w1) + (wt[6] * h1) + wt[14];
182 | culled = culled || z < 0;
183 |
184 | w = 1.0 / ((wt[3] * w0) + (wt[7] * h1) + wt[15]);
185 | vertexData[2] = w * ((wt[0] * w0) + (wt[4] * h1) + wt[12]);
186 | vertexData[3] = w * ((wt[1] * w0) + (wt[5] * h1) + wt[13]);
187 | z = (wt[2] * w0) + (wt[6] * h1) + wt[14];
188 | culled = culled || z < 0;
189 |
190 | w = 1.0 / ((wt[3] * w0) + (wt[7] * h0) + wt[15]);
191 | vertexData[4] = w * ((wt[0] * w0) + (wt[4] * h0) + wt[12]);
192 | vertexData[5] = w * ((wt[1] * w0) + (wt[5] * h0) + wt[13]);
193 | z = (wt[2] * w0) + (wt[6] * h0) + wt[14];
194 | culled = culled || z < 0;
195 |
196 | w = 1.0 / ((wt[3] * w1) + (wt[7] * h0) + wt[15]);
197 | vertexData[6] = w * ((wt[0] * w1) + (wt[4] * h0) + wt[12]);
198 | vertexData[7] = w * ((wt[1] * w1) + (wt[5] * h0) + wt[13]);
199 | z = (wt[2] * w1) + (wt[6] * h0) + wt[14];
200 | culled = culled || z < 0;
201 |
202 | this.culledByFrustrum = culled;
203 | }
204 |
205 | _calculateBounds(): void
206 | {
207 | this.calculateVertices();
208 | if (this.culledByFrustrum)
209 | {
210 | return;
211 | }
212 |
213 | const trim = this._texture.trim;
214 | const orig = this._texture.orig;
215 |
216 | if (!trim || (trim.width === orig.width && trim.height === orig.height))
217 | {
218 | // no trim! lets use the usual calculations..
219 | this._bounds.addQuad(this.vertexData);
220 |
221 | return;
222 | }
223 |
224 | this.calculateTrimmedVertices();
225 | if (!this.trimmedCulledByFrustrum)
226 | {
227 | this._bounds.addQuad((this as any).vertexTrimmedData as any);
228 | }
229 | }
230 |
231 | _render(renderer: Renderer): void
232 | {
233 | this.calculateVertices();
234 |
235 | if (this.culledByFrustrum)
236 | {
237 | return;
238 | }
239 |
240 | renderer.batch.setObjectRenderer((renderer as any).plugins[this.pluginName]);
241 | (renderer as any).plugins[this.pluginName].render(this);
242 | }
243 |
244 | containsPoint(point: IPointData): boolean
245 | {
246 | if (this.culledByFrustrum)
247 | {
248 | return false;
249 | }
250 |
251 | return super.containsPoint(point as any);
252 | }
253 |
254 | get worldTransform(): Matrix
255 | {
256 | return this.proj.affine ? this.transform.worldTransform : this.proj.world as any;
257 | }
258 |
259 | toLocal(position: IPointData, from?: DisplayObject,
260 | point?: T, skipUpdate?: boolean,
261 | step = TRANSFORM_STEP.ALL): T
262 | {
263 | return container3dToLocal.call(this, position, from, point, skipUpdate, step);
264 | }
265 |
266 | isFrontFace(forceUpdate?: boolean): boolean
267 | {
268 | return container3dIsFrontFace.call(this, forceUpdate);
269 | }
270 |
271 | getDepth(forceUpdate?: boolean): boolean
272 | {
273 | return container3dGetDepth.call(this, forceUpdate);
274 | }
275 |
276 | get position3d(): IPointData
277 | {
278 | return this.proj.position;
279 | }
280 | set position3d(value: IPointData)
281 | {
282 | this.proj.position.copyFrom(value);
283 | }
284 | get scale3d(): IPointData
285 | {
286 | return this.proj.scale;
287 | }
288 | set scale3d(value: IPointData)
289 | {
290 | this.proj.scale.copyFrom(value);
291 | }
292 | get euler(): Euler
293 | {
294 | return this.proj.euler;
295 | }
296 | set euler(value: Euler)
297 | {
298 | this.proj.euler.copyFrom(value);
299 | }
300 | get pivot3d(): IPointData
301 | {
302 | return this.proj.pivot;
303 | }
304 | set pivot3d(value: IPointData)
305 | {
306 | this.proj.pivot.copyFrom(value);
307 | }
308 | }
309 |
--------------------------------------------------------------------------------
/src/proj2d/Matrix2d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-mixed-operators,max-statements-per-line */
2 | // according to https://jsperf.com/obj-vs-array-view-access/1 , Float64Array is the best here
3 | import { IPointData, Matrix, Point } from '@pixi/math';
4 | import { AFFINE } from '../base';
5 |
6 | const mat3id = [1, 0, 0, 0, 1, 0, 0, 0, 1];
7 |
8 | export class Matrix2d
9 | {
10 | /**
11 | * A default (identity) matrix
12 | *
13 | * @static
14 | * @const
15 | */
16 | static readonly IDENTITY = new Matrix2d();
17 |
18 | /**
19 | * A temp matrix
20 | *
21 | * @static
22 | * @const
23 | */
24 | static readonly TEMP_MATRIX = new Matrix2d();
25 |
26 | /**
27 | * mat3 implementation through array of 9 elements
28 | */
29 | mat3: Float64Array;
30 |
31 | floatArray: Float32Array = null;
32 |
33 | constructor(backingArray?: ArrayLike)
34 | {
35 | this.mat3 = new Float64Array(backingArray || mat3id);
36 | }
37 |
38 | get a(): number
39 | {
40 | return this.mat3[0] / this.mat3[8];
41 | }
42 |
43 | set a(value: number)
44 | {
45 | this.mat3[0] = value * this.mat3[8];
46 | }
47 |
48 | get b(): number
49 | {
50 | return this.mat3[1] / this.mat3[8];
51 | }
52 |
53 | set b(value: number)
54 | {
55 | this.mat3[1] = value * this.mat3[8];
56 | }
57 |
58 | get c(): number
59 | {
60 | return this.mat3[3] / this.mat3[8];
61 | }
62 |
63 | set c(value: number)
64 | {
65 | this.mat3[3] = value * this.mat3[8];
66 | }
67 |
68 | get d(): number
69 | {
70 | return this.mat3[4] / this.mat3[8];
71 | }
72 |
73 | set d(value: number)
74 | {
75 | this.mat3[4] = value * this.mat3[8];
76 | }
77 |
78 | get tx(): number
79 | {
80 | return this.mat3[6] / this.mat3[8];
81 | }
82 |
83 | set tx(value: number)
84 | {
85 | this.mat3[6] = value * this.mat3[8];
86 | }
87 |
88 | get ty(): number
89 | {
90 | return this.mat3[7] / this.mat3[8];
91 | }
92 |
93 | set ty(value: number)
94 | {
95 | this.mat3[7] = value * this.mat3[8];
96 | }
97 |
98 | set(a: number, b: number, c: number, d: number, tx: number, ty: number): this
99 | {
100 | const mat3 = this.mat3;
101 |
102 | mat3[0] = a;
103 | mat3[1] = b;
104 | mat3[2] = 0;
105 | mat3[3] = c;
106 | mat3[4] = d;
107 | mat3[5] = 0;
108 | mat3[6] = tx;
109 | mat3[7] = ty;
110 | mat3[8] = 1;
111 |
112 | return this;
113 | }
114 |
115 | toArray(transpose?: boolean, out?: Float32Array): Float32Array
116 | {
117 | if (!this.floatArray)
118 | {
119 | this.floatArray = new Float32Array(9);
120 | }
121 |
122 | const array = out || this.floatArray;
123 | const mat3 = this.mat3;
124 |
125 | if (transpose)
126 | {
127 | array[0] = mat3[0];
128 | array[1] = mat3[1];
129 | array[2] = mat3[2];
130 | array[3] = mat3[3];
131 | array[4] = mat3[4];
132 | array[5] = mat3[5];
133 | array[6] = mat3[6];
134 | array[7] = mat3[7];
135 | array[8] = mat3[8];
136 | }
137 | else
138 | {
139 | // this branch is NEVER USED in pixi
140 | array[0] = mat3[0];
141 | array[1] = mat3[3];
142 | array[2] = mat3[6];
143 | array[3] = mat3[1];
144 | array[4] = mat3[4];
145 | array[5] = mat3[7];
146 | array[6] = mat3[2];
147 | array[7] = mat3[5];
148 | array[8] = mat3[8];
149 | }
150 |
151 | return array;
152 | }
153 |
154 | // TODO: remove props
155 | apply(pos: IPointData, newPos: IPointData): IPointData
156 | {
157 | newPos = newPos || new Point();
158 |
159 | const mat3 = this.mat3;
160 | const x = pos.x;
161 | const y = pos.y;
162 |
163 | const z = 1.0 / (mat3[2] * x + mat3[5] * y + mat3[8]);
164 |
165 | newPos.x = z * (mat3[0] * x + mat3[3] * y + mat3[6]);
166 | newPos.y = z * (mat3[1] * x + mat3[4] * y + mat3[7]);
167 |
168 | return newPos;
169 | }
170 |
171 | translate(tx: number, ty: number): this
172 | {
173 | const mat3 = this.mat3;
174 |
175 | mat3[0] += tx * mat3[2];
176 | mat3[1] += ty * mat3[2];
177 | mat3[3] += tx * mat3[5];
178 | mat3[4] += ty * mat3[5];
179 | mat3[6] += tx * mat3[8];
180 | mat3[7] += ty * mat3[8];
181 |
182 | return this;
183 | }
184 |
185 | scale(x: number, y: number): this
186 | {
187 | const mat3 = this.mat3;
188 |
189 | mat3[0] *= x;
190 | mat3[1] *= y;
191 | mat3[3] *= x;
192 | mat3[4] *= y;
193 | mat3[6] *= x;
194 | mat3[7] *= y;
195 |
196 | return this;
197 | }
198 |
199 | scaleAndTranslate(scaleX: number, scaleY: number, tx: number, ty: number): void
200 | {
201 | const mat3 = this.mat3;
202 |
203 | mat3[0] = scaleX * mat3[0] + tx * mat3[2];
204 | mat3[1] = scaleY * mat3[1] + ty * mat3[2];
205 | mat3[3] = scaleX * mat3[3] + tx * mat3[5];
206 | mat3[4] = scaleY * mat3[4] + ty * mat3[5];
207 | mat3[6] = scaleX * mat3[6] + tx * mat3[8];
208 | mat3[7] = scaleY * mat3[7] + ty * mat3[8];
209 | }
210 |
211 | // TODO: remove props
212 | applyInverse(pos: IPointData, newPos: IPointData): IPointData
213 | {
214 | newPos = newPos || new Point();
215 |
216 | const a = this.mat3;
217 | const x = pos.x;
218 | const y = pos.y;
219 |
220 | const a00 = a[0]; const a01 = a[3]; const a02 = a[6];
221 | const a10 = a[1]; const a11 = a[4]; const a12 = a[7];
222 | const a20 = a[2]; const a21 = a[5]; const
223 | a22 = a[8];
224 |
225 | const newX = (a22 * a11 - a12 * a21) * x + (-a22 * a01 + a02 * a21) * y + (a12 * a01 - a02 * a11);
226 | const newY = (-a22 * a10 + a12 * a20) * x + (a22 * a00 - a02 * a20) * y + (-a12 * a00 + a02 * a10);
227 | const newZ = (a21 * a10 - a11 * a20) * x + (-a21 * a00 + a01 * a20) * y + (a11 * a00 - a01 * a10);
228 |
229 | newPos.x = newX / newZ;
230 | newPos.y = newY / newZ;
231 |
232 | return newPos;
233 | }
234 |
235 | invert(): Matrix2d
236 | {
237 | const a = this.mat3;
238 |
239 | const a00 = a[0]; const a01 = a[1]; const a02 = a[2];
240 | const a10 = a[3]; const a11 = a[4]; const a12 = a[5];
241 | const a20 = a[6]; const a21 = a[7]; const a22 = a[8];
242 |
243 | const b01 = a22 * a11 - a12 * a21;
244 | const b11 = -a22 * a10 + a12 * a20;
245 | const b21 = a21 * a10 - a11 * a20;
246 |
247 | // Calculate the determinant
248 | let det = a00 * b01 + a01 * b11 + a02 * b21;
249 |
250 | if (!det)
251 | {
252 | return this;
253 | }
254 | det = 1.0 / det;
255 |
256 | a[0] = b01 * det;
257 | a[1] = (-a22 * a01 + a02 * a21) * det;
258 | a[2] = (a12 * a01 - a02 * a11) * det;
259 | a[3] = b11 * det;
260 | a[4] = (a22 * a00 - a02 * a20) * det;
261 | a[5] = (-a12 * a00 + a02 * a10) * det;
262 | a[6] = b21 * det;
263 | a[7] = (-a21 * a00 + a01 * a20) * det;
264 | a[8] = (a11 * a00 - a01 * a10) * det;
265 |
266 | return this;
267 | }
268 |
269 | identity(): Matrix2d
270 | {
271 | const mat3 = this.mat3;
272 |
273 | mat3[0] = 1;
274 | mat3[1] = 0;
275 | mat3[2] = 0;
276 | mat3[3] = 0;
277 | mat3[4] = 1;
278 | mat3[5] = 0;
279 | mat3[6] = 0;
280 | mat3[7] = 0;
281 | mat3[8] = 1;
282 |
283 | return this;
284 | }
285 |
286 | clone(): Matrix2d
287 | {
288 | return new Matrix2d(this.mat3);
289 | }
290 |
291 | copyTo2dOr3d(matrix: Matrix2d): Matrix2d
292 | {
293 | const mat3 = this.mat3;
294 | const ar2 = matrix.mat3;
295 |
296 | ar2[0] = mat3[0];
297 | ar2[1] = mat3[1];
298 | ar2[2] = mat3[2];
299 | ar2[3] = mat3[3];
300 | ar2[4] = mat3[4];
301 | ar2[5] = mat3[5];
302 | ar2[6] = mat3[6];
303 | ar2[7] = mat3[7];
304 | ar2[8] = mat3[8];
305 |
306 | return matrix;
307 | }
308 |
309 | /**
310 | * legacy method, change the values of given pixi matrix
311 | * @param matrix
312 | * @param affine
313 | * @param preserveOrientation
314 | * @return matrix
315 | */
316 | copyTo(matrix: Matrix, affine?: AFFINE, preserveOrientation?: boolean): Matrix
317 | {
318 | const mat3 = this.mat3;
319 | const d = 1.0 / mat3[8];
320 | const tx = mat3[6] * d; const
321 | ty = mat3[7] * d;
322 |
323 | matrix.a = (mat3[0] - mat3[2] * tx) * d;
324 | matrix.b = (mat3[1] - mat3[2] * ty) * d;
325 | matrix.c = (mat3[3] - mat3[5] * tx) * d;
326 | matrix.d = (mat3[4] - mat3[5] * ty) * d;
327 | matrix.tx = tx;
328 | matrix.ty = ty;
329 |
330 | if (affine >= 2)
331 | {
332 | let D = matrix.a * matrix.d - matrix.b * matrix.c;
333 |
334 | if (!preserveOrientation)
335 | {
336 | D = Math.abs(D);
337 | }
338 | if (affine === AFFINE.POINT)
339 | {
340 | if (D > 0)
341 | {
342 | D = 1;
343 | }
344 | else D = -1;
345 | matrix.a = D;
346 | matrix.b = 0;
347 | matrix.c = 0;
348 | matrix.d = D;
349 | }
350 | else if (affine === AFFINE.AXIS_X)
351 | {
352 | D /= Math.sqrt(matrix.b * matrix.b + matrix.d * matrix.d);
353 | matrix.c = 0;
354 | matrix.d = D;
355 | }
356 | else if (affine === AFFINE.AXIS_Y)
357 | {
358 | D /= Math.sqrt(matrix.a * matrix.a + matrix.c * matrix.c);
359 | matrix.a = D;
360 | matrix.c = 0;
361 | }
362 | else if (affine === AFFINE.AXIS_XR)
363 | {
364 | matrix.a = matrix.d * D;
365 | matrix.c = -matrix.b * D;
366 | }
367 | }
368 |
369 | return matrix;
370 | }
371 |
372 | /**
373 | * legacy method, change the values of given pixi matrix
374 | * @param matrix
375 | * @return
376 | */
377 | copyFrom(matrix: Matrix): this
378 | {
379 | const mat3 = this.mat3;
380 |
381 | mat3[0] = matrix.a;
382 | mat3[1] = matrix.b;
383 | mat3[2] = 0;
384 | mat3[3] = matrix.c;
385 | mat3[4] = matrix.d;
386 | mat3[5] = 0;
387 | mat3[6] = matrix.tx;
388 | mat3[7] = matrix.ty;
389 | mat3[8] = 1.0;
390 |
391 | return this;
392 | }
393 |
394 | setToMultLegacy(pt: Matrix, lt: Matrix2d): this
395 | {
396 | const out = this.mat3;
397 | const b = lt.mat3;
398 |
399 | const a00 = pt.a; const a01 = pt.b;
400 | const a10 = pt.c; const a11 = pt.d;
401 | const a20 = pt.tx; const a21 = pt.ty;
402 |
403 | const b00 = b[0]; const b01 = b[1]; const b02 = b[2];
404 | const b10 = b[3]; const b11 = b[4]; const b12 = b[5];
405 | const b20 = b[6]; const b21 = b[7]; const
406 | b22 = b[8];
407 |
408 | out[0] = b00 * a00 + b01 * a10 + b02 * a20;
409 | out[1] = b00 * a01 + b01 * a11 + b02 * a21;
410 | out[2] = b02;
411 |
412 | out[3] = b10 * a00 + b11 * a10 + b12 * a20;
413 | out[4] = b10 * a01 + b11 * a11 + b12 * a21;
414 | out[5] = b12;
415 |
416 | out[6] = b20 * a00 + b21 * a10 + b22 * a20;
417 | out[7] = b20 * a01 + b21 * a11 + b22 * a21;
418 | out[8] = b22;
419 |
420 | return this;
421 | }
422 |
423 | setToMultLegacy2(pt: Matrix2d, lt: Matrix): this
424 | {
425 | const out = this.mat3;
426 | const a = pt.mat3;
427 |
428 | const a00 = a[0]; const a01 = a[1]; const a02 = a[2];
429 | const a10 = a[3]; const a11 = a[4]; const a12 = a[5];
430 | const a20 = a[6]; const a21 = a[7]; const a22 = a[8];
431 |
432 | const b00 = lt.a; const b01 = lt.b;
433 | const b10 = lt.c; const b11 = lt.d;
434 | const b20 = lt.tx; const
435 | b21 = lt.ty;
436 |
437 | out[0] = b00 * a00 + b01 * a10;
438 | out[1] = b00 * a01 + b01 * a11;
439 | out[2] = b00 * a02 + b01 * a12;
440 |
441 | out[3] = b10 * a00 + b11 * a10;
442 | out[4] = b10 * a01 + b11 * a11;
443 | out[5] = b10 * a02 + b11 * a12;
444 |
445 | out[6] = b20 * a00 + b21 * a10 + a20;
446 | out[7] = b20 * a01 + b21 * a11 + a21;
447 | out[8] = b20 * a02 + b21 * a12 + a22;
448 |
449 | return this;
450 | }
451 |
452 | // that's transform multiplication we use
453 | setToMult(pt: Matrix2d, lt: Matrix2d): this
454 | {
455 | const out = this.mat3;
456 | const a = pt.mat3; const
457 | b = lt.mat3;
458 |
459 | const a00 = a[0]; const a01 = a[1]; const a02 = a[2];
460 | const a10 = a[3]; const a11 = a[4]; const a12 = a[5];
461 | const a20 = a[6]; const a21 = a[7]; const a22 = a[8];
462 |
463 | const b00 = b[0]; const b01 = b[1]; const b02 = b[2];
464 | const b10 = b[3]; const b11 = b[4]; const b12 = b[5];
465 | const b20 = b[6]; const b21 = b[7]; const
466 | b22 = b[8];
467 |
468 | out[0] = b00 * a00 + b01 * a10 + b02 * a20;
469 | out[1] = b00 * a01 + b01 * a11 + b02 * a21;
470 | out[2] = b00 * a02 + b01 * a12 + b02 * a22;
471 |
472 | out[3] = b10 * a00 + b11 * a10 + b12 * a20;
473 | out[4] = b10 * a01 + b11 * a11 + b12 * a21;
474 | out[5] = b10 * a02 + b11 * a12 + b12 * a22;
475 |
476 | out[6] = b20 * a00 + b21 * a10 + b22 * a20;
477 | out[7] = b20 * a01 + b21 * a11 + b22 * a21;
478 | out[8] = b20 * a02 + b21 * a12 + b22 * a22;
479 |
480 | return this;
481 | }
482 |
483 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
484 | prepend(lt: any): this
485 | {
486 | if (lt.mat3)
487 | {
488 | return this.setToMult(lt, this);
489 | }
490 |
491 | return this.setToMultLegacy(lt, this);
492 | }
493 | }
494 |
--------------------------------------------------------------------------------
/src/proj3d/Matrix3d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-mixed-operators,max-statements-per-line */
2 | // according to https://jsperf.com/obj-vs-array-view-access/1 , Float64Array is the best here
3 |
4 | import { IPointData, Matrix, Point } from '@pixi/math';
5 | import { Matrix2d } from '../proj2d';
6 | import { Point3d } from './Point3d';
7 | import { AFFINE } from '../base';
8 |
9 | const mat4id = [1, 0, 0, 0,
10 | 0, 1, 0, 0,
11 | 0, 0, 1, 0,
12 | 0, 0, 0, 1];
13 |
14 | export class Matrix3d
15 | {
16 | /**
17 | * A default (identity) matrix
18 | *
19 | * @static
20 | * @const
21 | */
22 | static readonly IDENTITY = new Matrix3d();
23 |
24 | /**
25 | * A temp matrix
26 | *
27 | * @static
28 | * @const
29 | */
30 | static readonly TEMP_MATRIX = new Matrix3d();
31 |
32 | /**
33 | * mat4 implementation through array of 16 elements
34 | */
35 | mat4: Float64Array;
36 |
37 | floatArray: Float32Array = null;
38 |
39 | _dirtyId = 0;
40 | _updateId = -1;
41 | _mat4inv: Float64Array = null;
42 | cacheInverse = false;
43 |
44 | constructor(backingArray?: ArrayLike)
45 | {
46 | this.mat4 = new Float64Array(backingArray || mat4id);
47 | }
48 |
49 | get a(): number
50 | {
51 | return this.mat4[0] / this.mat4[15];
52 | }
53 |
54 | set a(value: number)
55 | {
56 | this.mat4[0] = value * this.mat4[15];
57 | }
58 |
59 | get b(): number
60 | {
61 | return this.mat4[1] / this.mat4[15];
62 | }
63 |
64 | set b(value: number)
65 | {
66 | this.mat4[1] = value * this.mat4[15];
67 | }
68 |
69 | get c(): number
70 | {
71 | return this.mat4[4] / this.mat4[15];
72 | }
73 |
74 | set c(value: number)
75 | {
76 | this.mat4[4] = value * this.mat4[15];
77 | }
78 |
79 | get d(): number
80 | {
81 | return this.mat4[5] / this.mat4[15];
82 | }
83 |
84 | set d(value: number)
85 | {
86 | this.mat4[5] = value * this.mat4[15];
87 | }
88 |
89 | get tx(): number
90 | {
91 | return this.mat4[12] / this.mat4[15];
92 | }
93 |
94 | set tx(value: number)
95 | {
96 | this.mat4[12] = value * this.mat4[15];
97 | }
98 |
99 | get ty(): number
100 | {
101 | return this.mat4[13] / this.mat4[15];
102 | }
103 |
104 | set ty(value: number)
105 | {
106 | this.mat4[13] = value * this.mat4[15];
107 | }
108 |
109 | set(a: number, b: number, c: number, d: number, tx: number, ty: number): this
110 | {
111 | const mat4 = this.mat4;
112 |
113 | mat4[0] = a;
114 | mat4[1] = b;
115 | mat4[2] = 0;
116 | mat4[3] = 0;
117 | mat4[4] = c;
118 | mat4[5] = d;
119 | mat4[6] = 0;
120 | mat4[7] = 0;
121 | mat4[8] = 0;
122 | mat4[9] = 0;
123 | mat4[10] = 1;
124 | mat4[11] = 0;
125 | mat4[12] = tx;
126 | mat4[13] = ty;
127 | mat4[14] = 0;
128 | mat4[15] = 1;
129 |
130 | return this;
131 | }
132 |
133 | toArray(transpose?: boolean, out?: Float32Array): Float32Array
134 | {
135 | if (!this.floatArray)
136 | {
137 | this.floatArray = new Float32Array(9);
138 | }
139 |
140 | const array = out || this.floatArray;
141 | const mat3 = this.mat4;
142 |
143 | if (transpose)
144 | {
145 | array[0] = mat3[0];
146 | array[1] = mat3[1];
147 | array[2] = mat3[3];
148 | array[3] = mat3[4];
149 | array[4] = mat3[5];
150 | array[5] = mat3[7];
151 | array[6] = mat3[12];
152 | array[7] = mat3[13];
153 | array[8] = mat3[15];
154 | }
155 | else
156 | {
157 | // this branch is NEVER USED in pixi
158 | array[0] = mat3[0];
159 | array[1] = mat3[4];
160 | array[2] = mat3[12];
161 | array[3] = mat3[2];
162 | array[4] = mat3[6];
163 | array[5] = mat3[13];
164 | array[6] = mat3[3];
165 | array[7] = mat3[7];
166 | array[8] = mat3[15];
167 | }
168 |
169 | return array;
170 | }
171 |
172 | setToTranslation(tx: number, ty: number, tz: number): void
173 | {
174 | const mat4 = this.mat4;
175 |
176 | mat4[0] = 1;
177 | mat4[1] = 0;
178 | mat4[2] = 0;
179 | mat4[3] = 0;
180 |
181 | mat4[4] = 0;
182 | mat4[5] = 1;
183 | mat4[6] = 0;
184 | mat4[7] = 0;
185 |
186 | mat4[8] = 0;
187 | mat4[9] = 0;
188 | mat4[10] = 1;
189 | mat4[11] = 0;
190 |
191 | mat4[12] = tx;
192 | mat4[13] = ty;
193 | mat4[14] = tz;
194 | mat4[15] = 1;
195 | }
196 |
197 | // eslint-disable-next-line max-len
198 | setToRotationTranslationScale(quat: Float64Array, tx: number, ty: number, tz: number, sx: number, sy: number, sz: number): Float64Array
199 | {
200 | const out = this.mat4;
201 |
202 | const x = quat[0]; const y = quat[1]; const z = quat[2]; const
203 | w = quat[3];
204 | const x2 = x + x;
205 | const y2 = y + y;
206 | const z2 = z + z;
207 |
208 | const xx = x * x2;
209 | const xy = x * y2;
210 | const xz = x * z2;
211 | const yy = y * y2;
212 | const yz = y * z2;
213 | const zz = z * z2;
214 | const wx = w * x2;
215 | const wy = w * y2;
216 | const wz = w * z2;
217 |
218 | out[0] = (1 - (yy + zz)) * sx;
219 | out[1] = (xy + wz) * sx;
220 | out[2] = (xz - wy) * sx;
221 | out[3] = 0;
222 | out[4] = (xy - wz) * sy;
223 | out[5] = (1 - (xx + zz)) * sy;
224 | out[6] = (yz + wx) * sy;
225 | out[7] = 0;
226 | out[8] = (xz + wy) * sz;
227 | out[9] = (yz - wx) * sz;
228 | out[10] = (1 - (xx + yy)) * sz;
229 | out[11] = 0;
230 | out[12] = tx;
231 | out[13] = ty;
232 | out[14] = tz;
233 | out[15] = 1;
234 |
235 | return out;
236 | }
237 |
238 | apply(pos: IPointData, newPos: IPointData): IPointData
239 | {
240 | newPos = newPos || new Point3d();
241 |
242 | const mat4 = this.mat4;
243 | const x = pos.x;
244 | const y = pos.y;
245 | // TODO: pixi 6.1.0 global mixin
246 | const z = (pos as any).z || 0;
247 |
248 | // TODO: apply for 2d point
249 |
250 | const w = 1.0 / (mat4[3] * x + mat4[7] * y + mat4[11] * z + mat4[15]);
251 |
252 | newPos.x = w * (mat4[0] * x + mat4[4] * y + mat4[8] * z + mat4[12]);
253 | newPos.y = w * (mat4[1] * x + mat4[5] * y + mat4[9] * z + mat4[13]);
254 | // TODO: pixi 6.1.0 global mixin
255 | (newPos as any).z = w * (mat4[2] * x + mat4[6] * y + mat4[10] * z + mat4[14]);
256 |
257 | return newPos;
258 | }
259 |
260 | translate(tx: number, ty: number, tz: number): this
261 | {
262 | const a = this.mat4;
263 |
264 | a[12] = a[0] * tx + a[4] * ty + a[8] * tz + a[12];
265 | a[13] = a[1] * tx + a[5] * ty + a[9] * tz + a[13];
266 | a[14] = a[2] * tx + a[6] * ty + a[10] * tz + a[14];
267 | a[15] = a[3] * tx + a[7] * ty + a[11] * tz + a[15];
268 |
269 | return this;
270 | }
271 |
272 | scale(x: number, y: number, z?: number): this
273 | {
274 | const mat4 = this.mat4;
275 |
276 | mat4[0] *= x;
277 | mat4[1] *= x;
278 | mat4[2] *= x;
279 | mat4[3] *= x;
280 |
281 | mat4[4] *= y;
282 | mat4[5] *= y;
283 | mat4[6] *= y;
284 | mat4[7] *= y;
285 |
286 | if (z !== undefined)
287 | {
288 | mat4[8] *= z;
289 | mat4[9] *= z;
290 | mat4[10] *= z;
291 | mat4[11] *= z;
292 | }
293 |
294 | return this;
295 | }
296 |
297 | scaleAndTranslate(scaleX: number, scaleY: number, scaleZ: number, tx: number, ty: number, tz: number): void
298 | {
299 | const mat4 = this.mat4;
300 |
301 | mat4[0] = scaleX * mat4[0] + tx * mat4[3];
302 | mat4[1] = scaleY * mat4[1] + ty * mat4[3];
303 | mat4[2] = scaleZ * mat4[2] + tz * mat4[3];
304 |
305 | mat4[4] = scaleX * mat4[4] + tx * mat4[7];
306 | mat4[5] = scaleY * mat4[5] + ty * mat4[7];
307 | mat4[6] = scaleZ * mat4[6] + tz * mat4[7];
308 |
309 | mat4[8] = scaleX * mat4[8] + tx * mat4[11];
310 | mat4[9] = scaleY * mat4[9] + ty * mat4[11];
311 | mat4[10] = scaleZ * mat4[10] + tz * mat4[11];
312 |
313 | mat4[12] = scaleX * mat4[12] + tx * mat4[15];
314 | mat4[13] = scaleY * mat4[13] + ty * mat4[15];
315 | mat4[14] = scaleZ * mat4[14] + tz * mat4[15];
316 | }
317 |
318 | // TODO: remove props
319 | applyInverse(pos: IPointData, newPos?: P): P
320 | {
321 | newPos = (newPos || new Point3d()) as any;
322 | if (!this._mat4inv)
323 | {
324 | this._mat4inv = new Float64Array(16);
325 | }
326 |
327 | const mat4 = this._mat4inv;
328 | const a = this.mat4;
329 | const x = pos.x;
330 | const y = pos.y;
331 | // TODO: pixi 6.1.0 global mixin
332 | let z = (pos as any).z || 0;
333 |
334 | if (!this.cacheInverse || this._updateId !== this._dirtyId)
335 | {
336 | this._updateId = this._dirtyId;
337 | Matrix3d.glMatrixMat4Invert(mat4, a);
338 | }
339 |
340 | const w1 = 1.0 / (mat4[3] * x + mat4[7] * y + mat4[11] * z + mat4[15]);
341 | const x1 = w1 * (mat4[0] * x + mat4[4] * y + mat4[8] * z + mat4[12]);
342 | const y1 = w1 * (mat4[1] * x + mat4[5] * y + mat4[9] * z + mat4[13]);
343 | const z1 = w1 * (mat4[2] * x + mat4[6] * y + mat4[10] * z + mat4[14]);
344 |
345 | z += 1.0;
346 |
347 | const w2 = 1.0 / (mat4[3] * x + mat4[7] * y + mat4[11] * z + mat4[15]);
348 | const x2 = w2 * (mat4[0] * x + mat4[4] * y + mat4[8] * z + mat4[12]);
349 | const y2 = w2 * (mat4[1] * x + mat4[5] * y + mat4[9] * z + mat4[13]);
350 | const z2 = w2 * (mat4[2] * x + mat4[6] * y + mat4[10] * z + mat4[14]);
351 |
352 | if (Math.abs(z1 - z2) < 1e-10)
353 | {
354 | (newPos as any).set(NaN, NaN, 0);
355 | }
356 |
357 | const alpha = (0 - z1) / (z2 - z1);
358 |
359 | (newPos as any).set((x2 - x1) * alpha + x1, (y2 - y1) * alpha + y1, 0.0);
360 |
361 | return newPos;
362 | }
363 |
364 | invert(): Matrix3d
365 | {
366 | Matrix3d.glMatrixMat4Invert(this.mat4, this.mat4);
367 |
368 | return this;
369 | }
370 |
371 | invertCopyTo(matrix: Matrix3d): void
372 | {
373 | if (!this._mat4inv)
374 | {
375 | this._mat4inv = new Float64Array(16);
376 | }
377 |
378 | const mat4 = this._mat4inv;
379 | const a = this.mat4;
380 |
381 | if (!this.cacheInverse || this._updateId !== this._dirtyId)
382 | {
383 | this._updateId = this._dirtyId;
384 | Matrix3d.glMatrixMat4Invert(mat4, a);
385 | }
386 |
387 | matrix.mat4.set(mat4);
388 | }
389 |
390 | identity(): Matrix3d
391 | {
392 | const mat3 = this.mat4;
393 |
394 | mat3[0] = 1;
395 | mat3[1] = 0;
396 | mat3[2] = 0;
397 | mat3[3] = 0;
398 |
399 | mat3[4] = 0;
400 | mat3[5] = 1;
401 | mat3[6] = 0;
402 | mat3[7] = 0;
403 |
404 | mat3[8] = 0;
405 | mat3[9] = 0;
406 | mat3[10] = 1;
407 | mat3[11] = 0;
408 |
409 | mat3[12] = 0;
410 | mat3[13] = 0;
411 | mat3[14] = 0;
412 | mat3[15] = 1;
413 |
414 | return this;
415 | }
416 |
417 | clone(): Matrix3d
418 | {
419 | return new Matrix3d(this.mat4);
420 | }
421 |
422 | copyTo3d(matrix: Matrix3d): Matrix3d
423 | {
424 | const mat3 = this.mat4;
425 | const ar2 = matrix.mat4;
426 |
427 | ar2[0] = mat3[0];
428 | ar2[1] = mat3[1];
429 | ar2[2] = mat3[2];
430 | ar2[3] = mat3[3];
431 | ar2[4] = mat3[4];
432 | ar2[5] = mat3[5];
433 | ar2[6] = mat3[6];
434 | ar2[7] = mat3[7];
435 | ar2[8] = mat3[8];
436 |
437 | return matrix;
438 | }
439 |
440 | copyTo2d(matrix: Matrix2d): Matrix2d
441 | {
442 | const mat3 = this.mat4;
443 | const ar2 = matrix.mat3;
444 |
445 | ar2[0] = mat3[0];
446 | ar2[1] = mat3[1];
447 | ar2[2] = mat3[3];
448 | ar2[3] = mat3[4];
449 | ar2[4] = mat3[5];
450 | ar2[5] = mat3[7];
451 | ar2[6] = mat3[12];
452 | ar2[7] = mat3[13];
453 | ar2[8] = mat3[15];
454 |
455 | return matrix;
456 | }
457 |
458 | copyTo2dOr3d
(matrix: P): P
459 | {
460 | if (matrix instanceof Matrix2d)
461 | {
462 | return this.copyTo2d(matrix) as any;
463 | }
464 |
465 | return this.copyTo3d(matrix as any) as any;
466 | }
467 |
468 | /**
469 | * legacy method, change the values of given pixi matrix
470 | * @param matrix
471 | * @param affine
472 | * @param preserveOrientation
473 | * @return matrix
474 | */
475 | copyTo(matrix: Matrix, affine?: AFFINE, preserveOrientation?: boolean): Matrix
476 | {
477 | const mat3 = this.mat4;
478 | const d = 1.0 / mat3[15];
479 | const tx = mat3[12] * d; const
480 | ty = mat3[13] * d;
481 |
482 | matrix.a = (mat3[0] - mat3[3] * tx) * d;
483 | matrix.b = (mat3[1] - mat3[3] * ty) * d;
484 | matrix.c = (mat3[4] - mat3[7] * tx) * d;
485 | matrix.d = (mat3[5] - mat3[7] * ty) * d;
486 | matrix.tx = tx;
487 | matrix.ty = ty;
488 |
489 | if (affine >= 2)
490 | {
491 | let D = matrix.a * matrix.d - matrix.b * matrix.c;
492 |
493 | if (!preserveOrientation)
494 | {
495 | D = Math.abs(D);
496 | }
497 | if (affine === AFFINE.POINT)
498 | {
499 | if (D > 0)
500 | {
501 | D = 1;
502 | }
503 | else D = -1;
504 | matrix.a = D;
505 | matrix.b = 0;
506 | matrix.c = 0;
507 | matrix.d = D;
508 | }
509 | else if (affine === AFFINE.AXIS_X)
510 | {
511 | D /= Math.sqrt(matrix.b * matrix.b + matrix.d * matrix.d);
512 | matrix.c = 0;
513 | matrix.d = D;
514 | }
515 | else if (affine === AFFINE.AXIS_Y)
516 | {
517 | D /= Math.sqrt(matrix.a * matrix.a + matrix.c * matrix.c);
518 | matrix.a = D;
519 | matrix.c = 0;
520 | }
521 | }
522 |
523 | return matrix;
524 | }
525 |
526 | /**
527 | * legacy method, change the values of given pixi matrix
528 | * @param matrix
529 | * @return
530 | */
531 | copyFrom(matrix: Matrix): this
532 | {
533 | const mat3 = this.mat4;
534 |
535 | mat3[0] = matrix.a;
536 | mat3[1] = matrix.b;
537 | mat3[2] = 0;
538 | mat3[3] = 0;
539 |
540 | mat3[4] = matrix.c;
541 | mat3[5] = matrix.d;
542 | mat3[6] = 0;
543 | mat3[7] = 0;
544 |
545 | mat3[8] = 0;
546 | mat3[9] = 0;
547 | mat3[10] = 1;
548 | mat3[11] = 0;
549 |
550 | mat3[12] = matrix.tx;
551 | mat3[13] = matrix.ty;
552 | mat3[14] = 0;
553 | mat3[15] = 1;
554 |
555 | this._dirtyId++;
556 |
557 | return this;
558 | }
559 |
560 | setToMultLegacy(pt: Matrix, lt: Matrix3d): this
561 | {
562 | const out = this.mat4;
563 | const b = lt.mat4;
564 |
565 | const a00 = pt.a; const a01 = pt.b;
566 | const a10 = pt.c; const a11 = pt.d;
567 | const a30 = pt.tx; const
568 | a31 = pt.ty;
569 |
570 | let b0 = b[0]; let b1 = b[1]; let b2 = b[2]; let
571 | b3 = b[3];
572 |
573 | out[0] = b0 * a00 + b1 * a10 + b3 * a30;
574 | out[1] = b0 * a01 + b1 * a11 + b3 * a31;
575 | out[2] = b2;
576 | out[3] = b3;
577 |
578 | b0 = b[4];
579 | b1 = b[5];
580 | b2 = b[6];
581 | b3 = b[7];
582 | out[4] = b0 * a00 + b1 * a10 + b3 * a30;
583 | out[5] = b0 * a01 + b1 * a11 + b3 * a31;
584 | out[6] = b2;
585 | out[7] = b3;
586 |
587 | b0 = b[8];
588 | b1 = b[9];
589 | b2 = b[10];
590 | b3 = b[11];
591 | out[8] = b0 * a00 + b1 * a10 + b3 * a30;
592 | out[9] = b0 * a01 + b1 * a11 + b3 * a31;
593 | out[10] = b2;
594 | out[11] = b3;
595 |
596 | b0 = b[12];
597 | b1 = b[13];
598 | b2 = b[14];
599 | b3 = b[15];
600 | out[12] = b0 * a00 + b1 * a10 + b3 * a30;
601 | out[13] = b0 * a01 + b1 * a11 + b3 * a31;
602 | out[14] = b2;
603 | out[15] = b3;
604 |
605 | this._dirtyId++;
606 |
607 | return this;
608 | }
609 |
610 | setToMultLegacy2(pt: Matrix3d, lt: Matrix): this
611 | {
612 | const out = this.mat4;
613 | const a = pt.mat4;
614 |
615 | const a00 = a[0]; const a01 = a[1]; const a02 = a[2]; const
616 | a03 = a[3];
617 | const a10 = a[4]; const a11 = a[5]; const a12 = a[6]; const
618 | a13 = a[7];
619 |
620 | const b00 = lt.a; const b01 = lt.b;
621 | const b10 = lt.c; const b11 = lt.d;
622 | const b30 = lt.tx; const
623 | b31 = lt.ty;
624 |
625 | out[0] = b00 * a00 + b01 * a10;
626 | out[1] = b00 * a01 + b01 * a11;
627 | out[2] = b00 * a02 + b01 * a12;
628 | out[3] = b00 * a03 + b01 * a13;
629 |
630 | out[4] = b10 * a00 + b11 * a10;
631 | out[5] = b10 * a01 + b11 * a11;
632 | out[6] = b10 * a02 + b11 * a12;
633 | out[7] = b10 * a03 + b11 * a13;
634 |
635 | out[8] = a[8];
636 | out[9] = a[9];
637 | out[10] = a[10];
638 | out[11] = a[11];
639 |
640 | out[12] = b30 * a00 + b31 * a10 + a[12];
641 | out[13] = b30 * a01 + b31 * a11 + a[13];
642 | out[14] = b30 * a02 + b31 * a12 + a[14];
643 | out[15] = b30 * a03 + b31 * a13 + a[15];
644 |
645 | this._dirtyId++;
646 |
647 | return this;
648 | }
649 |
650 | // that's transform multiplication we use
651 | setToMult(pt: Matrix3d, lt: Matrix3d): this
652 | {
653 | Matrix3d.glMatrixMat4Multiply(this.mat4, pt.mat4, lt.mat4);
654 |
655 | this._dirtyId++;
656 |
657 | return this;
658 | }
659 |
660 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
661 | prepend(lt: any): void
662 | {
663 | if (lt.mat4)
664 | {
665 | this.setToMult(lt, this);
666 | }
667 | else
668 | {
669 | this.setToMultLegacy(lt, this);
670 | }
671 | }
672 |
673 | static glMatrixMat4Invert(out: Float64Array, a: Float64Array): Float64Array
674 | {
675 | const a00 = a[0]; const a01 = a[1]; const a02 = a[2]; const
676 | a03 = a[3];
677 | const a10 = a[4]; const a11 = a[5]; const a12 = a[6]; const
678 | a13 = a[7];
679 | const a20 = a[8]; const a21 = a[9]; const a22 = a[10]; const
680 | a23 = a[11];
681 | const a30 = a[12]; const a31 = a[13]; const a32 = a[14]; const
682 | a33 = a[15];
683 |
684 | const b00 = a00 * a11 - a01 * a10;
685 | const b01 = a00 * a12 - a02 * a10;
686 | const b02 = a00 * a13 - a03 * a10;
687 | const b03 = a01 * a12 - a02 * a11;
688 | const b04 = a01 * a13 - a03 * a11;
689 | const b05 = a02 * a13 - a03 * a12;
690 | const b06 = a20 * a31 - a21 * a30;
691 | const b07 = a20 * a32 - a22 * a30;
692 | const b08 = a20 * a33 - a23 * a30;
693 | const b09 = a21 * a32 - a22 * a31;
694 | const b10 = a21 * a33 - a23 * a31;
695 | const b11 = a22 * a33 - a23 * a32;
696 |
697 | // Calculate the determinant
698 | let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
699 |
700 | if (!det)
701 | {
702 | return null;
703 | }
704 | det = 1.0 / det;
705 |
706 | out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
707 | out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
708 | out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
709 | out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
710 | out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
711 | out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
712 | out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
713 | out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
714 | out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
715 | out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
716 | out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
717 | out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
718 | out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
719 | out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
720 | out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
721 | out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
722 |
723 | return out;
724 | }
725 |
726 | static glMatrixMat4Multiply(out: Float64Array, a: Float64Array, b: Float64Array): Float64Array
727 | {
728 | const a00 = a[0]; const a01 = a[1]; const a02 = a[2]; const
729 | a03 = a[3];
730 | const a10 = a[4]; const a11 = a[5]; const a12 = a[6]; const
731 | a13 = a[7];
732 | const a20 = a[8]; const a21 = a[9]; const a22 = a[10]; const
733 | a23 = a[11];
734 | const a30 = a[12]; const a31 = a[13]; const a32 = a[14]; const
735 | a33 = a[15];
736 |
737 | // Cache only the current line of the second matrix
738 | let b0 = b[0]; let b1 = b[1]; let b2 = b[2]; let
739 | b3 = b[3];
740 |
741 | out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
742 | out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
743 | out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
744 | out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
745 |
746 | b0 = b[4];
747 | b1 = b[5];
748 | b2 = b[6];
749 | b3 = b[7];
750 | out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
751 | out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
752 | out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
753 | out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
754 |
755 | b0 = b[8];
756 | b1 = b[9];
757 | b2 = b[10];
758 | b3 = b[11];
759 | out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
760 | out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
761 | out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
762 | out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
763 |
764 | b0 = b[12];
765 | b1 = b[13];
766 | b2 = b[14];
767 | b3 = b[15];
768 | out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
769 | out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
770 | out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
771 | out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
772 |
773 | return out;
774 | }
775 | }
776 |
--------------------------------------------------------------------------------