├── .npmrc ├── rollup.config.js ├── src ├── spine │ ├── index.ts │ ├── base.ts │ ├── Spine2d.ts │ └── Spine3d.ts ├── index.ts ├── base │ ├── index.ts │ ├── AbstractProjection.ts │ ├── webgl │ │ ├── Sprite2dRenderer.ts │ │ └── UniformBatchRenderer.ts │ └── LinearProjection.ts ├── proj3d │ ├── index.ts │ ├── Camera3d.ts │ ├── Point3d.ts │ ├── sprites │ │ ├── Text3d.ts │ │ ├── convert.ts │ │ └── Sprite3d.ts │ ├── Projection3d.ts │ ├── Euler.ts │ ├── Container3d.ts │ ├── ObservableEuler.ts │ ├── mesh │ │ └── Mesh3d2d.ts │ └── Matrix3d.ts ├── curve │ ├── index.ts │ ├── Container2s.ts │ ├── sprites │ │ ├── Text2s.ts │ │ ├── convert.ts │ │ └── Sprite2s.ts │ ├── BaseSurface.ts │ ├── BilinearSurface.ts │ ├── ProjectionSurface.ts │ └── SpriteBilinearRenderer.ts ├── proj2d │ ├── index.ts │ ├── sprites │ │ ├── Text2d.ts │ │ ├── convert.ts │ │ └── Sprite2d.ts │ ├── z_masks │ │ ├── MaskHacker.ts │ │ └── SpriteMaskFilter.ts │ ├── tiling │ │ ├── TilingSprite2d.ts │ │ └── TilingSprite2dRenderer.ts │ ├── Container2d.ts │ ├── Projection2d.ts │ ├── mesh │ │ └── Mesh2d.ts │ └── Matrix2d.ts └── utils.ts ├── .eslintrc.json ├── .editorconfig ├── scripts └── injectGlobalMixins.js ├── tsconfig.json ├── .gitignore ├── global.d.ts ├── spine ├── Spine3d.ts └── Spine2d.ts ├── LICENSE ├── package.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict = true 2 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import config from '@pixi-build-tools/rollup-configurator'; 2 | 3 | export default config; 4 | -------------------------------------------------------------------------------- /src/spine/index.ts: -------------------------------------------------------------------------------- 1 | export * from './base'; 2 | export * from './Spine2d'; 3 | export * from './Spine3d'; 4 | 5 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@pixi/eslint-config"], 3 | "rules": { 4 | "linebreak-style": ["off", "linux"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | trim_trailing_whitespace = true 6 | indent_style = space 7 | indent_size = 4 8 | 9 | [package.json] 10 | 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /scripts/injectGlobalMixins.js: -------------------------------------------------------------------------------- 1 | var prepend = require('prepend'); 2 | 3 | prepend('index.d.ts', '/// \n', function(error) { 4 | if (error) 5 | console.error(error.message); 6 | }); 7 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/triple-slash-reference,spaced-comment 2 | /// 3 | 4 | export * from './base'; 5 | export * from './utils'; 6 | export * from './proj2d'; 7 | export * from './proj3d'; 8 | 9 | export * from './curve'; 10 | export * from './spine'; 11 | -------------------------------------------------------------------------------- /src/base/index.ts: -------------------------------------------------------------------------------- 1 | import { extensions } from '@pixi/core'; 2 | import { Batch2dRenderer } from './webgl/Sprite2dRenderer'; 3 | 4 | export * from './AbstractProjection'; 5 | export * from './LinearProjection'; 6 | export * from './webgl/Sprite2dRenderer'; 7 | export * from './webgl/UniformBatchRenderer'; 8 | 9 | extensions.add(Batch2dRenderer); 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@pixi/extension-scripts/lib/configs/tsconfig.json", 3 | "compilerOptions": { 4 | "strict": false, 5 | "preserveConstEnums": true, 6 | "baseUrl": "./", 7 | "paths": { 8 | "pixi-projection": [ 9 | "src" 10 | ] 11 | } 12 | }, 13 | "include": [ 14 | "src/**/*" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/proj3d/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Point3d'; 2 | export * from './Euler'; 3 | export * from './ObservableEuler'; 4 | export * from './Matrix3d'; 5 | export * from './Projection3d'; 6 | export * from './Container3d'; 7 | export * from './Camera3d'; 8 | 9 | export * from './sprites/convert'; 10 | export * from './sprites/Sprite3d'; 11 | export * from './sprites/Text3d'; 12 | 13 | export * from './mesh/Mesh3d2d'; 14 | -------------------------------------------------------------------------------- /src/curve/index.ts: -------------------------------------------------------------------------------- 1 | import { extensions } from '@pixi/core'; 2 | import { BatchBilinearRenderer } from './SpriteBilinearRenderer'; 3 | 4 | export * from './BaseSurface'; 5 | export * from './BilinearSurface'; 6 | export * from './ProjectionSurface'; 7 | export * from './SpriteBilinearRenderer'; 8 | 9 | export * from './sprites/convert'; 10 | export * from './sprites/Sprite2s'; 11 | export * from './sprites/Text2s'; 12 | 13 | extensions.add(BatchBilinearRenderer); 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # sublime text files 2 | *.sublime* 3 | *.*~*.TMP 4 | 5 | # temp files 6 | .DS_Store 7 | Thumbs.db 8 | Desktop.ini 9 | npm-debug.log 10 | 11 | # project files 12 | .project 13 | 14 | # vim swap files 15 | *.sw* 16 | 17 | # emacs temp files 18 | *~ 19 | \#*# 20 | 21 | # project ignores 22 | !.gitkeep 23 | *__temp 24 | node_modules 25 | docs/ 26 | 27 | # jetBrains IDE ignores 28 | .idea 29 | 30 | # tsc output 31 | compile 32 | dist 33 | lib 34 | index.d.ts 35 | -------------------------------------------------------------------------------- /src/curve/Container2s.ts: -------------------------------------------------------------------------------- 1 | import { ProjectionSurface } from './ProjectionSurface'; 2 | import { Container } from '@pixi/display'; 3 | import { Matrix } from '@pixi/math'; 4 | 5 | export class Container2s extends Container 6 | { 7 | constructor() 8 | { 9 | super(); 10 | this.proj = new ProjectionSurface(this.transform); 11 | } 12 | 13 | proj: ProjectionSurface; 14 | 15 | get worldTransform(): Matrix 16 | { 17 | return this.proj as any; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/spine/base.ts: -------------------------------------------------------------------------------- 1 | import { Container } from '@pixi/display'; 2 | import { Sprite } from '@pixi/sprite'; 3 | import { Texture } from '@pixi/core'; 4 | import { Graphics } from '@pixi/graphics'; 5 | import { SimpleMesh } from '@pixi/mesh-extras'; 6 | 7 | export interface ISpineClass 8 | { 9 | newContainer(): Container; 10 | 11 | newSprite(tex: Texture): Sprite; 12 | 13 | newGraphics(): Graphics; 14 | 15 | newMesh(texture: Texture, vertices?: Float32Array, uvs?: Float32Array, 16 | indices?: Uint16Array, drawMode?: number): SimpleMesh; 17 | 18 | transformHack(): number; 19 | } 20 | -------------------------------------------------------------------------------- /src/proj2d/index.ts: -------------------------------------------------------------------------------- 1 | import { extensions } from '@pixi/core'; 2 | import { TilingSprite2dRenderer } from './tiling/TilingSprite2dRenderer'; 3 | 4 | export * from './Matrix2d'; 5 | export * from './Projection2d'; 6 | export * from './Container2d'; 7 | 8 | export * from './sprites/convert'; 9 | export * from './sprites/Sprite2d'; 10 | export * from './sprites/Text2d'; 11 | 12 | export * from './tiling/TilingSprite2d'; 13 | export * from './tiling/TilingSprite2dRenderer'; 14 | 15 | export * from './z_masks/MaskHacker'; 16 | export * from './z_masks/SpriteMaskFilter'; 17 | 18 | export * from './mesh/Mesh2d'; 19 | 20 | extensions.add(TilingSprite2dRenderer); 21 | -------------------------------------------------------------------------------- /global.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace GlobalMixins { 2 | export interface Transform { 3 | proj?: import('pixi-projection').AbstractProjection; 4 | } 5 | 6 | export interface Sprite { 7 | _texture: import('@pixi/core').Texture; 8 | convertTo2d?(): void; 9 | } 10 | 11 | export interface Container { 12 | convertTo2d?(): void; 13 | convertSubtreeTo2d?(): void; 14 | convertTo2s(): void; 15 | convertSubtreeTo2s(): void; 16 | convertTo3d?(): void; 17 | convertSubtreeTo3d?(): void; 18 | } 19 | 20 | export interface IPointData { 21 | z?: number; 22 | } 23 | 24 | export interface Point { 25 | set(x?: number, y?: number, z?: number): void; 26 | } 27 | 28 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 29 | export interface Spine extends Partial { 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/curve/sprites/Text2s.ts: -------------------------------------------------------------------------------- 1 | import { Text, TextStyle } from '@pixi/text'; 2 | import { ProjectionSurface } from '../ProjectionSurface'; 3 | import { Matrix } from '@pixi/math'; 4 | import { Sprite2s } from './Sprite2s'; 5 | 6 | export class Text2s extends Text 7 | { 8 | constructor(text?: string, style?: TextStyle, canvas?: HTMLCanvasElement) 9 | { 10 | super(text, style, canvas); 11 | this.proj = new ProjectionSurface(this.transform); 12 | this.pluginName = 'batch_bilinear'; 13 | } 14 | 15 | proj: ProjectionSurface; 16 | 17 | aTrans = new Matrix(); 18 | 19 | get worldTransform(): Matrix 20 | { 21 | return this.proj as any; 22 | } 23 | } 24 | 25 | (Text2s.prototype as any).calculateVertices = Sprite2s.prototype.calculateVertices; 26 | (Text2s.prototype as any).calculateTrimmedVertices = Sprite2s.prototype.calculateTrimmedVertices; 27 | (Text2s.prototype as any)._calculateBounds = Sprite2s.prototype._calculateBounds; 28 | -------------------------------------------------------------------------------- /src/proj2d/sprites/Text2d.ts: -------------------------------------------------------------------------------- 1 | import { Text, TextStyle } from '@pixi/text'; 2 | import { Projection2d } from '../Projection2d'; 3 | import { Sprite2d } from './Sprite2d'; 4 | import { Matrix } from '@pixi/math'; 5 | 6 | export class Text2d extends Text 7 | { 8 | constructor(text?: string, style?: TextStyle, canvas?: HTMLCanvasElement) 9 | { 10 | super(text, style, canvas); 11 | this.proj = new Projection2d(this.transform); 12 | this.pluginName = 'batch2d'; 13 | } 14 | 15 | proj: Projection2d; 16 | vertexData2d: Float32Array = null; 17 | 18 | get worldTransform(): Matrix 19 | { 20 | return this.proj.affine ? this.transform.worldTransform : this.proj.world as any; 21 | } 22 | } 23 | 24 | Text2d.prototype.calculateVertices = Sprite2d.prototype.calculateVertices; 25 | Text2d.prototype.calculateTrimmedVertices = Sprite2d.prototype.calculateTrimmedVertices; 26 | (Text2d.prototype as any)._calculateBounds = Sprite2d.prototype._calculateBounds; 27 | -------------------------------------------------------------------------------- /spine/Spine3d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | module pixi_projection { 3 | export interface Sprite3d { 4 | region: PIXI.spine.core.TextureRegion; 5 | } 6 | 7 | export interface SimpleMesh3d2d { 8 | region: PIXI.spine.core.TextureRegion; 9 | } 10 | 11 | export class Spine3d extends PIXI.spine.Spine { 12 | constructor(spineData: PIXI.spine.core.SkeletonData) { 13 | super(spineData); 14 | 15 | this.convertTo3d(); 16 | } 17 | 18 | proj: Projection2d; 19 | 20 | newContainer() { 21 | return new Container3d(); 22 | } 23 | 24 | newSprite(tex: PIXI.Texture) { 25 | return new Sprite3d(tex); 26 | } 27 | 28 | newGraphics() { 29 | //TODO: make Graphics3d 30 | return new PIXI.Graphics(); 31 | } 32 | 33 | newMesh(texture: PIXI.Texture, vertices?: Float32Array, uvs?: Float32Array, indices?: Uint16Array, drawMode?: number) { 34 | return new SimpleMesh3d2d(texture, vertices, uvs, indices, drawMode) as any; 35 | } 36 | 37 | transformHack() { 38 | return 3; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/proj2d/z_masks/MaskHacker.ts: -------------------------------------------------------------------------------- 1 | import { MaskData, MaskSystem } from '@pixi/core'; 2 | import type { Sprite } from '@pixi/sprite'; 3 | import { SpriteMaskFilter2d } from './SpriteMaskFilter'; 4 | 5 | const oldPushSpriteMask = MaskSystem.prototype.pushSpriteMask; 6 | 7 | function pushSpriteMaskOverride(maskData: MaskData): void 8 | { 9 | const { maskObject } = maskData; 10 | const origFilter = maskData._filters; 11 | 12 | if (!origFilter) 13 | { 14 | let alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; 15 | 16 | if (!alphaMaskFilter) 17 | { 18 | alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new SpriteMaskFilter2d(maskObject as Sprite)]; 19 | } 20 | maskData._filters = alphaMaskFilter; 21 | } 22 | oldPushSpriteMask.call(this, maskData); 23 | if (!origFilter) 24 | { 25 | maskData._filters = null; 26 | } 27 | } 28 | 29 | export function patchSpriteMask() 30 | { 31 | MaskSystem.prototype.pushSpriteMask = pushSpriteMaskOverride; 32 | } 33 | -------------------------------------------------------------------------------- /spine/Spine2d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import {Graphics} from "@pixi/graphics"; 3 | 4 | module pixi_projection { 5 | export interface Sprite2d { 6 | region: PIXI.spine.core.TextureRegion; 7 | } 8 | 9 | export interface Mesh2d { 10 | region: PIXI.spine.core.TextureRegion; 11 | } 12 | 13 | export class Spine2d extends PIXI.spine.Spine { 14 | constructor(spineData: PIXI.spine.core.SkeletonData) { 15 | super(spineData); 16 | 17 | this.convertTo2d(); 18 | } 19 | 20 | proj: Projection2d; 21 | 22 | newContainer() { 23 | return new Container2d(); 24 | } 25 | 26 | newSprite(tex: Texture) { 27 | return new Sprite2d(tex); 28 | } 29 | 30 | newGraphics() { 31 | //TODO: make Graphics2d 32 | const graphics = new Graphics(); 33 | graphics.convertTo2d(); 34 | return graphics; 35 | } 36 | 37 | newMesh(texture: PIXI.Texture, vertices?: Float32Array, uvs?: Float32Array, indices?: Uint16Array, drawMode?: number) { 38 | return new SimpleMesh2d(texture, vertices, uvs, indices, drawMode) as any; 39 | } 40 | 41 | transformHack() { 42 | return 2; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 PixiJS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/base/AbstractProjection.ts: -------------------------------------------------------------------------------- 1 | import { Transform } from '@pixi/math'; 2 | 3 | export class AbstractProjection 4 | { 5 | constructor(legacy: Transform, enable = true) 6 | { 7 | this.legacy = legacy; 8 | 9 | if (enable) 10 | { 11 | this.enabled = true; 12 | } 13 | 14 | // sorry for hidden class, it would be good to have special projection field in official pixi 15 | // TODO: pixi 6.1.0 global mixin 16 | (this.legacy as any).proj = this; 17 | } 18 | 19 | legacy: Transform; 20 | 21 | _enabled = false; 22 | 23 | get enabled(): boolean 24 | { 25 | return this._enabled; 26 | } 27 | 28 | set enabled(value: boolean) 29 | { 30 | this._enabled = value; 31 | } 32 | 33 | clear(): void 34 | // eslint-disable-next-line @typescript-eslint/no-empty-function 35 | { 36 | } 37 | } 38 | 39 | export enum TRANSFORM_STEP 40 | { 41 | NONE = 0, 42 | // POS = 1, 43 | // ROT = 2, 44 | // SCALE = 3, 45 | // PIVOT = 4, 46 | BEFORE_PROJ = 4, 47 | PROJ = 5, 48 | // POS_2 = 6, 49 | // ROT_2 = 7, 50 | // SCALE_2 = 8, 51 | // PIVOT_2 = 9, 52 | ALL = 9 53 | } 54 | -------------------------------------------------------------------------------- /src/spine/Spine2d.ts: -------------------------------------------------------------------------------- 1 | import { ISpineClass } from './base'; 2 | import { Graphics } from '@pixi/graphics'; 3 | import { Sprite2d, Container2d, SimpleMesh2d } from '../proj2d'; 4 | import { Texture } from '@pixi/core'; 5 | 6 | export function applySpine2dMixin(spineClassPrototype: ISpineClass): void 7 | { 8 | spineClassPrototype.newMesh = function newMesh(texture: Texture, vertices?: Float32Array, 9 | uvs?: Float32Array, indices?: Uint16Array, drawMode?: number) 10 | { 11 | return new SimpleMesh2d(texture, vertices, uvs, indices, drawMode) as any; 12 | }; 13 | spineClassPrototype.newContainer = function newMesh() 14 | { 15 | if (!this.proj) 16 | { 17 | this.convertTo2d(); 18 | } 19 | 20 | return new Container2d() as any; 21 | }; 22 | spineClassPrototype.newSprite = function newSprite(texture: Texture) 23 | { 24 | return new Sprite2d(texture); 25 | }; 26 | spineClassPrototype.newGraphics = function newMesh() 27 | { 28 | const graphics = new Graphics(); 29 | // TODO: make Graphics2d 30 | 31 | graphics.convertTo2d(); 32 | 33 | return graphics; 34 | }; 35 | spineClassPrototype.transformHack = function transformHack() 36 | { 37 | return 2; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/spine/Spine3d.ts: -------------------------------------------------------------------------------- 1 | import { ISpineClass } from './base'; 2 | import { Graphics } from '@pixi/graphics'; 3 | import { Sprite3d, Container3d, SimpleMesh3d2d } from '../proj3d'; 4 | import { Texture } from '@pixi/core'; 5 | 6 | export function applySpine3dMixin(spineClassPrototype: ISpineClass): void 7 | { 8 | spineClassPrototype.newMesh = function newMesh(texture: Texture, vertices?: Float32Array, 9 | uvs?: Float32Array, indices?: Uint16Array, drawMode?: number) 10 | { 11 | return new SimpleMesh3d2d(texture, vertices, uvs, indices, drawMode) as any; 12 | }; 13 | spineClassPrototype.newContainer = function newMesh() 14 | { 15 | if (!this.proj) 16 | { 17 | this.convertTo3d(); 18 | } 19 | 20 | return new Container3d() as any; 21 | }; 22 | spineClassPrototype.newSprite = function newSprite(texture: Texture) 23 | { 24 | return new Sprite3d(texture); 25 | }; 26 | spineClassPrototype.newGraphics = function newMesh() 27 | { 28 | const graphics = new Graphics(); 29 | // TODO: make Graphics2d 30 | 31 | graphics.convertTo3d(); 32 | 33 | return graphics; 34 | }; 35 | spineClassPrototype.transformHack = function transformHack() 36 | { 37 | return 2; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/curve/sprites/convert.ts: -------------------------------------------------------------------------------- 1 | import { Sprite } from '@pixi/sprite'; 2 | import { Container } from '@pixi/display'; 3 | import { Matrix } from '@pixi/math'; 4 | import { Sprite2s } from './Sprite2s'; 5 | import { ProjectionSurface } from '../ProjectionSurface'; 6 | 7 | Sprite.prototype.convertTo2s = function spriteConvertTo2s() 8 | { 9 | if (this.proj) return; 10 | // container 11 | this.pluginName = 'sprite_bilinear'; 12 | this.aTrans = new Matrix(); 13 | this.calculateVertices = Sprite2s.prototype.calculateVertices; 14 | this.calculateTrimmedVertices = Sprite2s.prototype.calculateTrimmedVertices; 15 | this._calculateBounds = Sprite2s.prototype._calculateBounds; 16 | Container.prototype.convertTo2s.call(this); 17 | }; 18 | 19 | Container.prototype.convertTo2s = function convertTo2s() 20 | { 21 | if (this.proj) return; 22 | this.proj = new ProjectionSurface(this.transform); 23 | Object.defineProperty(this, 'worldTransform', { 24 | get() 25 | { 26 | return this.proj; 27 | }, 28 | enumerable: true, 29 | configurable: true 30 | }); 31 | }; 32 | 33 | Container.prototype.convertSubtreeTo2s = function convertSubtreeTo2s() 34 | { 35 | this.convertTo2s(); 36 | for (let i = 0; i < this.children.length; i++) 37 | { 38 | this.children[i].convertSubtreeTo2s(); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /src/proj3d/Camera3d.ts: -------------------------------------------------------------------------------- 1 | import { Container3d } from './Container3d'; 2 | 3 | export class Camera3d extends Container3d 4 | { 5 | constructor() 6 | { 7 | super(); 8 | this.proj.cameraMode = true; 9 | this.setPlanes(400, 10, 10000, false); 10 | } 11 | 12 | _far = 0; 13 | _near = 0; 14 | _focus = 0; 15 | _orthographic = false; 16 | 17 | get far(): number 18 | { 19 | return this._far; 20 | } 21 | 22 | get near(): number 23 | { 24 | return this._near; 25 | } 26 | 27 | get focus(): number 28 | { 29 | return this._focus; 30 | } 31 | 32 | get ortographic(): boolean 33 | { 34 | return this._orthographic; 35 | } 36 | 37 | setPlanes(focus: number, near = 10, far = 10000, orthographic = false): void 38 | { 39 | this._focus = focus; 40 | this._near = near; 41 | this._far = far; 42 | this._orthographic = orthographic; 43 | 44 | const proj = this.proj; 45 | const mat4 = proj.cameraMatrix.mat4; 46 | 47 | proj._projID++; 48 | 49 | mat4[10] = 1.0 / (far - near); 50 | mat4[14] = (focus - near) / (far - near); 51 | if (this._orthographic) 52 | { 53 | mat4[11] = 0; 54 | } 55 | else 56 | { 57 | mat4[11] = 1.0 / focus; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-mixed-operators */ 2 | import { IPointData, Point } from '@pixi/math'; 3 | 4 | // eslint-disable-next-line max-len 5 | export function getIntersectionFactor(p1: IPointData, p2: IPointData, p3: IPointData, p4: IPointData, out: IPointData): number 6 | { 7 | const A1 = p2.x - p1.x; const B1 = p3.x - p4.x; 8 | const C1 = p3.x - p1.x; 9 | const A2 = p2.y - p1.y; const B2 = p3.y - p4.y; 10 | const C2 = p3.y - p1.y; 11 | const D = A1 * B2 - A2 * B1; 12 | 13 | if (Math.abs(D) < 1e-7) 14 | { 15 | out.x = A1; 16 | out.y = A2; 17 | 18 | return 0; 19 | } 20 | const T = C1 * B2 - C2 * B1; 21 | const U = A1 * C2 - A2 * C1; 22 | 23 | const t = T / D; const 24 | u = U / D; 25 | 26 | if (u < (1e-6) || u - 1 > -1e-6) 27 | { 28 | return -1; 29 | } 30 | 31 | out.x = p1.x + t * (p2.x - p1.x); 32 | out.y = p1.y + t * (p2.y - p1.y); 33 | 34 | return 1; 35 | } 36 | 37 | export function getPositionFromQuad(p: Array, 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 | [![npm version](https://badge.fury.io/js/pixi-projection.svg)](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 | --------------------------------------------------------------------------------