From 06f59b2a07d7eead87a4f0eab47777f8dcffe0c9 Mon Sep 17 00:00:00 2001 From: fanmingfei Date: Mon, 16 Aug 2021 22:54:27 +0800 Subject: [PATCH 1/9] feat: init particles --- packages/plugin-renderer-particles/README.md | 11 ++++++++ .../plugin-renderer-particles.test.js | 7 +++++ .../api-extractor.json | 7 +++++ packages/plugin-renderer-particles/index.js | 7 +++++ .../plugin-renderer-particles/lib/index.js | 0 .../plugin-renderer-particles/package.json | 26 +++++++++++++++++++ 6 files changed, 58 insertions(+) create mode 100644 packages/plugin-renderer-particles/README.md create mode 100644 packages/plugin-renderer-particles/__tests__/plugin-renderer-particles.test.js create mode 100644 packages/plugin-renderer-particles/api-extractor.json create mode 100644 packages/plugin-renderer-particles/index.js create mode 100644 packages/plugin-renderer-particles/lib/index.js create mode 100644 packages/plugin-renderer-particles/package.json diff --git a/packages/plugin-renderer-particles/README.md b/packages/plugin-renderer-particles/README.md new file mode 100644 index 00000000..319316dc --- /dev/null +++ b/packages/plugin-renderer-particles/README.md @@ -0,0 +1,11 @@ +# `plugin-renderer-particles` + +> TODO: description + +## Usage + +``` +const pluginRendererParticles = require('plugin-renderer-particles'); + +// TODO: DEMONSTRATE API +``` diff --git a/packages/plugin-renderer-particles/__tests__/plugin-renderer-particles.test.js b/packages/plugin-renderer-particles/__tests__/plugin-renderer-particles.test.js new file mode 100644 index 00000000..0d768136 --- /dev/null +++ b/packages/plugin-renderer-particles/__tests__/plugin-renderer-particles.test.js @@ -0,0 +1,7 @@ +'use strict'; + +const pluginRendererParticles = require('..'); + +describe('plugin-renderer-particles', () => { + it('needs tests'); +}); diff --git a/packages/plugin-renderer-particles/api-extractor.json b/packages/plugin-renderer-particles/api-extractor.json new file mode 100644 index 00000000..44d31feb --- /dev/null +++ b/packages/plugin-renderer-particles/api-extractor.json @@ -0,0 +1,7 @@ +{ + "extends": "../../api-extractor.json", + "mainEntryPointFilePath": "./dist/packages/plugin-renderer-particles/lib/index.d.ts", + "dtsRollup": { + "publicTrimmedFilePath": "./dist/plugin-renderer-particles.d.ts" + } +} \ No newline at end of file diff --git a/packages/plugin-renderer-particles/index.js b/packages/plugin-renderer-particles/index.js new file mode 100644 index 00000000..c552f510 --- /dev/null +++ b/packages/plugin-renderer-particles/index.js @@ -0,0 +1,7 @@ +'use strict' + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./dist/plugin-renderer-particles.cjs.prod.js') +} else { + module.exports = require('./dist/plugin-renderer-particles.cjs.js') +} diff --git a/packages/plugin-renderer-particles/lib/index.js b/packages/plugin-renderer-particles/lib/index.js new file mode 100644 index 00000000..e69de29b diff --git a/packages/plugin-renderer-particles/package.json b/packages/plugin-renderer-particles/package.json new file mode 100644 index 00000000..a7d85afb --- /dev/null +++ b/packages/plugin-renderer-particles/package.json @@ -0,0 +1,26 @@ +{ + "name": "@eva/plugin-renderer-particles", + "version": "1.1.2-alpha.0", + "description": "@eva/plugin-renderer-particles", + "main": "index.js", + "module": "dist/plugin-renderer-particles.esm.js", + "bundle": "EVA.plugin.renderer.particles", + "unpkg": "dist/EVA.plugin.renderer.particles.min.js", + "files": [ + "index.js", + "dist" + ], + "types": "dist/plugin-renderer-particles.d.ts", + "keywords": [ + "eva.js" + ], + "author": "fanmingfei ", + "license": "MIT", + "homepage": "https://eva.js.org", + "dependencies": { + "@eva/inspector-decorator": "^0.0.4", + "@eva/plugin-renderer": "1.1.2-alpha.0", + "@eva/renderer-adapter": "1.1.2-alpha.0", + "@eva/eva.js": "1.1.2-alpha.0" + } +} From 1766ad420dd6ffbd5df4daf394f87313d9fdf877 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E9=A3=9E=E7=8C=AB?= Date: Thu, 19 Aug 2021 14:56:06 +0800 Subject: [PATCH 2/9] Feat/particles (#108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat:提交粒子插件example * feat:提交粒子插件代码 Co-authored-by: 青藕 --- examples/public/bubbles.json | 47 +++++++ examples/src/particles.ts | 52 +++++++ examples/vite.config.ts | 1 - packages/plugin-renderer-particles/README.md | 13 +- .../lib/component.ts | 129 ++++++++++++++++++ .../plugin-renderer-particles/lib/index.js | 0 .../plugin-renderer-particles/lib/index.ts | 4 + .../plugin-renderer-particles/lib/system.ts | 59 ++++++++ .../plugin-renderer-particles/package.json | 13 +- 9 files changed, 302 insertions(+), 16 deletions(-) create mode 100644 examples/public/bubbles.json create mode 100644 examples/src/particles.ts create mode 100644 packages/plugin-renderer-particles/lib/component.ts delete mode 100644 packages/plugin-renderer-particles/lib/index.js create mode 100644 packages/plugin-renderer-particles/lib/index.ts create mode 100644 packages/plugin-renderer-particles/lib/system.ts diff --git a/examples/public/bubbles.json b/examples/public/bubbles.json new file mode 100644 index 00000000..ab6a55ec --- /dev/null +++ b/examples/public/bubbles.json @@ -0,0 +1,47 @@ +{ + "alpha": { + "start": 1, + "end": 0.22 + }, + "scale": { + "start": 0.25, + "end": 0.5, + "minimumScaleMultiplier": 0.5 + }, + "color": { + "start": "ffffff", + "end": "ffffff" + }, + "speed": { + "start": 200, + "end": 200 + }, + "startRotation": { + "min": 260, + "max": 280 + }, + "rotationSpeed": { + "min": 0, + "max": 50 + }, + "lifetime": { + "min": 3.5, + "max": 4 + }, + "blendMode": "normal", + "frequency": 0.016, + "emitterLifetime": 0, + "maxParticles": 50, + "pos": { + "x": 0, + "y": 0 + }, + "addAtBack": false, + "spawnType": "rect", + "spawnRect": { + "x": -450, + "y": 200, + "w": 900, + "h": 0 + } +} \ No newline at end of file diff --git a/examples/src/particles.ts b/examples/src/particles.ts new file mode 100644 index 00000000..622b53b9 --- /dev/null +++ b/examples/src/particles.ts @@ -0,0 +1,52 @@ +import { RendererSystem } from "@eva/plugin-renderer"; +import { Game, GameObject, resource, RESOURCE_TYPE } from "@eva/eva.js"; +import { Particles, ParticleSystem } from "@eva/plugin-renderer-particles"; +export const name = 'image'; +export async function init(canvas) { + resource.addResource([ + { + name: 'bubbles', + type: RESOURCE_TYPE['Particles'] || 'Particles', + src: { + img_0: { + type: 'png', + url: 'https://gw.alicdn.com/imgextra/i2/O1CN01Fi8ma31eWAcqY8pXg_!!6000000003878-2-tps-99-99.png', + }, + json: { + type: "json", + url: "./public/bubbles.json" + } + }, + preload: true, + }, + ]); + + const game = new Game({ + systems: [ + new RendererSystem({ + canvas, + width: window.innerWidth, + height: window.innerHeight - 200, + }), + new ParticleSystem(), + ], + }); + + const particlesObject = new GameObject('particlesObject', { + size: { width: 750, height: 1319 }, + origin: { x: 0, y: 0 }, + position: { x: window.innerWidth / 2, y: window.innerHeight / 3 }, + anchor: { + x: 0, + y: 0, + }, + }); + + let emitter = particlesObject.addComponent( + new Particles({ + resource: 'bubbles', + }), + ); + game.scene.addChild(particlesObject); + emitter.play() +} \ No newline at end of file diff --git a/examples/vite.config.ts b/examples/vite.config.ts index 9a46093e..b0c29ff7 100644 --- a/examples/vite.config.ts +++ b/examples/vite.config.ts @@ -12,7 +12,6 @@ for (const path of paths) { }) } } - export default defineConfig({ server: { open: true, diff --git a/packages/plugin-renderer-particles/README.md b/packages/plugin-renderer-particles/README.md index 319316dc..ee5d9ae3 100644 --- a/packages/plugin-renderer-particles/README.md +++ b/packages/plugin-renderer-particles/README.md @@ -1,11 +1,6 @@ -# `plugin-renderer-particles` +# @eva/plugin-renderer-particles -> TODO: description +More Introduction -## Usage - -``` -const pluginRendererParticles = require('plugin-renderer-particles'); - -// TODO: DEMONSTRATE API -``` +- [EN](https://eva.js.org) +- [中文](https://eva-engine.gitee.io) diff --git a/packages/plugin-renderer-particles/lib/component.ts b/packages/plugin-renderer-particles/lib/component.ts new file mode 100644 index 00000000..7ef855c8 --- /dev/null +++ b/packages/plugin-renderer-particles/lib/component.ts @@ -0,0 +1,129 @@ +import { Component, resource } from '@eva/eva.js'; +import { + Emitter, + LinkedListContainer, + ParticleUtils, + PathParticle, + AnimatedParticle +} from 'pixi-particles' + +import { Container } from '@eva/renderer-adapter'; + + +interface ParticleParams { + resource: string, + type?: 'anim' | 'path', + colorStep?: number +} + +export default class ParticleComponent extends Component { + static componentName: string = 'ParticleComponent'; + emitter: Emitter; + updateHook: any; + stage: any; + colorStep: any; + _resource: string; + type: 'anim' | 'path'; + launched: boolean = false; + ready: boolean = false; + private times + set resource(val) { + if (val === this._resource) return + this._resource = val + this.stage?.removeChild() + this.emitter?.destroy() + this.emitter = undefined + this.ready = false + this.launched = false + } + get resource() { + return this._resource + } + init( + { + resource, + type, + colorStep + } + ) { + this.updateHook = null; + this.stage = null; + this.resource = resource; + this.colorStep = colorStep; + this.type = type; + } + /** + * 设置发射器的Container + * @param stage Container + */ + async setStage(stage?: Container) { + this.stage = stage || this.stage; + const container = this.getContainer(0) as Container; + this.stage.addChild(container); + + const { instance, data } = await resource.getResource(this.resource) + this.emitter = new Emitter(container, instance, data.json); + if (this.colorStep) { + this.emitter.startColor = ParticleUtils.createSteppedGradient( + data.json.color.list, + this.colorStep + ); + } + if (this.type === 'path') { + this.emitter.particleConstructor = PathParticle; + } else if (this.type === 'anim') { + this.emitter.particleConstructor = AnimatedParticle; + } + this.ready && this.play() + } + getContainer(parentType: number) { + switch (parentType) { + case 1: + return new LinkedListContainer(); + default: + return new Container() + } + } + play(times: number = 1) { + if (!this.emitter) { + this.ready = true + this.times = times + return; + } + this.launched = true; + let currentTimes = 0; + const loop = () => { + currentTimes++ + if (currentTimes < this.times) { + this.emitter.playOnce(loop) + } + } + this.emitter.playOnce(loop) + } + update(e) { + if (!this.launched) return + this.emitter && this.emitter.update(e.deltaTime * 0.001) + this.updateHook && this.emitter && this.updateHook(e.deltaTime); + } + change() { + this.setStage() + } + /** + * 停止粒子活动 + * @param clear 是否在画布中清除所有粒子 + */ + pause(clear: boolean = false) { + if (clear) { + this.emitter.destroy() + return; + } + this.launched = false; + } + resume() { + this.launched = true + } + + destroy() { + this.emitter.destroy() + } +} diff --git a/packages/plugin-renderer-particles/lib/index.js b/packages/plugin-renderer-particles/lib/index.js deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/plugin-renderer-particles/lib/index.ts b/packages/plugin-renderer-particles/lib/index.ts new file mode 100644 index 00000000..ff5105f2 --- /dev/null +++ b/packages/plugin-renderer-particles/lib/index.ts @@ -0,0 +1,4 @@ +import Particles from './component'; +import ParticleSystem from './system'; + +export { Particles, ParticleSystem }; \ No newline at end of file diff --git a/packages/plugin-renderer-particles/lib/system.ts b/packages/plugin-renderer-particles/lib/system.ts new file mode 100644 index 00000000..51e6de52 --- /dev/null +++ b/packages/plugin-renderer-particles/lib/system.ts @@ -0,0 +1,59 @@ +import { + decorators, + OBSERVER_TYPE, + ComponentChanged, + resource +} from '@eva/eva.js'; + +import { + ContainerManager, + Renderer, + RendererSystem, + RendererManager +} from '@eva/plugin-renderer'; +import { Texture } from 'pixi.js'; +import ParticleComponent from './component'; + + +resource.registerInstance('Particles', ({ data }) => { + try { + const textures = [] + for (const key in data) { + if (key.indexOf('img_') === 0) { + textures.push(Texture.from(data[key])) + } + } + console.log(data, textures) + return textures + } catch (e) { console.log(e) } +}) + +@decorators.componentObserver({ + ParticleComponent: ['_resource'], +}) +class ParticleSystem extends Renderer { + static readonly systemName: string = 'ParticleSystem'; + containerManager: ContainerManager; + renderSystem: RendererSystem; + rendererManager: RendererManager; + init() { + this.renderSystem = this.game.getSystem(RendererSystem) as RendererSystem; + this.renderSystem.rendererManager.register(this); + } + async componentChanged(changed: ComponentChanged) { + if (changed.componentName === 'ParticleComponent') { + const component: ParticleComponent = changed.component as ParticleComponent; + if (changed.type === OBSERVER_TYPE.ADD) { + const container = this.containerManager + .getContainer(changed.gameObject.id); + component.setStage(container); + } else if (changed.type === OBSERVER_TYPE.CHANGE) { + component.change && component.change() + } else if (changed.type === OBSERVER_TYPE.REMOVE) { + component.destroy && component.destroy(); + } + } + } + +} +export default ParticleSystem; \ No newline at end of file diff --git a/packages/plugin-renderer-particles/package.json b/packages/plugin-renderer-particles/package.json index a7d85afb..157ca67d 100644 --- a/packages/plugin-renderer-particles/package.json +++ b/packages/plugin-renderer-particles/package.json @@ -1,6 +1,6 @@ { "name": "@eva/plugin-renderer-particles", - "version": "1.1.2-alpha.0", + "version": "1.0.0-alpha.0", "description": "@eva/plugin-renderer-particles", "main": "index.js", "module": "dist/plugin-renderer-particles.esm.js", @@ -18,9 +18,10 @@ "license": "MIT", "homepage": "https://eva.js.org", "dependencies": { - "@eva/inspector-decorator": "^0.0.4", - "@eva/plugin-renderer": "1.1.2-alpha.0", - "@eva/renderer-adapter": "1.1.2-alpha.0", - "@eva/eva.js": "1.1.2-alpha.0" + "@eva/eva.js": "^1.1.1", + "@eva/plugin-renderer": "^1.1.1", + "@eva/renderer-adapter": "^1.1.1", + "pixi-particles": "^4.3.1", + "pixi.js": "^4.8.9" } -} +} \ No newline at end of file From aacbbf8d81e82297d139ad48efeb81aad5009c0e Mon Sep 17 00:00:00 2001 From: fanmingfei Date: Thu, 19 Aug 2021 15:21:22 +0800 Subject: [PATCH 3/9] feat: particles error --- examples/src/particles.ts | 2 +- packages/eva.js/lib/loader/Resource.ts | 2 + .../plugin-renderer-dragonbone/package.json | 2 +- packages/plugin-renderer-event/package.json | 2 +- packages/plugin-renderer-img/package.json | 2 +- packages/plugin-renderer-lottie/package.json | 2 +- packages/plugin-renderer-mask/package.json | 2 +- .../lib/component.ts | 4 +- .../lib/pixi-particles.js | 2697 +++++++++++++++++ .../plugin-renderer-particles/lib/system.ts | 5 +- .../plugin-renderer-particles/package.json | 1 - packages/plugin-renderer-render/package.json | 2 +- packages/plugin-renderer-spine/package.json | 2 +- packages/plugin-renderer-spine38/package.json | 2 +- .../package.json | 2 +- packages/plugin-renderer-sprite/package.json | 2 +- packages/plugin-renderer-text/package.json | 2 +- packages/plugin-renderer/package.json | 2 +- 18 files changed, 2717 insertions(+), 18 deletions(-) create mode 100644 packages/plugin-renderer-particles/lib/pixi-particles.js diff --git a/examples/src/particles.ts b/examples/src/particles.ts index 622b53b9..f9e347c6 100644 --- a/examples/src/particles.ts +++ b/examples/src/particles.ts @@ -6,7 +6,7 @@ export async function init(canvas) { resource.addResource([ { name: 'bubbles', - type: RESOURCE_TYPE['Particles'] || 'Particles', + type: RESOURCE_TYPE.PARTICLES, src: { img_0: { type: 'png', diff --git a/packages/eva.js/lib/loader/Resource.ts b/packages/eva.js/lib/loader/Resource.ts index d8f3e3e3..65e13eb7 100644 --- a/packages/eva.js/lib/loader/Resource.ts +++ b/packages/eva.js/lib/loader/Resource.ts @@ -17,6 +17,8 @@ export enum RESOURCE_TYPE { 'SPRITE' = 'SPRITE', 'SPRITE_ANIMATION' = 'SPRITE_ANIMATION', 'DRAGONBONE' = 'DRAGONBONE', + 'PARTICLES' = 'PARTICLES', + 'LOTTIE' = 'LOTTIE', 'SPINE' = 'SPINE', 'AUDIO' = 'AUDIO', 'VIDEO' = 'VIDEO', diff --git a/packages/plugin-renderer-dragonbone/package.json b/packages/plugin-renderer-dragonbone/package.json index a929ba1d..d9cd60e2 100644 --- a/packages/plugin-renderer-dragonbone/package.json +++ b/packages/plugin-renderer-dragonbone/package.json @@ -21,6 +21,6 @@ "@eva/inspector-decorator": "^0.0.4", "@eva/plugin-renderer": "1.1.2-alpha.0", "@eva/eva.js": "1.1.2-alpha.0", - "pixi.js": "^4.8.7" + "pixi.js": "^4.8.9" } } diff --git a/packages/plugin-renderer-event/package.json b/packages/plugin-renderer-event/package.json index f467898b..ef6b48d0 100644 --- a/packages/plugin-renderer-event/package.json +++ b/packages/plugin-renderer-event/package.json @@ -20,6 +20,6 @@ "dependencies": { "@eva/plugin-renderer": "1.1.2-alpha.0", "@eva/eva.js": "1.1.2-alpha.0", - "pixi.js": "^4.8.7" + "pixi.js": "^4.8.9" } } diff --git a/packages/plugin-renderer-img/package.json b/packages/plugin-renderer-img/package.json index 99b07a41..e4c10cee 100644 --- a/packages/plugin-renderer-img/package.json +++ b/packages/plugin-renderer-img/package.json @@ -22,6 +22,6 @@ "@eva/plugin-renderer": "1.1.2-alpha.0", "@eva/renderer-adapter": "1.1.2-alpha.0", "@eva/eva.js": "1.1.2-alpha.0", - "pixi.js": "^4.8.7" + "pixi.js": "^4.8.9" } } diff --git a/packages/plugin-renderer-lottie/package.json b/packages/plugin-renderer-lottie/package.json index 9294b6c4..462ba07c 100644 --- a/packages/plugin-renderer-lottie/package.json +++ b/packages/plugin-renderer-lottie/package.json @@ -21,6 +21,6 @@ "@eva/eva.js": "1.1.2-alpha.0", "@eva/plugin-renderer": "1.1.2-alpha.0", "ios-version": "^1.0.1", - "pixi.js": "^4.8.7" + "pixi.js": "^4.8.9" } } diff --git a/packages/plugin-renderer-mask/package.json b/packages/plugin-renderer-mask/package.json index b8495d33..d8c30d51 100644 --- a/packages/plugin-renderer-mask/package.json +++ b/packages/plugin-renderer-mask/package.json @@ -22,6 +22,6 @@ "@eva/plugin-renderer": "1.1.2-alpha.0", "@eva/renderer-adapter": "1.1.2-alpha.0", "@eva/eva.js": "1.1.2-alpha.0", - "pixi.js": "^4.8.7" + "pixi.js": "^4.8.9" } } diff --git a/packages/plugin-renderer-particles/lib/component.ts b/packages/plugin-renderer-particles/lib/component.ts index 7ef855c8..6415308a 100644 --- a/packages/plugin-renderer-particles/lib/component.ts +++ b/packages/plugin-renderer-particles/lib/component.ts @@ -5,7 +5,7 @@ import { ParticleUtils, PathParticle, AnimatedParticle -} from 'pixi-particles' +} from './pixi-particles.js' import { Container } from '@eva/renderer-adapter'; @@ -18,7 +18,7 @@ interface ParticleParams { export default class ParticleComponent extends Component { static componentName: string = 'ParticleComponent'; - emitter: Emitter; + emitter: any; updateHook: any; stage: any; colorStep: any; diff --git a/packages/plugin-renderer-particles/lib/pixi-particles.js b/packages/plugin-renderer-particles/lib/pixi-particles.js new file mode 100644 index 00000000..4c256c39 --- /dev/null +++ b/packages/plugin-renderer-particles/lib/pixi-particles.js @@ -0,0 +1,2697 @@ +/*! + * pixi-particles - v4.3.1 + * Compiled Wed, 09 Jun 2021 13:12:54 UTC + * + * pixi-particles is licensed under the MIT License. + * http://www.opensource.org/licenses/mit-license + */ +import * as pixi from 'pixi.js'; +import { VERSION, BLEND_MODES, Texture, Point, Sprite, Circle, Rectangle, settings, DisplayObject, Container } from 'pixi.js'; + +/** + * A single node in a PropertyList. + */ +var PropertyNode = /** @class */ (function () { + /** + * @param value The value for this node + * @param time The time for this node, between 0-1 + * @param [ease] Custom ease for this list. Only relevant for the first node. + */ + function PropertyNode(value, time, ease) { + this.value = value; + this.time = time; + this.next = null; + this.isStepped = false; + if (ease) { + this.ease = typeof ease === 'function' ? ease : ParticleUtils.generateEase(ease); + } + else { + this.ease = null; + } + } + /** + * Creates a list of property values from a data object {list, isStepped} with a list of objects in + * the form {value, time}. Alternatively, the data object can be in the deprecated form of + * {start, end}. + * @param data The data for the list. + * @param data.list The array of value and time objects. + * @param data.isStepped If the list is stepped rather than interpolated. + * @param data.ease Custom ease for this list. + * @return The first node in the list + */ + // eslint-disable-next-line max-len + PropertyNode.createList = function (data) { + if ('list' in data) { + var array = data.list; + var node = void 0; + var _a = array[0], value = _a.value, time = _a.time; + // eslint-disable-next-line max-len + var first = node = new PropertyNode(typeof value === 'string' ? ParticleUtils.hexToRGB(value) : value, time, data.ease); + // only set up subsequent nodes if there are a bunch or the 2nd one is different from the first + if (array.length > 2 || (array.length === 2 && array[1].value !== value)) { + for (var i = 1; i < array.length; ++i) { + var _b = array[i], value_1 = _b.value, time_1 = _b.time; + node.next = new PropertyNode(typeof value_1 === 'string' ? ParticleUtils.hexToRGB(value_1) : value_1, time_1); + node = node.next; + } + } + first.isStepped = !!data.isStepped; + return first; + } + // Handle deprecated version here + var start = new PropertyNode(typeof data.start === 'string' ? ParticleUtils.hexToRGB(data.start) : data.start, 0); + // only set up a next value if it is different from the starting value + if (data.end !== data.start) { + start.next = new PropertyNode(typeof data.end === 'string' ? ParticleUtils.hexToRGB(data.end) : data.end, 1); + } + return start; + }; + return PropertyNode; +}()); + +// get Texture.from()/Texture.fromImage(), in V4 and V5 friendly methods +/** + * @hidden + */ +var TextureFromString; +// to avoid Rollup transforming our import, save pixi namespace in a variable +var pixiNS = pixi; +if (parseInt((/^(\d+)\./).exec(VERSION)[1], 10) < 5) { + TextureFromString = pixiNS.Texture.fromImage; +} +else { + TextureFromString = pixiNS.Texture.from; +} +function GetTextureFromString(s) { + return TextureFromString(s); +} +/** + * Contains helper functions for particles and emitters to use. + */ +var ParticleUtils; +(function (ParticleUtils) { + /** + * If errors and warnings should be logged within the library. + */ + ParticleUtils.verbose = false; + ParticleUtils.DEG_TO_RADS = Math.PI / 180; + /** + * Rotates a point by a given angle. + * @param angle The angle to rotate by in degrees + * @param p The point to rotate around 0,0. + */ + function rotatePoint(angle, p) { + if (!angle) + return; + angle *= ParticleUtils.DEG_TO_RADS; + var s = Math.sin(angle); + var c = Math.cos(angle); + var xnew = (p.x * c) - (p.y * s); + var ynew = (p.x * s) + (p.y * c); + p.x = xnew; + p.y = ynew; + } + ParticleUtils.rotatePoint = rotatePoint; + /** + * Combines separate color components (0-255) into a single uint color. + * @param r The red value of the color + * @param g The green value of the color + * @param b The blue value of the color + * @return The color in the form of 0xRRGGBB + */ + function combineRGBComponents(r, g, b /* , a*/) { + return /* a << 24 |*/ (r << 16) | (g << 8) | b; + } + ParticleUtils.combineRGBComponents = combineRGBComponents; + /** + * Reduces the point to a length of 1. + * @param point The point to normalize + */ + function normalize(point) { + var oneOverLen = 1 / ParticleUtils.length(point); + point.x *= oneOverLen; + point.y *= oneOverLen; + } + ParticleUtils.normalize = normalize; + /** + * Multiplies the x and y values of this point by a value. + * @param point The point to scaleBy + * @param value The value to scale by. + */ + function scaleBy(point, value) { + point.x *= value; + point.y *= value; + } + ParticleUtils.scaleBy = scaleBy; + /** + * Returns the length (or magnitude) of this point. + * @param point The point to measure length + * @return The length of this point. + */ + function length(point) { + return Math.sqrt((point.x * point.x) + (point.y * point.y)); + } + ParticleUtils.length = length; + /** + * Converts a hex string from "#AARRGGBB", "#RRGGBB", "0xAARRGGBB", "0xRRGGBB", + * "AARRGGBB", or "RRGGBB" to an object of ints of 0-255, as + * {r, g, b, (a)}. + * @param color The input color string. + * @param output An object to put the output in. If omitted, a new object is created. + * @return The object with r, g, and b properties, possibly with an a property. + */ + function hexToRGB(color, output) { + if (!output) { + output = {}; + } + if (color.charAt(0) === '#') { + color = color.substr(1); + } + else if (color.indexOf('0x') === 0) { + color = color.substr(2); + } + var alpha; + if (color.length === 8) { + alpha = color.substr(0, 2); + color = color.substr(2); + } + output.r = parseInt(color.substr(0, 2), 16); // Red + output.g = parseInt(color.substr(2, 2), 16); // Green + output.b = parseInt(color.substr(4, 2), 16); // Blue + if (alpha) { + output.a = parseInt(alpha, 16); + } + return output; + } + ParticleUtils.hexToRGB = hexToRGB; + /** + * Generates a custom ease function, based on the GreenSock custom ease, as demonstrated + * by the related tool at http://www.greensock.com/customease/. + * @param segments An array of segments, as created by + * http://www.greensock.com/customease/. + * @return A function that calculates the percentage of change at + * a given point in time (0-1 inclusive). + */ + function generateEase(segments) { + var qty = segments.length; + var oneOverQty = 1 / qty; + /* + * Calculates the percentage of change at a given point in time (0-1 inclusive). + * @param {Number} time The time of the ease, 0-1 inclusive. + * @return {Number} The percentage of the change, 0-1 inclusive (unless your + * ease goes outside those bounds). + */ + // eslint-disable-next-line func-names + return function (time) { + var i = (qty * time) | 0; // do a quick floor operation + var t = (time - (i * oneOverQty)) * qty; + var s = segments[i] || segments[qty - 1]; + return (s.s + (t * ((2 * (1 - t) * (s.cp - s.s)) + (t * (s.e - s.s))))); + }; + } + ParticleUtils.generateEase = generateEase; + /** + * Gets a blend mode, ensuring that it is valid. + * @param name The name of the blend mode to get. + * @return The blend mode as specified in the PIXI.BLEND_MODES enumeration. + */ + function getBlendMode(name) { + if (!name) + return BLEND_MODES.NORMAL; + name = name.toUpperCase(); + while (name.indexOf(' ') >= 0) { + name = name.replace(' ', '_'); + } + return BLEND_MODES[name] || BLEND_MODES.NORMAL; + } + ParticleUtils.getBlendMode = getBlendMode; + /** + * Converts a list of {value, time} objects starting at time 0 and ending at time 1 into an evenly + * spaced stepped list of PropertyNodes for color values. This is primarily to handle conversion of + * linear gradients to fewer colors, allowing for some optimization for Canvas2d fallbacks. + * @param list The list of data to convert. + * @param [numSteps=10] The number of steps to use. + * @return The blend mode as specified in the PIXI.blendModes enumeration. + */ + function createSteppedGradient(list, numSteps) { + if (numSteps === void 0) { numSteps = 10; } + if (typeof numSteps !== 'number' || numSteps <= 0) { + numSteps = 10; + } + var first = new PropertyNode(ParticleUtils.hexToRGB(list[0].value), list[0].time); + first.isStepped = true; + var currentNode = first; + var current = list[0]; + var nextIndex = 1; + var next = list[nextIndex]; + for (var i = 1; i < numSteps; ++i) { + var lerp = i / numSteps; + // ensure we are on the right segment, if multiple + while (lerp > next.time) { + current = next; + next = list[++nextIndex]; + } + // convert the lerp value to the segment range + lerp = (lerp - current.time) / (next.time - current.time); + var curVal = ParticleUtils.hexToRGB(current.value); + var nextVal = ParticleUtils.hexToRGB(next.value); + var output = { + r: ((nextVal.r - curVal.r) * lerp) + curVal.r, + g: ((nextVal.g - curVal.g) * lerp) + curVal.g, + b: ((nextVal.b - curVal.b) * lerp) + curVal.b, + }; + currentNode.next = new PropertyNode(output, i / numSteps); + currentNode = currentNode.next; + } + // we don't need to have a PropertyNode for time of 1, because in a stepped version at that point + // the particle has died of old age + return first; + } + ParticleUtils.createSteppedGradient = createSteppedGradient; +})(ParticleUtils || (ParticleUtils = {})); + +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ +/* global Reflect, Promise */ + +var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); +}; + +function __extends(d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +} + +function intValueSimple(lerp) { + if (this.ease) { + lerp = this.ease(lerp); + } + return ((this.next.value - this.current.value) * lerp) + this.current.value; +} +function intColorSimple(lerp) { + if (this.ease) { + lerp = this.ease(lerp); + } + var curVal = this.current.value; + var nextVal = this.next.value; + var r = ((nextVal.r - curVal.r) * lerp) + curVal.r; + var g = ((nextVal.g - curVal.g) * lerp) + curVal.g; + var b = ((nextVal.b - curVal.b) * lerp) + curVal.b; + return ParticleUtils.combineRGBComponents(r, g, b); +} +function intValueComplex(lerp) { + if (this.ease) { + lerp = this.ease(lerp); + } + // make sure we are on the right segment + while (lerp > this.next.time) { + this.current = this.next; + this.next = this.next.next; + } + // convert the lerp value to the segment range + lerp = (lerp - this.current.time) / (this.next.time - this.current.time); + return ((this.next.value - this.current.value) * lerp) + this.current.value; +} +function intColorComplex(lerp) { + if (this.ease) { + lerp = this.ease(lerp); + } + // make sure we are on the right segment + while (lerp > this.next.time) { + this.current = this.next; + this.next = this.next.next; + } + // convert the lerp value to the segment range + lerp = (lerp - this.current.time) / (this.next.time - this.current.time); + var curVal = this.current.value; + var nextVal = this.next.value; + var r = ((nextVal.r - curVal.r) * lerp) + curVal.r; + var g = ((nextVal.g - curVal.g) * lerp) + curVal.g; + var b = ((nextVal.b - curVal.b) * lerp) + curVal.b; + return ParticleUtils.combineRGBComponents(r, g, b); +} +function intValueStepped(lerp) { + if (this.ease) { + lerp = this.ease(lerp); + } + // make sure we are on the right segment + while (this.next && lerp > this.next.time) { + this.current = this.next; + this.next = this.next.next; + } + return this.current.value; +} +function intColorStepped(lerp) { + if (this.ease) { + lerp = this.ease(lerp); + } + // make sure we are on the right segment + while (this.next && lerp > this.next.time) { + this.current = this.next; + this.next = this.next.next; + } + var curVal = this.current.value; + return ParticleUtils.combineRGBComponents(curVal.r, curVal.g, curVal.b); +} +/** + * Singly linked list container for keeping track of interpolated properties for particles. + * Each Particle will have one of these for each interpolated property. + */ +var PropertyList = /** @class */ (function () { + /** + * @param isColor If this list handles color values + */ + function PropertyList(isColor) { + if (isColor === void 0) { isColor = false; } + this.current = null; + this.next = null; + this.isColor = !!isColor; + this.interpolate = null; + this.ease = null; + } + /** + * Resets the list for use. + * @param first The first node in the list. + * @param first.isStepped If the values should be stepped instead of interpolated linearly. + */ + PropertyList.prototype.reset = function (first) { + this.current = first; + this.next = first.next; + var isSimple = this.next && this.next.time >= 1; + if (isSimple) { + this.interpolate = this.isColor ? intColorSimple : intValueSimple; + } + else if (first.isStepped) { + this.interpolate = this.isColor ? intColorStepped : intValueStepped; + } + else { + this.interpolate = this.isColor ? intColorComplex : intValueComplex; + } + this.ease = this.current.ease; + }; + return PropertyList; +}()); + +/** + * An individual particle image. You shouldn't have to deal with these. + */ +var Particle = /** @class */ (function (_super) { + __extends(Particle, _super); + /** + * @param {PIXI.particles.Emitter} emitter The emitter that controls this particle. + */ + function Particle(emitter) { + var _this = + // start off the sprite with a blank texture, since we are going to replace it + // later when the particle is initialized. + _super.call(this) || this; + // initialize LinkedListChild props so they are included in underlying JS class definition + _this.prevChild = _this.nextChild = null; + _this.emitter = emitter; + // particles should be centered + _this.anchor.x = _this.anchor.y = 0.5; + _this.velocity = new Point(); + _this.rotationSpeed = 0; + _this.rotationAcceleration = 0; + _this.maxLife = 0; + _this.age = 0; + _this.ease = null; + _this.extraData = null; + _this.alphaList = new PropertyList(); + _this.speedList = new PropertyList(); + _this.speedMultiplier = 1; + _this.acceleration = new Point(); + _this.maxSpeed = NaN; + _this.scaleList = new PropertyList(); + _this.scaleMultiplier = 1; + _this.colorList = new PropertyList(true); + _this._doAlpha = false; + _this._doScale = false; + _this._doSpeed = false; + _this._doAcceleration = false; + _this._doColor = false; + _this._doNormalMovement = false; + _this._oneOverLife = 0; + _this.next = null; + _this.prev = null; + // save often used functions on the instance instead of the prototype for better speed + _this.init = _this.init; + _this.Particle_init = Particle.prototype.init; + _this.update = _this.update; + _this.Particle_update = Particle.prototype.update; + _this.Sprite_destroy = _super.prototype.destroy; + _this.Particle_destroy = Particle.prototype.destroy; + _this.applyArt = _this.applyArt; + _this.kill = _this.kill; + return _this; + } + /** + * Initializes the particle for use, based on the properties that have to + * have been set already on the particle. + */ + Particle.prototype.init = function () { + // reset the age + this.age = 0; + // set up the velocity based on the start speed and rotation + this.velocity.x = this.speedList.current.value * this.speedMultiplier; + this.velocity.y = 0; + ParticleUtils.rotatePoint(this.rotation, this.velocity); + if (this.noRotation) { + this.rotation = 0; + } + else { + // convert rotation to Radians from Degrees + this.rotation *= ParticleUtils.DEG_TO_RADS; + } + // convert rotation speed to Radians from Degrees + this.rotationSpeed *= ParticleUtils.DEG_TO_RADS; + this.rotationAcceleration *= ParticleUtils.DEG_TO_RADS; + // set alpha to inital alpha + this.alpha = this.alphaList.current.value; + // set scale to initial scale + this.scale.x = this.scale.y = this.scaleList.current.value; + // figure out what we need to interpolate + this._doAlpha = !!this.alphaList.current.next; + this._doSpeed = !!this.speedList.current.next; + this._doScale = !!this.scaleList.current.next; + this._doColor = !!this.colorList.current.next; + this._doAcceleration = this.acceleration.x !== 0 || this.acceleration.y !== 0; + // _doNormalMovement can be cancelled by subclasses + this._doNormalMovement = this._doSpeed || this.speedList.current.value !== 0 || this._doAcceleration; + // save our lerp helper + this._oneOverLife = 1 / this.maxLife; + // set the inital color + var color = this.colorList.current.value; + this.tint = ParticleUtils.combineRGBComponents(color.r, color.g, color.b); + // ensure visibility + this.visible = true; + }; + /** + * Sets the texture for the particle. This can be overridden to allow + * for an animated particle. + * @param art The texture to set. + */ + Particle.prototype.applyArt = function (art) { + this.texture = art || Texture.EMPTY; + }; + /** + * Updates the particle. + * @param delta Time elapsed since the previous frame, in __seconds__. + * @return The standard interpolation multiplier (0-1) used for all + * relevant particle properties. A value of -1 means the particle + * died of old age instead. + */ + Particle.prototype.update = function (delta) { + // increase age + this.age += delta; + // recycle particle if it is too old + if (this.age >= this.maxLife || this.age < 0) { + this.kill(); + return -1; + } + // determine our interpolation value + var lerp = this.age * this._oneOverLife; // lifetime / maxLife; + if (this.ease) { + if (this.ease.length === 4) { + // the t, b, c, d parameters that some tween libraries use + // (time, initial value, end value, duration) + lerp = this.ease(lerp, 0, 1, 1); + } + else { + // the simplified version that we like that takes + // one parameter, time from 0-1. TweenJS eases provide this usage. + lerp = this.ease(lerp); + } + } + // interpolate alpha + if (this._doAlpha) { + this.alpha = this.alphaList.interpolate(lerp); + } + // interpolate scale + if (this._doScale) { + var scale = this.scaleList.interpolate(lerp) * this.scaleMultiplier; + this.scale.x = this.scale.y = scale; + } + // handle movement + if (this._doNormalMovement) { + var deltaX = void 0; + var deltaY = void 0; + // interpolate speed + if (this._doSpeed) { + var speed = this.speedList.interpolate(lerp) * this.speedMultiplier; + ParticleUtils.normalize(this.velocity); + ParticleUtils.scaleBy(this.velocity, speed); + deltaX = this.velocity.x * delta; + deltaY = this.velocity.y * delta; + } + else if (this._doAcceleration) { + var oldVX = this.velocity.x; + var oldVY = this.velocity.y; + this.velocity.x += this.acceleration.x * delta; + this.velocity.y += this.acceleration.y * delta; + if (this.maxSpeed) { + var currentSpeed = ParticleUtils.length(this.velocity); + // if we are going faster than we should, clamp at the max speed + // DO NOT recalculate vector length + if (currentSpeed > this.maxSpeed) { + ParticleUtils.scaleBy(this.velocity, this.maxSpeed / currentSpeed); + } + } + // calculate position delta by the midpoint between our old velocity and our new velocity + deltaX = (oldVX + this.velocity.x) / 2 * delta; + deltaY = (oldVY + this.velocity.y) / 2 * delta; + } + else { + deltaX = this.velocity.x * delta; + deltaY = this.velocity.y * delta; + } + // adjust position based on velocity + this.position.x += deltaX; + this.position.y += deltaY; + } + // interpolate color + if (this._doColor) { + this.tint = this.colorList.interpolate(lerp); + } + // update rotation + if (this.rotationAcceleration !== 0) { + var newRotationSpeed = this.rotationSpeed + (this.rotationAcceleration * delta); + this.rotation += (this.rotationSpeed + newRotationSpeed) / 2 * delta; + this.rotationSpeed = newRotationSpeed; + } + else if (this.rotationSpeed !== 0) { + this.rotation += this.rotationSpeed * delta; + } + else if (this.acceleration && !this.noRotation) { + this.rotation = Math.atan2(this.velocity.y, this.velocity.x); // + Math.PI / 2; + } + return lerp; + }; + /** + * Kills the particle, removing it from the display list + * and telling the emitter to recycle it. + */ + Particle.prototype.kill = function () { + this.emitter.recycle(this); + }; + /** + * Destroys the particle, removing references and preventing future use. + */ + Particle.prototype.destroy = function () { + if (this.parent) { + this.parent.removeChild(this); + } + this.Sprite_destroy(); + this.emitter = this.velocity = this.colorList = this.scaleList = this.alphaList + = this.speedList = this.ease = this.next = this.prev = null; + }; + /** + * Checks over the art that was passed to the Emitter's init() function, to do any special + * modifications to prepare it ahead of time. + * @param art The array of art data. For Particle, it should be an array of + * Textures. Any strings in the array will be converted to + * Textures via Texture.from(). + * @return The art, after any needed modifications. + */ + Particle.parseArt = function (art) { + // convert any strings to Textures. + var i; + for (i = art.length; i >= 0; --i) { + if (typeof art[i] === 'string') { + art[i] = GetTextureFromString(art[i]); + } + } + // particles from different base textures will be slower in WebGL than if they + // were from one spritesheet + if (ParticleUtils.verbose) { + for (i = art.length - 1; i > 0; --i) { + if (art[i].baseTexture !== art[i - 1].baseTexture) { + if (window.console) { + // eslint-disable-next-line max-len + console.warn('PixiParticles: using particle textures from different images may hinder performance in WebGL'); + } + break; + } + } + } + return art; + }; + /** + * Parses extra emitter data to ensure it is set up for this particle class. + * Particle does nothing to the extra data. + * @param extraData The extra data from the particle config. + * @return The parsed extra data. + */ + Particle.parseData = function (extraData) { + return extraData; + }; + return Particle; +}(Sprite)); + +/** + * Chain of line segments for generating spawn positions. + */ +var PolygonalChain = /** @class */ (function () { + /** + * @param data Point data for polygon chains. Either a list of points for a single chain, or a list of chains. + */ + function PolygonalChain(data) { + this.segments = []; + this.countingLengths = []; + this.totalLength = 0; + this.init(data); + } + /** + * @param data Point data for polygon chains. Either a list of points for a single chain, or a list of chains. + */ + PolygonalChain.prototype.init = function (data) { + // if data is not present, set up a segment of length 0 + if (!data || !data.length) { + this.segments.push({ p1: { x: 0, y: 0 }, p2: { x: 0, y: 0 }, l: 0 }); + } + else if (Array.isArray(data[0])) { + // list of segment chains, each defined as a list of points + for (var i = 0; i < data.length; ++i) { + // loop through the chain, connecting points + var chain = data[i]; + var prevPoint = chain[0]; + for (var j = 1; j < chain.length; ++j) { + var second = chain[j]; + this.segments.push({ p1: prevPoint, p2: second, l: 0 }); + prevPoint = second; + } + } + } + else { + var prevPoint = data[0]; + // list of points + for (var i = 1; i < data.length; ++i) { + var second = data[i]; + this.segments.push({ p1: prevPoint, p2: second, l: 0 }); + prevPoint = second; + } + } + // now go through our segments to calculate the lengths so that we + // can set up a nice weighted random distribution + for (var i = 0; i < this.segments.length; ++i) { + var _a = this.segments[i], p1 = _a.p1, p2 = _a.p2; + var segLength = Math.sqrt(((p2.x - p1.x) * (p2.x - p1.x)) + ((p2.y - p1.y) * (p2.y - p1.y))); + // save length so we can turn a random number into a 0-1 interpolation value later + this.segments[i].l = segLength; + this.totalLength += segLength; + // keep track of the length so far, counting up + this.countingLengths.push(this.totalLength); + } + }; + /** + * Gets a random point in the chain. + * @param out The point to store the selected position in. + */ + PolygonalChain.prototype.getRandomPoint = function (out) { + // select a random spot in the length of the chain + var rand = Math.random() * this.totalLength; + var chosenSeg; + var lerp; + // if only one segment, it wins + if (this.segments.length === 1) { + chosenSeg = this.segments[0]; + lerp = rand; + } + else { + // otherwise, go through countingLengths until we have determined + // which segment we chose + for (var i = 0; i < this.countingLengths.length; ++i) { + if (rand < this.countingLengths[i]) { + chosenSeg = this.segments[i]; + // set lerp equal to the length into that segment + // (i.e. the remainder after subtracting all the segments before it) + lerp = i === 0 ? rand : rand - this.countingLengths[i - 1]; + break; + } + } + } + // divide lerp by the segment length, to result in a 0-1 number. + lerp /= chosenSeg.l || 1; + var p1 = chosenSeg.p1, p2 = chosenSeg.p2; + // now calculate the position in the segment that the lerp value represents + out.x = p1.x + (lerp * (p2.x - p1.x)); + out.y = p1.y + (lerp * (p2.y - p1.y)); + }; + return PolygonalChain; +}()); + +// get the shared ticker, in V4 and V5 friendly methods +/** + * @hidden + */ +var ticker; +// to avoid Rollup transforming our import, save pixi namespace in a variable +var pixiNS$1 = pixi; +if (parseInt((/^(\d+)\./).exec(VERSION)[1], 10) < 5) { + ticker = pixiNS$1.ticker.shared; +} +else { + ticker = pixiNS$1.Ticker.shared; +} +/** + * @hidden + */ +var helperPoint = new Point(); +/** + * A particle emitter. + */ +var Emitter = /** @class */ (function () { + /** + * @param particleParent The container to add the particles to. + * @param particleImages A texture or array of textures to use + * for the particles. Strings will be turned + * into textures via Texture.fromImage(). + * @param config A configuration object containing settings for the emitter. + * @param config.emit If config.emit is explicitly passed as false, the + * Emitter will start disabled. + * @param config.autoUpdate If config.autoUpdate is explicitly passed as + * true, the Emitter will automatically call + * update via the PIXI shared ticker. + */ + function Emitter(particleParent, particleImages, config) { + /** + * A number keeping index of currently applied image. Used to emit arts in order. + */ + this._currentImageIndex = -1; + this._particleConstructor = Particle; + // properties for individual particles + this.particleImages = null; + this.startAlpha = null; + this.startSpeed = null; + this.minimumSpeedMultiplier = 1; + this.acceleration = null; + this.maxSpeed = NaN; + this.startScale = null; + this.minimumScaleMultiplier = 1; + this.startColor = null; + this.minLifetime = 0; + this.maxLifetime = 0; + this.minStartRotation = 0; + this.maxStartRotation = 0; + this.noRotation = false; + this.minRotationSpeed = 0; + this.maxRotationSpeed = 0; + this.particleBlendMode = 0; + this.customEase = null; + this.extraData = null; + // properties for spawning particles + this._frequency = 1; + this.spawnChance = 1; + this.maxParticles = 1000; + this.emitterLifetime = -1; + this.spawnPos = null; + this.spawnType = null; + this._spawnFunc = null; + this.spawnRect = null; + this.spawnCircle = null; + this.spawnPolygonalChain = null; + this.particlesPerWave = 1; + this.particleSpacing = 0; + this.angleStart = 0; + // emitter properties + this.rotation = 0; + this.ownerPos = null; + this._prevEmitterPos = null; + this._prevPosIsValid = false; + this._posChanged = false; + this._parent = null; + this.addAtBack = false; + this.particleCount = 0; + this._emit = false; + this._spawnTimer = 0; + this._emitterLife = -1; + this._activeParticlesFirst = null; + this._activeParticlesLast = null; + this._poolFirst = null; + this._origConfig = null; + this._origArt = null; + this._autoUpdate = false; + this._currentImageIndex = -1; + this._destroyWhenComplete = false; + this._completeCallback = null; + // set the initial parent + this.parent = particleParent; + if (particleImages && config) { + this.init(particleImages, config); + } + // save often used functions on the instance instead of the prototype for better speed + this.recycle = this.recycle; + this.update = this.update; + this.rotate = this.rotate; + this.updateSpawnPos = this.updateSpawnPos; + this.updateOwnerPos = this.updateOwnerPos; + } + Object.defineProperty(Emitter.prototype, 'orderedArt', { + /** + * If the emitter is using particle art in order as provided in `particleImages`. + * Effective only when `particleImages` has multiple art options. + * This is particularly useful ensuring that each art shows up once, in case you need to emit a body in an order. + * For example: dragon - [Head, body1, body2, ..., tail] + */ + get: function () { return this._currentImageIndex !== -1; }, + set: function (value) { + this._currentImageIndex = value ? 0 : -1; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Emitter.prototype, 'frequency', { + /** + * Time between particle spawns in seconds. If this value is not a number greater than 0, + * it will be set to 1 (particle per second) to prevent infinite loops. + */ + get: function () { return this._frequency; }, + set: function (value) { + // do some error checking to prevent infinite loops + if (typeof value === 'number' && value > 0) { + this._frequency = value; + } + else { + this._frequency = 1; + } + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Emitter.prototype, 'particleConstructor', { + /** + * The constructor used to create new particles. The default is + * the built in Particle class. Setting this will dump any active or + * pooled particles, if the emitter has already been used. + */ + get: function () { return this._particleConstructor; }, + set: function (value) { + if (value !== this._particleConstructor) { + this._particleConstructor = value; + // clean up existing particles + this.cleanup(); + // scrap all the particles + for (var particle = this._poolFirst; particle; particle = particle.next) { + particle.destroy(); + } + this._poolFirst = null; + // re-initialize the emitter so that the new constructor can do anything it needs to + if (this._origConfig && this._origArt) { + this.init(this._origArt, this._origConfig); + } + } + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Emitter.prototype, 'parent', { + /** + * The container to add particles to. Settings this will dump any active particles. + */ + get: function () { return this._parent; }, + set: function (value) { + this.cleanup(); + this._parent = value; + }, + enumerable: true, + configurable: true + }); + /** + * Sets up the emitter based on the config settings. + * @param art A texture or array of textures to use for the particles. + * @param config A configuration object containing settings for the emitter. + */ + Emitter.prototype.init = function (art, config) { + if (!art || !config) { + return; + } + // clean up any existing particles + this.cleanup(); + // store the original config and particle images, in case we need to re-initialize + // when the particle constructor is changed + this._origConfig = config; + this._origArt = art; + // set up the array of data, also ensuring that it is an array + art = Array.isArray(art) ? art.slice() : [art]; + // run the art through the particle class's parsing function + var partClass = this._particleConstructor; + this.particleImages = partClass.parseArt ? partClass.parseArt(art) : art; + // ///////////////////////// + // Particle Properties // + // ///////////////////////// + // set up the alpha + if (config.alpha) { + this.startAlpha = PropertyNode.createList(config.alpha); + } + else { + this.startAlpha = new PropertyNode(1, 0); + } + // set up the speed + if (config.speed) { + this.startSpeed = PropertyNode.createList(config.speed); + // eslint-disable-next-line max-len + this.minimumSpeedMultiplier = ('minimumSpeedMultiplier' in config ? config.minimumSpeedMultiplier : config.speed.minimumSpeedMultiplier) || 1; + } + else { + this.minimumSpeedMultiplier = 1; + this.startSpeed = new PropertyNode(0, 0); + } + // set up acceleration + var acceleration = config.acceleration; + if (acceleration && (acceleration.x || acceleration.y)) { + // make sure we disable speed interpolation + this.startSpeed.next = null; + this.acceleration = new Point(acceleration.x, acceleration.y); + this.maxSpeed = config.maxSpeed || NaN; + } + else { + this.acceleration = new Point(); + } + // set up the scale + if (config.scale) { + this.startScale = PropertyNode.createList(config.scale); + // eslint-disable-next-line max-len + this.minimumScaleMultiplier = ('minimumScaleMultiplier' in config ? config.minimumScaleMultiplier : config.scale.minimumScaleMultiplier) || 1; + } + else { + this.startScale = new PropertyNode(1, 0); + this.minimumScaleMultiplier = 1; + } + // set up the color + if (config.color) { + this.startColor = PropertyNode.createList(config.color); + } + else { + this.startColor = new PropertyNode({ r: 0xFF, g: 0xFF, b: 0xFF }, 0); + } + // set up the start rotation + if (config.startRotation) { + this.minStartRotation = config.startRotation.min; + this.maxStartRotation = config.startRotation.max; + } + else { + this.minStartRotation = this.maxStartRotation = 0; + } + if (config.noRotation + && (this.minStartRotation || this.maxStartRotation)) { + this.noRotation = !!config.noRotation; + } + else { + this.noRotation = false; + } + // set up the rotation speed + if (config.rotationSpeed) { + this.minRotationSpeed = config.rotationSpeed.min; + this.maxRotationSpeed = config.rotationSpeed.max; + } + else { + this.minRotationSpeed = this.maxRotationSpeed = 0; + } + this.rotationAcceleration = config.rotationAcceleration || 0; + // set up the lifetime + this.minLifetime = config.lifetime.min; + this.maxLifetime = config.lifetime.max; + // get the blend mode + this.particleBlendMode = ParticleUtils.getBlendMode(config.blendMode); + // use the custom ease if provided + if (config.ease) { + this.customEase = typeof config.ease === 'function' + ? config.ease : ParticleUtils.generateEase(config.ease); + } + else { + this.customEase = null; + } + // set up the extra data, running it through the particle class's parseData function. + if (partClass.parseData) { + this.extraData = partClass.parseData(config.extraData); + } + else { + this.extraData = config.extraData || null; + } + // //////////////////////// + // Emitter Properties // + // //////////////////////// + // reset spawn type specific settings + this.spawnRect = this.spawnCircle = null; + this.particlesPerWave = 1; + if (config.particlesPerWave && config.particlesPerWave > 1) { + this.particlesPerWave = config.particlesPerWave; + } + this.particleSpacing = 0; + this.angleStart = 0; + // determine the spawn function to use + this.parseSpawnType(config); + // set the spawning frequency + this.frequency = config.frequency; + this.spawnChance = (typeof config.spawnChance === 'number' && config.spawnChance > 0) ? config.spawnChance : 1; + // set the emitter lifetime + this.emitterLifetime = config.emitterLifetime || -1; + // set the max particles + this.maxParticles = config.maxParticles > 0 ? config.maxParticles : 1000; + // determine if we should add the particle at the back of the list or not + this.addAtBack = !!config.addAtBack; + // reset the emitter position and rotation variables + this.rotation = 0; + this.ownerPos = new Point(); + this.spawnPos = new Point(config.pos.x, config.pos.y); + this.initAdditional(art, config); + this._prevEmitterPos = this.spawnPos.clone(); + // previous emitter position is invalid and should not be used for interpolation + this._prevPosIsValid = false; + // start emitting + this._spawnTimer = 0; + this.emit = config.emit === undefined ? true : !!config.emit; + this.autoUpdate = !!config.autoUpdate; + this.orderedArt = !!config.orderedArt; + }; + /** + * Sets up additional parameters to the emitter from config settings. + * Using for parsing additional parameters on classes that extend from Emitter + * @param art A texture or array of textures to use for the particles. + * @param config A configuration object containing settings for the emitter. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + Emitter.prototype.initAdditional = function (art, config) { + // override in subclasses + }; + /** + * Parsing emitter spawn type from config settings. + * Place for override and add new kind of spawn type + * @param config A configuration object containing settings for the emitter. + */ + Emitter.prototype.parseSpawnType = function (config) { + var spawnCircle; + switch (config.spawnType) { + case 'rect': + this.spawnType = 'rect'; + this._spawnFunc = this._spawnRect; + var spawnRect = config.spawnRect; + this.spawnRect = new Rectangle(spawnRect.x, spawnRect.y, spawnRect.w, spawnRect.h); + break; + case 'circle': + this.spawnType = 'circle'; + this._spawnFunc = this._spawnCircle; + spawnCircle = config.spawnCircle; + this.spawnCircle = new Circle(spawnCircle.x, spawnCircle.y, spawnCircle.r); + break; + case 'ring': + this.spawnType = 'ring'; + this._spawnFunc = this._spawnRing; + spawnCircle = config.spawnCircle; + this.spawnCircle = new Circle(spawnCircle.x, spawnCircle.y, spawnCircle.r); + this.spawnCircle.minRadius = spawnCircle.minR; + break; + case 'burst': + this.spawnType = 'burst'; + this._spawnFunc = this._spawnBurst; + this.particleSpacing = config.particleSpacing; + this.angleStart = config.angleStart ? config.angleStart : 0; + break; + case 'point': + this.spawnType = 'point'; + this._spawnFunc = this._spawnPoint; + break; + case 'polygonalChain': + this.spawnType = 'polygonalChain'; + this._spawnFunc = this._spawnPolygonalChain; + this.spawnPolygonalChain = new PolygonalChain(config.spawnPolygon); + break; + default: + this.spawnType = 'point'; + this._spawnFunc = this._spawnPoint; + break; + } + }; + /** + * Recycles an individual particle. For internal use only. + * @param particle The particle to recycle. + * @internal + */ + Emitter.prototype.recycle = function (particle) { + if (particle.next) { + particle.next.prev = particle.prev; + } + if (particle.prev) { + particle.prev.next = particle.next; + } + if (particle === this._activeParticlesLast) { + this._activeParticlesLast = particle.prev; + } + if (particle === this._activeParticlesFirst) { + this._activeParticlesFirst = particle.next; + } + // add to pool + particle.prev = null; + particle.next = this._poolFirst; + this._poolFirst = particle; + // remove child from display, or make it invisible if it is in a ParticleContainer + if (particle.parent) { + particle.parent.removeChild(particle); + } + // decrease count + --this.particleCount; + }; + /** + * Sets the rotation of the emitter to a new value. + * @param newRot The new rotation, in degrees. + */ + Emitter.prototype.rotate = function (newRot) { + if (this.rotation === newRot) + return; + // caclulate the difference in rotation for rotating spawnPos + var diff = newRot - this.rotation; + this.rotation = newRot; + // rotate spawnPos + ParticleUtils.rotatePoint(diff, this.spawnPos); + // mark the position as having changed + this._posChanged = true; + }; + /** + * Changes the spawn position of the emitter. + * @param x The new x value of the spawn position for the emitter. + * @param y The new y value of the spawn position for the emitter. + */ + Emitter.prototype.updateSpawnPos = function (x, y) { + this._posChanged = true; + this.spawnPos.x = x; + this.spawnPos.y = y; + }; + /** + * Changes the position of the emitter's owner. You should call this if you are adding + * particles to the world container that your emitter's owner is moving around in. + * @param x The new x value of the emitter's owner. + * @param y The new y value of the emitter's owner. + */ + Emitter.prototype.updateOwnerPos = function (x, y) { + this._posChanged = true; + this.ownerPos.x = x; + this.ownerPos.y = y; + }; + /** + * Prevents emitter position interpolation in the next update. + * This should be used if you made a major position change of your emitter's owner + * that was not normal movement. + */ + Emitter.prototype.resetPositionTracking = function () { + this._prevPosIsValid = false; + }; + Object.defineProperty(Emitter.prototype, 'emit', { + /** + * If particles should be emitted during update() calls. Setting this to false + * stops new particles from being created, but allows existing ones to die out. + */ + get: function () { return this._emit; }, + set: function (value) { + this._emit = !!value; + this._emitterLife = this.emitterLifetime; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Emitter.prototype, 'autoUpdate', { + /** + * If the update function is called automatically from the shared ticker. + * Setting this to false requires calling the update function manually. + */ + get: function () { return this._autoUpdate; }, + set: function (value) { + if (this._autoUpdate && !value) { + ticker.remove(this.update, this); + } + else if (!this._autoUpdate && value) { + ticker.add(this.update, this); + } + this._autoUpdate = !!value; + }, + enumerable: true, + configurable: true + }); + /** + * Starts emitting particles, sets autoUpdate to true, and sets up the Emitter to destroy itself + * when particle emission is complete. + * @param callback Callback for when emission is complete (all particles have died off) + */ + Emitter.prototype.playOnceAndDestroy = function (callback) { + this.autoUpdate = true; + this.emit = true; + this._destroyWhenComplete = true; + this._completeCallback = callback; + }; + /** + * Starts emitting particles and optionally calls a callback when particle emission is complete. + * @param callback Callback for when emission is complete (all particles have died off) + */ + Emitter.prototype.playOnce = function (callback) { + this.emit = true; + this._completeCallback = callback; + }; + /** + * Updates all particles spawned by this emitter and emits new ones. + * @param delta Time elapsed since the previous frame, in __seconds__. + */ + Emitter.prototype.update = function (delta) { + if (this._autoUpdate) { + delta = delta / settings.TARGET_FPMS / 1000; + } + // if we don't have a parent to add particles to, then don't do anything. + // this also works as a isDestroyed check + if (!this._parent) + return; + // update existing particles + var i; + var particle; + var next; + for (particle = this._activeParticlesFirst; particle; particle = next) { + next = particle.next; + particle.update(delta); + } + var prevX; + var prevY; + // if the previous position is valid, store these for later interpolation + if (this._prevPosIsValid) { + prevX = this._prevEmitterPos.x; + prevY = this._prevEmitterPos.y; + } + // store current position of the emitter as local variables + var curX = this.ownerPos.x + this.spawnPos.x; + var curY = this.ownerPos.y + this.spawnPos.y; + // spawn new particles + if (this._emit) { + // decrease spawn timer + this._spawnTimer -= delta < 0 ? 0 : delta; + // while _spawnTimer < 0, we have particles to spawn + while (this._spawnTimer <= 0) { + // determine if the emitter should stop spawning + if (this._emitterLife >= 0) { + this._emitterLife -= this._frequency; + if (this._emitterLife <= 0) { + this._spawnTimer = 0; + this._emitterLife = 0; + this.emit = false; + break; + } + } + // determine if we have hit the particle limit + if (this.particleCount >= this.maxParticles) { + this._spawnTimer += this._frequency; + continue; + } + // determine the particle lifetime + var lifetime = void 0; + if (this.minLifetime === this.maxLifetime) { + lifetime = this.minLifetime; + } + else { + lifetime = (Math.random() * (this.maxLifetime - this.minLifetime)) + this.minLifetime; + } + // only make the particle if it wouldn't immediately destroy itself + if (-this._spawnTimer < lifetime) { + // If the position has changed and this isn't the first spawn, + // interpolate the spawn position + var emitPosX = void 0; + var emitPosY = void 0; + if (this._prevPosIsValid && this._posChanged) { + // 1 - _spawnTimer / delta, but _spawnTimer is negative + var lerp = 1 + (this._spawnTimer / delta); + emitPosX = ((curX - prevX) * lerp) + prevX; + emitPosY = ((curY - prevY) * lerp) + prevY; + } + else // otherwise just set to the spawn position + { + emitPosX = curX; + emitPosY = curY; + } + // create enough particles to fill the wave (non-burst types have a wave of 1) + i = 0; + for (var len = Math.min(this.particlesPerWave, this.maxParticles - this.particleCount); i < len; ++i) { + // see if we actually spawn one + if (this.spawnChance < 1 && Math.random() >= this.spawnChance) { + continue; + } + // create particle + var p = void 0; + if (this._poolFirst) { + p = this._poolFirst; + this._poolFirst = this._poolFirst.next; + p.next = null; + } + else { + p = new this.particleConstructor(this); + } + // set a random texture if we have more than one + if (this.particleImages.length > 1) { + // if using ordered art + if (this._currentImageIndex !== -1) { + // get current art index, then increment for the next particle + p.applyArt(this.particleImages[this._currentImageIndex++]); + // loop around if needed + if (this._currentImageIndex < 0 || this._currentImageIndex >= this.particleImages.length) { + this._currentImageIndex = 0; + } + } + // otherwise grab a random one + else { + p.applyArt(this.particleImages[Math.floor(Math.random() * this.particleImages.length)]); + } + } + else { + // if they are actually the same texture, a standard particle + // will quit early from the texture setting in setTexture(). + p.applyArt(this.particleImages[0]); + } + // set up the start and end values + p.alphaList.reset(this.startAlpha); + if (this.minimumSpeedMultiplier !== 1) { + // eslint-disable-next-line max-len + p.speedMultiplier = (Math.random() * (1 - this.minimumSpeedMultiplier)) + this.minimumSpeedMultiplier; + } + p.speedList.reset(this.startSpeed); + p.acceleration.x = this.acceleration.x; + p.acceleration.y = this.acceleration.y; + p.maxSpeed = this.maxSpeed; + if (this.minimumScaleMultiplier !== 1) { + // eslint-disable-next-line max-len + p.scaleMultiplier = (Math.random() * (1 - this.minimumScaleMultiplier)) + this.minimumScaleMultiplier; + } + p.scaleList.reset(this.startScale); + p.colorList.reset(this.startColor); + // randomize the rotation speed + if (this.minRotationSpeed === this.maxRotationSpeed) { + p.rotationSpeed = this.minRotationSpeed; + } + else { + // eslint-disable-next-line max-len + p.rotationSpeed = (Math.random() * (this.maxRotationSpeed - this.minRotationSpeed)) + this.minRotationSpeed; + } + p.rotationAcceleration = this.rotationAcceleration; + p.noRotation = this.noRotation; + // set up the lifetime + p.maxLife = lifetime; + // set the blend mode + p.blendMode = this.particleBlendMode; + // set the custom ease, if any + p.ease = this.customEase; + // set the extra data, if any + p.extraData = this.extraData; + // set additional properties to particle + this.applyAdditionalProperties(p); + // call the proper function to handle rotation and position of particle + this._spawnFunc(p, emitPosX, emitPosY, i); + // initialize particle + p.init(); + // add the particle to the display list + if (this.addAtBack) { + this._parent.addChildAt(p, 0); + } + else { + this._parent.addChild(p); + } + // add particle to list of active particles + if (this._activeParticlesLast) { + this._activeParticlesLast.next = p; + p.prev = this._activeParticlesLast; + this._activeParticlesLast = p; + } + else { + this._activeParticlesLast = this._activeParticlesFirst = p; + } + ++this.particleCount; + // update the particle by the time passed, so the particles are spread out properly + p.update(-this._spawnTimer); // we want a positive delta, because a negative delta messes things up + } + } + // increase timer and continue on to any other particles that need to be created + this._spawnTimer += this._frequency; + } + } + // if the position changed before this update, then keep track of that + if (this._posChanged) { + this._prevEmitterPos.x = curX; + this._prevEmitterPos.y = curY; + this._prevPosIsValid = true; + this._posChanged = false; + } + // if we are all done and should destroy ourselves, take care of that + if (!this._emit && !this._activeParticlesFirst) { + if (this._completeCallback) { + var cb = this._completeCallback; + this._completeCallback = null; + cb(); + } + if (this._destroyWhenComplete) { + this.destroy(); + } + } + }; + /** + * Set additional properties to new particle. + * Using on classes that extend from Emitter + * @param p The particle + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + Emitter.prototype.applyAdditionalProperties = function (p) { + // for override in subclass + }; + /** + * Positions a particle for a point type emitter. + * @param p The particle to position and rotate. + * @param emitPosX The emitter's x position + * @param emitPosY The emitter's y position + * @param i The particle number in the current wave. Not used for this function. + */ + Emitter.prototype._spawnPoint = function (p, emitPosX, emitPosY) { + // set the initial rotation/direction of the particle based on + // starting particle angle and rotation of emitter + if (this.minStartRotation === this.maxStartRotation) { + p.rotation = this.minStartRotation + this.rotation; + } + else { + // eslint-disable-next-line max-len + p.rotation = (Math.random() * (this.maxStartRotation - this.minStartRotation)) + this.minStartRotation + this.rotation; + } + // drop the particle at the emitter's position + p.position.x = emitPosX; + p.position.y = emitPosY; + }; + /** + * Positions a particle for a rectangle type emitter. + * @param p The particle to position and rotate. + * @param emitPosX The emitter's x position + * @param emitPosY The emitter's y position + * @param i The particle number in the current wave. Not used for this function. + */ + Emitter.prototype._spawnRect = function (p, emitPosX, emitPosY) { + // set the initial rotation/direction of the particle based on starting + // particle angle and rotation of emitter + if (this.minStartRotation === this.maxStartRotation) { + p.rotation = this.minStartRotation + this.rotation; + } + else { + // eslint-disable-next-line max-len + p.rotation = (Math.random() * (this.maxStartRotation - this.minStartRotation)) + this.minStartRotation + this.rotation; + } + // place the particle at a random point in the rectangle + helperPoint.x = (Math.random() * this.spawnRect.width) + this.spawnRect.x; + helperPoint.y = (Math.random() * this.spawnRect.height) + this.spawnRect.y; + if (this.rotation !== 0) { + ParticleUtils.rotatePoint(this.rotation, helperPoint); + } + p.position.x = emitPosX + helperPoint.x; + p.position.y = emitPosY + helperPoint.y; + }; + /** + * Positions a particle for a circle type emitter. + * @param p The particle to position and rotate. + * @param emitPosX The emitter's x position + * @param emitPosY The emitter's y position + * @param i The particle number in the current wave. Not used for this function. + */ + Emitter.prototype._spawnCircle = function (p, emitPosX, emitPosY) { + // set the initial rotation/direction of the particle based on starting + // particle angle and rotation of emitter + if (this.minStartRotation === this.maxStartRotation) { + p.rotation = this.minStartRotation + this.rotation; + } + else { + // eslint-disable-next-line max-len + p.rotation = (Math.random() * (this.maxStartRotation - this.minStartRotation)) + this.minStartRotation + this.rotation; + } + // place the particle at a random radius in the circle + helperPoint.x = Math.random() * this.spawnCircle.radius; + helperPoint.y = 0; + // rotate the point to a random angle in the circle + ParticleUtils.rotatePoint(Math.random() * 360, helperPoint); + // offset by the circle's center + helperPoint.x += this.spawnCircle.x; + helperPoint.y += this.spawnCircle.y; + // rotate the point by the emitter's rotation + if (this.rotation !== 0) { + ParticleUtils.rotatePoint(this.rotation, helperPoint); + } + // set the position, offset by the emitter's position + p.position.x = emitPosX + helperPoint.x; + p.position.y = emitPosY + helperPoint.y; + }; + /** + * Positions a particle for a ring type emitter. + * @param p The particle to position and rotate. + * @param emitPosX The emitter's x position + * @param emitPosY The emitter's y position + * @param i The particle number in the current wave. Not used for this function. + */ + Emitter.prototype._spawnRing = function (p, emitPosX, emitPosY) { + var spawnCircle = this.spawnCircle; + // set the initial rotation/direction of the particle based on starting + // particle angle and rotation of emitter + if (this.minStartRotation === this.maxStartRotation) { + p.rotation = this.minStartRotation + this.rotation; + } + else { + p.rotation = (Math.random() * (this.maxStartRotation - this.minStartRotation)) + + this.minStartRotation + this.rotation; + } + // place the particle at a random radius in the ring + if (spawnCircle.minRadius !== spawnCircle.radius) { + helperPoint.x = (Math.random() * (spawnCircle.radius - spawnCircle.minRadius)) + + spawnCircle.minRadius; + } + else { + helperPoint.x = spawnCircle.radius; + } + helperPoint.y = 0; + // rotate the point to a random angle in the circle + var angle = Math.random() * 360; + p.rotation += angle; + ParticleUtils.rotatePoint(angle, helperPoint); + // offset by the circle's center + helperPoint.x += this.spawnCircle.x; + helperPoint.y += this.spawnCircle.y; + // rotate the point by the emitter's rotation + if (this.rotation !== 0) { + ParticleUtils.rotatePoint(this.rotation, helperPoint); + } + // set the position, offset by the emitter's position + p.position.x = emitPosX + helperPoint.x; + p.position.y = emitPosY + helperPoint.y; + }; + /** + * Positions a particle for polygonal chain. + * @param p The particle to position and rotate. + * @param emitPosX The emitter's x position + * @param emitPosY The emitter's y position + * @param i The particle number in the current wave. Not used for this function. + */ + Emitter.prototype._spawnPolygonalChain = function (p, emitPosX, emitPosY) { + // set the initial rotation/direction of the particle based on starting + // particle angle and rotation of emitter + if (this.minStartRotation === this.maxStartRotation) { + p.rotation = this.minStartRotation + this.rotation; + } + else { + p.rotation = (Math.random() * (this.maxStartRotation - this.minStartRotation)) + + this.minStartRotation + this.rotation; + } + // get random point on the polygon chain + this.spawnPolygonalChain.getRandomPoint(helperPoint); + // rotate the point by the emitter's rotation + if (this.rotation !== 0) { + ParticleUtils.rotatePoint(this.rotation, helperPoint); + } + // set the position, offset by the emitter's position + p.position.x = emitPosX + helperPoint.x; + p.position.y = emitPosY + helperPoint.y; + }; + /** + * Positions a particle for a burst type emitter. + * @param p The particle to position and rotate. + * @param emitPosX The emitter's x position + * @param emitPosY The emitter's y position + * @param i The particle number in the current wave. + */ + Emitter.prototype._spawnBurst = function (p, emitPosX, emitPosY, i) { + // set the initial rotation/direction of the particle based on spawn + // angle and rotation of emitter + if (this.particleSpacing === 0) { + p.rotation = Math.random() * 360; + } + else { + p.rotation = this.angleStart + (this.particleSpacing * i) + this.rotation; + } + // drop the particle at the emitter's position + p.position.x = emitPosX; + p.position.y = emitPosY; + }; + /** + * Kills all active particles immediately. + */ + Emitter.prototype.cleanup = function () { + var particle; + var next; + for (particle = this._activeParticlesFirst; particle; particle = next) { + next = particle.next; + this.recycle(particle); + if (particle.parent) { + particle.parent.removeChild(particle); + } + } + this._activeParticlesFirst = this._activeParticlesLast = null; + this.particleCount = 0; + }; + /** + * Destroys the emitter and all of its particles. + */ + Emitter.prototype.destroy = function () { + // make sure we aren't still listening to any tickers + this.autoUpdate = false; + // puts all active particles in the pool, and removes them from the particle parent + this.cleanup(); + // wipe the pool clean + var next; + for (var particle = this._poolFirst; particle; particle = next) { + // store next value so we don't lose it in our destroy call + next = particle.next; + particle.destroy(); + } + this._poolFirst = this._parent = this.particleImages = this.spawnPos = this.ownerPos + = this.startColor = this.startScale = this.startAlpha = this.startSpeed + = this.customEase = this._completeCallback = null; + }; + return Emitter; +}()); + +/** + * A helper point for math things. + * @hidden + */ +var helperPoint$1 = new Point(); +/** + * A hand picked list of Math functions (and a couple properties) that are + * allowable. They should be used without the preceding "Math." + * @hidden + */ +var MATH_FUNCS = [ + 'pow', + 'sqrt', + 'abs', + 'floor', + 'round', + 'ceil', + 'E', + 'PI', + 'sin', + 'cos', + 'tan', + 'asin', + 'acos', + 'atan', + 'atan2', + 'log', +]; +/** + * create an actual regular expression object from the string + * @hidden + */ +var WHITELISTER = new RegExp([ + // Allow the 4 basic operations, parentheses and all numbers/decimals, as well + // as 'x', for the variable usage. + '[01234567890\\.\\*\\-\\+\\/\\(\\)x ,]', +].concat(MATH_FUNCS).join('|'), 'g'); +/** + * Parses a string into a function for path following. + * This involves whitelisting the string for safety, inserting "Math." to math function + * names, and using `new Function()` to generate a function. + * @hidden + * @param pathString The string to parse. + * @return The path function - takes x, outputs y. + */ +function parsePath(pathString) { + var matches = pathString.match(WHITELISTER); + for (var i = matches.length - 1; i >= 0; --i) { + if (MATH_FUNCS.indexOf(matches[i]) >= 0) { + matches[i] = 'Math.' + matches[i]; + } + } + pathString = matches.join(''); + // eslint-disable-next-line no-new-func + return new Function('x', 'return ' + pathString + ';'); +} +/** + * An particle that follows a path defined by an algebraic expression, e.g. "sin(x)" or + * "5x + 3". + * To use this class, the particle config must have a "path" string in the + * "extraData" parameter. This string should have "x" in it to represent movement (from the + * speed settings of the particle). It may have numbers, parentheses, the four basic + * operations, and the following Math functions or properties (without the preceding "Math."): + * "pow", "sqrt", "abs", "floor", "round", "ceil", "E", "PI", "sin", "cos", "tan", "asin", + * "acos", "atan", "atan2", "log". + * The overall movement of the particle and the expression value become x and y positions for + * the particle, respectively. The final position is rotated by the spawn rotation/angle of + * the particle. + * + * Some example paths: + * + * "sin(x/10) * 20" // A sine wave path. + * "cos(x/100) * 30" // Particles curve counterclockwise (for medium speed/low lifetime particles) + * "pow(x/10, 2) / 2" // Particles curve clockwise (remember, +y is down). + */ +var PathParticle = /** @class */ (function (_super) { + __extends(PathParticle, _super); + /** + * @param {PIXI.particles.Emitter} emitter The emitter that controls this PathParticle. + */ + function PathParticle(emitter) { + var _this = _super.call(this, emitter) || this; + _this.path = null; + _this.initialRotation = 0; + _this.initialPosition = new Point(); + _this.movement = 0; + return _this; + } + /** + * Initializes the particle for use, based on the properties that have to + * have been set already on the particle. + */ + PathParticle.prototype.init = function () { + // get initial rotation before it is converted to radians + this.initialRotation = this.rotation; + // standard init + this.Particle_init(); + // set the path for the particle + this.path = this.extraData.path; + // cancel the normal movement behavior + this._doNormalMovement = !this.path; + // reset movement + this.movement = 0; + // grab position + this.initialPosition.x = this.position.x; + this.initialPosition.y = this.position.y; + }; + /** + * Updates the particle. + * @param delta Time elapsed since the previous frame, in __seconds__. + */ + PathParticle.prototype.update = function (delta) { + var lerp = this.Particle_update(delta); + // if the particle died during the update, then don't bother + if (lerp >= 0 && this.path) { + // increase linear movement based on speed + if (this._doSpeed) { + var speed = this.speedList.interpolate(lerp) * this.speedMultiplier; + this.movement += speed * delta; + } + else { + var speed = this.speedList.current.value * this.speedMultiplier; + this.movement += speed * delta; + } + // set up the helper point for rotation + helperPoint$1.x = this.movement; + helperPoint$1.y = this.path(this.movement); + ParticleUtils.rotatePoint(this.initialRotation, helperPoint$1); + this.position.x = this.initialPosition.x + helperPoint$1.x; + this.position.y = this.initialPosition.y + helperPoint$1.y; + } + return lerp; + }; + /** + * Destroys the particle, removing references and preventing future use. + */ + PathParticle.prototype.destroy = function () { + this.Particle_destroy(); + this.path = this.initialPosition = null; + }; + /** + * Checks over the art that was passed to the Emitter's init() function, to do any special + * modifications to prepare it ahead of time. This just runs Particle.parseArt(). + * @param art The array of art data. For Particle, it should be an array of + * Textures. Any strings in the array will be converted to + * Textures via Texture.fromImage(). + * @return The art, after any needed modifications. + */ + PathParticle.parseArt = function (art) { + return Particle.parseArt(art); + }; + /** + * Parses extra emitter data to ensure it is set up for this particle class. + * PathParticle checks for the existence of path data, and parses the path data for use + * by particle instances. + * @param extraData The extra data from the particle config. + * @return The parsed extra data. + */ + PathParticle.parseData = function (extraData) { + var output = {}; + if (extraData && extraData.path) { + try { + output.path = parsePath(extraData.path); + } + catch (e) { + if (ParticleUtils.verbose) { + console.error('PathParticle: error in parsing path expression'); + } + output.path = null; + } + } + else { + if (ParticleUtils.verbose) { + console.error('PathParticle requires a path string in extraData!'); + } + output.path = null; + } + return output; + }; + return PathParticle; +}(Particle)); + +/** + * An individual particle image with an animation. Art data passed to the emitter must be + * formatted in a particular way for AnimatedParticle to be able to handle it: + * + * ```typescript + * { + * //framerate is required. It is the animation speed of the particle in frames per + * //second. + * //A value of "matchLife" causes the animation to match the lifetime of an individual + * //particle, instead of at a constant framerate. This causes the animation to play + * //through one time, completing when the particle expires. + * framerate: 6, + * //loop is optional, and defaults to false. + * loop: true, + * //textures is required, and can be an array of any (non-zero) length. + * textures: [ + * //each entry represents a single texture that should be used for one or more + * //frames. Any strings will be converted to Textures with Texture.from(). + * //Instances of PIXI.Texture will be used directly. + * "animFrame1.png", + * //entries can be an object with a 'count' property, telling AnimatedParticle to + * //use that texture for 'count' frames sequentially. + * { + * texture: "animFrame2.png", + * count: 3 + * }, + * "animFrame3.png" + * ] + * } + * ``` + */ +var AnimatedParticle = /** @class */ (function (_super) { + __extends(AnimatedParticle, _super); + /** + * @param emitter The emitter that controls this AnimatedParticle. + */ + function AnimatedParticle(emitter) { + var _this = _super.call(this, emitter) || this; + _this.textures = null; + _this.duration = 0; + _this.framerate = 0; + _this.elapsed = 0; + _this.loop = false; + return _this; + } + /** + * Initializes the particle for use, based on the properties that have to + * have been set already on the particle. + */ + AnimatedParticle.prototype.init = function () { + this.Particle_init(); + this.elapsed = 0; + // if the animation needs to match the particle's life, then cacluate variables + if (this.framerate < 0) { + this.duration = this.maxLife; + this.framerate = this.textures.length / this.duration; + } + }; + /** + * Sets the textures for the particle. + * @param art An array of PIXI.Texture objects for this animated particle. + */ + AnimatedParticle.prototype.applyArt = function (art) { + this.textures = art.textures; + this.framerate = art.framerate; + this.duration = art.duration; + this.loop = art.loop; + }; + /** + * Updates the particle. + * @param delta Time elapsed since the previous frame, in __seconds__. + */ + AnimatedParticle.prototype.update = function (delta) { + var lerp = this.Particle_update(delta); + // only animate the particle if it is still alive + if (lerp >= 0) { + this.elapsed += delta; + if (this.elapsed >= this.duration) { + // loop elapsed back around + if (this.loop) { + this.elapsed = this.elapsed % this.duration; + } + // subtract a small amount to prevent attempting to go past the end of the animation + else { + this.elapsed = this.duration - 0.000001; + } + } + // add a very small number to the frame and then floor it to avoid + // the frame being one short due to floating point errors. + var frame = ((this.elapsed * this.framerate) + 0.0000001) | 0; + // in the very rare case that framerate * elapsed math ends up going past the end, use the last texture + this.texture = this.textures[frame] || this.textures[this.textures.length - 1] || Texture.EMPTY; + } + return lerp; + }; + /** + * Destroys the particle, removing references and preventing future use. + */ + AnimatedParticle.prototype.destroy = function () { + this.Particle_destroy(); + this.textures = null; + }; + /** + * Checks over the art that was passed to the Emitter's init() function, to do any special + * modifications to prepare it ahead of time. + * @param art The array of art data, properly formatted for AnimatedParticle. + * @return The art, after any needed modifications. + */ + AnimatedParticle.parseArt = function (art) { + var outArr = []; + for (var i = 0; i < art.length; ++i) { + var data = art[i]; + var output = outArr[i] = {}; + var outTextures = output.textures = []; + var textures = data.textures; + for (var j = 0; j < textures.length; ++j) { + var tex = textures[j]; + if (typeof tex === 'string') { + outTextures.push(GetTextureFromString(tex)); + } + else if (tex instanceof Texture) { + outTextures.push(tex); + } + // assume an object with extra data determining duplicate frame data + else { + var dupe = tex.count || 1; + if (typeof tex.texture === 'string') { + tex = GetTextureFromString(tex.texture); + } + else // if(tex.texture instanceof Texture) + { + tex = tex.texture; + } + for (; dupe > 0; --dupe) { + outTextures.push(tex); + } + } + } + // use these values to signify that the animation should match the particle life time. + if (data.framerate === 'matchLife') { + // -1 means that it should be calculated + output.framerate = -1; + output.duration = 0; + output.loop = false; + } + else { + // determine if the animation should loop + output.loop = !!data.loop; + // get the framerate, default to 60 + output.framerate = data.framerate > 0 ? data.framerate : 60; + // determine the duration + output.duration = outTextures.length / output.framerate; + } + } + return outArr; + }; + return AnimatedParticle; +}(Particle)); + +/** + * A semi-experimental Container that uses a doubly linked list to manage children instead of an + * array. This means that adding/removing children often is not the same performance hit that + * it would to be continually pushing/splicing. + * However, this is primarily intended to be used for heavy particle usage, and may not handle + * edge cases well if used as a complete Container replacement. + */ +var LinkedListContainer = /** @class */ (function (_super) { + __extends(LinkedListContainer, _super); + function LinkedListContainer() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this._firstChild = null; + _this._lastChild = null; + _this._childCount = 0; + return _this; + } + Object.defineProperty(LinkedListContainer.prototype, 'firstChild', { + get: function () { + return this._firstChild; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(LinkedListContainer.prototype, 'lastChild', { + get: function () { + return this._lastChild; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(LinkedListContainer.prototype, 'childCount', { + get: function () { + return this._childCount; + }, + enumerable: true, + configurable: true + }); + LinkedListContainer.prototype.addChild = function () { + var children = []; + for (var _i = 0; _i < arguments.length; _i++) { + children[_i] = arguments[_i]; + } + // if there is only one argument we can bypass looping through the them + if (children.length > 1) { + // loop through the array and add all children + for (var i = 0; i < children.length; i++) { + // eslint-disable-next-line prefer-rest-params + this.addChild(children[i]); + } + } + else { + var child = children[0]; + // if the child has a parent then lets remove it as PixiJS objects can only exist in one place + if (child.parent) { + child.parent.removeChild(child); + } + child.parent = this; + this.sortDirty = true; + // ensure child transform will be recalculated + child.transform._parentID = -1; + // add to list if we have a list + if (this._lastChild) { + this._lastChild.nextChild = child; + child.prevChild = this._lastChild; + this._lastChild = child; + } + // otherwise initialize the list + else { + this._firstChild = this._lastChild = child; + } + // update child count + ++this._childCount; + // ensure bounds will be recalculated + this._boundsID++; + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(); + this.emit('childAdded', child, this, this._childCount); + child.emit('added', this); + } + return children[0]; + }; + LinkedListContainer.prototype.addChildAt = function (child, index) { + if (index < 0 || index > this._childCount) { + throw new Error('addChildAt: The index ' + index + ' supplied is out of bounds ' + this._childCount); + } + if (child.parent) { + child.parent.removeChild(child); + } + child.parent = this; + this.sortDirty = true; + // ensure child transform will be recalculated + child.transform._parentID = -1; + var c = child; + // if no children, do basic initialization + if (!this._firstChild) { + this._firstChild = this._lastChild = c; + } + // add at beginning (back) + else if (index === 0) { + this._firstChild.prevChild = c; + c.nextChild = this._firstChild; + this._firstChild = c; + } + // add at end (front) + else if (index === this._childCount) { + this._lastChild.nextChild = c; + c.prevChild = this._lastChild; + this._lastChild = c; + } + // otherwise we have to start counting through the children to find the right one + // - SLOW, only provided to fully support the possibility of use + else { + var i = 0; + var target = this._firstChild; + while (i < index) { + target = target.nextChild; + ++i; + } + // insert before the target that we found at the specified index + target.prevChild.nextChild = c; + c.prevChild = target.prevChild; + c.nextChild = target; + target.prevChild = c; + } + // update child count + ++this._childCount; + // ensure bounds will be recalculated + this._boundsID++; + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); // the PixiJS types say this has no arguments + child.emit('added', this); + this.emit('childAdded', child, this, index); + return child; + }; + /** + * Adds a child to the container to be rendered below another child. + * + * @param child The child to add + * @param relative - The current child to add the new child relative to. + * @return The child that was added. + */ + LinkedListContainer.prototype.addChildBelow = function (child, relative) { + if (relative.parent !== this) { + throw new Error('addChildBelow: The relative target must be a child of this parent'); + } + if (child.parent) { + child.parent.removeChild(child); + } + child.parent = this; + this.sortDirty = true; + // ensure child transform will be recalculated + child.transform._parentID = -1; + // insert before the target that we were given + relative.prevChild.nextChild = child; + child.prevChild = relative.prevChild; + child.nextChild = relative; + relative.prevChild = child; + if (this._firstChild === relative) { + this._firstChild = child; + } + // update child count + ++this._childCount; + // ensure bounds will be recalculated + this._boundsID++; + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(); + this.emit('childAdded', child, this, this._childCount); + child.emit('added', this); + return child; + }; + /** + * Adds a child to the container to be rendered above another child. + * + * @param child The child to add + * @param relative - The current child to add the new child relative to. + * @return The child that was added. + */ + LinkedListContainer.prototype.addChildAbove = function (child, relative) { + if (relative.parent !== this) { + throw new Error('addChildBelow: The relative target must be a child of this parent'); + } + if (child.parent) { + child.parent.removeChild(child); + } + child.parent = this; + this.sortDirty = true; + // ensure child transform will be recalculated + child.transform._parentID = -1; + // insert after the target that we were given + relative.nextChild.prevChild = child; + child.nextChild = relative.nextChild; + child.prevChild = relative; + relative.nextChild = child; + if (this._lastChild === relative) { + this._lastChild = child; + } + // update child count + ++this._childCount; + // ensure bounds will be recalculated + this._boundsID++; + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(); + this.emit('childAdded', child, this, this._childCount); + child.emit('added', this); + return child; + }; + LinkedListContainer.prototype.swapChildren = function (child, child2) { + if (child === child2 || child.parent !== this || child2.parent !== this) { + return; + } + var _a = child, prevChild = _a.prevChild, nextChild = _a.nextChild; + child.prevChild = child2.prevChild; + child.nextChild = child2.nextChild; + child2.prevChild = prevChild; + child2.nextChild = nextChild; + if (this._firstChild === child) { + this._firstChild = child2; + } + else if (this._firstChild === child2) { + this._firstChild = child; + } + if (this._lastChild === child) { + this._lastChild = child2; + } + else if (this._lastChild === child2) { + this._lastChild = child; + } + this.onChildrenChange(); + }; + LinkedListContainer.prototype.getChildIndex = function (child) { + var index = 0; + var test = this._firstChild; + while (test) { + if (test === child) { + break; + } + test = test.nextChild; + ++index; + } + if (!test) { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + return index; + }; + LinkedListContainer.prototype.setChildIndex = function (child, index) { + if (index < 0 || index >= this._childCount) { + throw new Error('The index ' + index + ' supplied is out of bounds ' + this._childCount); + } + if (child.parent !== this) { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + // remove child + if (child.nextChild) { + child.nextChild.prevChild = child.prevChild; + } + if (child.prevChild) { + child.prevChild.nextChild = child.nextChild; + } + if (this._firstChild === child) { + this._firstChild = child.nextChild; + } + if (this._lastChild === child) { + this._lastChild = child.prevChild; + } + child.nextChild = null; + child.prevChild = null; + // do addChildAt + if (!this._firstChild) { + this._firstChild = this._lastChild = child; + } + else if (index === 0) { + this._firstChild.prevChild = child; + child.nextChild = this._firstChild; + this._firstChild = child; + } + else if (index === this._childCount) { + this._lastChild.nextChild = child; + child.prevChild = this._lastChild; + this._lastChild = child; + } + else { + var i = 0; + var target = this._firstChild; + while (i < index) { + target = target.nextChild; + ++i; + } + target.prevChild.nextChild = child; + child.prevChild = target.prevChild; + child.nextChild = target; + target.prevChild = child; + } + this.onChildrenChange(index); + }; + LinkedListContainer.prototype.removeChild = function () { + var children = []; + for (var _i = 0; _i < arguments.length; _i++) { + children[_i] = arguments[_i]; + } + // if there is only one argument we can bypass looping through the them + if (children.length > 1) { + // loop through the arguments property and remove all children + for (var i = 0; i < children.length; i++) { + this.removeChild(children[i]); + } + } + else { + var child = children[0]; + // bail if not actually our child + if (child.parent !== this) + return null; + child.parent = null; + // ensure child transform will be recalculated + child.transform._parentID = -1; + // swap out child references + if (child.nextChild) { + child.nextChild.prevChild = child.prevChild; + } + if (child.prevChild) { + child.prevChild.nextChild = child.nextChild; + } + if (this._firstChild === child) { + this._firstChild = child.nextChild; + } + if (this._lastChild === child) { + this._lastChild = child.prevChild; + } + // clear sibling references + child.nextChild = null; + child.prevChild = null; + // update child count + --this._childCount; + // ensure bounds will be recalculated + this._boundsID++; + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(); + child.emit('removed', this); + this.emit('childRemoved', child, this); + } + return children[0]; + }; + LinkedListContainer.prototype.getChildAt = function (index) { + if (index < 0 || index >= this._childCount) { + throw new Error('getChildAt: Index (' + index + ') does not exist.'); + } + if (index === 0) { + return this._firstChild; + } + // add at end (front) + else if (index === this._childCount) { + return this._lastChild; + } + // otherwise we have to start counting through the children to find the right one + // - SLOW, only provided to fully support the possibility of use + var i = 0; + var target = this._firstChild; + while (i < index) { + target = target.nextChild; + ++i; + } + return target; + }; + LinkedListContainer.prototype.removeChildAt = function (index) { + var child = this.getChildAt(index); + // ensure child transform will be recalculated.. + child.parent = null; + child.transform._parentID = -1; + // swap out child references + if (child.nextChild) { + child.nextChild.prevChild = child.prevChild; + } + if (child.prevChild) { + child.prevChild.nextChild = child.nextChild; + } + if (this._firstChild === child) { + this._firstChild = child.nextChild; + } + if (this._lastChild === child) { + this._lastChild = child.prevChild; + } + // clear sibling references + child.nextChild = null; + child.prevChild = null; + // update child count + --this._childCount; + // ensure bounds will be recalculated + this._boundsID++; + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); // the PixiJS types say this has no arguments + child.emit('removed', this); + this.emit('childRemoved', child, this, index); + return child; + }; + LinkedListContainer.prototype.removeChildren = function (beginIndex, endIndex) { + if (beginIndex === void 0) { beginIndex = 0; } + if (endIndex === void 0) { endIndex = this._childCount; } + var begin = beginIndex; + var end = endIndex; + var range = end - begin; + if (range > 0 && range <= end) { + var removed = []; + var child = this._firstChild; + for (var i = 0; i <= end && child; ++i, child = child.nextChild) { + if (i >= begin) { + removed.push(child); + } + } + // child before removed section + var prevChild = removed[0].prevChild; + // child after removed section + var nextChild = removed[removed.length - 1].nextChild; + if (!nextChild) { + // if we removed the last child, then the new last child is the one before + // the removed section + this._lastChild = prevChild; + } + else { + // otherwise, stitch the child before the section to the child after + nextChild.prevChild = prevChild; + } + if (!prevChild) { + // if we removed the first child, then the new first child is the one after + // the removed section + this._firstChild = nextChild; + } + else { + // otherwise stich the child after the section to the one before + prevChild.nextChild = nextChild; + } + for (var i = 0; i < removed.length; ++i) { + // clear parenting and sibling references for all removed children + removed[i].parent = null; + if (removed[i].transform) { + removed[i].transform._parentID = -1; + } + removed[i].nextChild = null; + removed[i].prevChild = null; + } + this._boundsID++; + this.onChildrenChange(beginIndex); + for (var i = 0; i < removed.length; ++i) { + removed[i].emit('removed', this); + this.emit('childRemoved', removed[i], this, i); + } + return removed; + } + else if (range === 0 && this._childCount === 0) { + return []; + } + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + }; + /** + * Updates the transform on all children of this container for rendering. + * Copied from and overrides PixiJS v5 method (v4 method is identical) + */ + LinkedListContainer.prototype.updateTransform = function () { + this._boundsID++; + this.transform.updateTransform(this.parent.transform); + // TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + var child; + var next; + for (child = this._firstChild; child; child = next) { + next = child.nextChild; + if (child.visible) { + child.updateTransform(); + } + } + }; + /** + * Recalculates the bounds of the container. + * Copied from and overrides PixiJS v5 method (v4 method is identical) + */ + LinkedListContainer.prototype.calculateBounds = function () { + this._bounds.clear(); + this._calculateBounds(); + var child; + var next; + for (child = this._firstChild; child; child = next) { + next = child.nextChild; + if (!child.visible || !child.renderable) { + continue; + } + child.calculateBounds(); + // TODO: filter+mask, need to mask both somehow + if (child._mask) { + var maskObject = (child._mask.maskObject || child._mask); + maskObject.calculateBounds(); + this._bounds.addBoundsMask(child._bounds, maskObject._bounds); + } + else if (child.filterArea) { + this._bounds.addBoundsArea(child._bounds, child.filterArea); + } + else { + this._bounds.addBounds(child._bounds); + } + } + this._bounds.updateID = this._boundsID; + }; + /** + * Retrieves the local bounds of the displayObject as a rectangle object. Copied from and overrides PixiJS v5 method + */ + LinkedListContainer.prototype.getLocalBounds = function (rect, skipChildrenUpdate) { + if (skipChildrenUpdate === void 0) { skipChildrenUpdate = false; } + // skip Container's getLocalBounds, go directly to DisplayObject + var result = DisplayObject.prototype.getLocalBounds.call(this, rect); + if (!skipChildrenUpdate) { + var child = void 0; + var next = void 0; + for (child = this._firstChild; child; child = next) { + next = child.nextChild; + if (child.visible) { + child.updateTransform(); + } + } + } + return result; + }; + /** + * Renders the object using the WebGL renderer. Copied from and overrides PixiJS v5 method + */ + LinkedListContainer.prototype.render = function (renderer) { + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) { + return; + } + // do a quick check to see if this element has a mask or a filter. + if (this._mask || (this.filters && this.filters.length)) { + this.renderAdvanced(renderer); + } + else { + this._render(renderer); + var child = void 0; + var next = void 0; + // simple render children! + for (child = this._firstChild; child; child = next) { + next = child.nextChild; + child.render(renderer); + } + } + }; + /** + * Render the object using the WebGL renderer and advanced features. Copied from and overrides PixiJS v5 method + */ + LinkedListContainer.prototype.renderAdvanced = function (renderer) { + renderer.batch.flush(); + var filters = this.filters; + var mask = this._mask; + // _enabledFilters note: As of development, _enabledFilters is not documented in pixi.js + // types but is in code of current release (5.2.4). + // push filter first as we need to ensure the stencil buffer is correct for any masking + if (filters) { + if (!this._enabledFilters) { + this._enabledFilters = []; + } + this._enabledFilters.length = 0; + for (var i = 0; i < filters.length; i++) { + if (filters[i].enabled) { + this._enabledFilters.push(filters[i]); + } + } + if (this._enabledFilters.length) { + renderer.filter.push(this, this._enabledFilters); + } + } + if (mask) { + renderer.mask.push(this, this._mask); + } + // add this object to the batch, only rendered if it has a texture. + this._render(renderer); + var child; + var next; + // now loop through the children and make sure they get rendered + for (child = this._firstChild; child; child = next) { + next = child.nextChild; + child.render(renderer); + } + renderer.batch.flush(); + if (mask) { + renderer.mask.pop(this); + } + if (filters && this._enabledFilters && this._enabledFilters.length) { + renderer.filter.pop(); + } + }; + /** + * Renders the object using the WebGL renderer. Copied from and overrides PixiJS V4 method. + */ + LinkedListContainer.prototype.renderWebGL = function (renderer) { + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) { + return; + } + // do a quick check to see if this element has a mask or a filter. + if (this._mask || (this.filters && this.filters.length)) { + this.renderAdvancedWebGL(renderer); + } + else { + this._renderWebGL(renderer); + var child = void 0; + var next = void 0; + // simple render children! + for (child = this._firstChild; child; child = next) { + next = child.nextChild; + child.renderWebGL(renderer); + } + } + }; + /** + * Render the object using the WebGL renderer and advanced features. Copied from and overrides PixiJS V4 method. + */ + LinkedListContainer.prototype.renderAdvancedWebGL = function (renderer) { + renderer.flush(); + // _filters is a v4 specific property + var filters = this._filters; + var mask = this._mask; + // push filter first as we need to ensure the stencil buffer is correct for any masking + if (filters) { + if (!this._enabledFilters) { + this._enabledFilters = []; + } + this._enabledFilters.length = 0; + for (var i = 0; i < filters.length; i++) { + if (filters[i].enabled) { + this._enabledFilters.push(filters[i]); + } + } + if (this._enabledFilters.length) { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + if (mask) { + renderer.maskManager.pushMask(this, this._mask); + } + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + var child; + var next; + // now loop through the children and make sure they get rendered + for (child = this._firstChild; child; child = next) { + next = child.nextChild; + child.renderWebGL(renderer); + } + renderer.flush(); + if (mask) { + renderer.maskManager.popMask(this, this._mask); + } + if (filters && this._enabledFilters && this._enabledFilters.length) { + renderer.filterManager.popFilter(); + } + }; + /** + * Renders the object using the Canvas renderer. Copied from and overrides PixiJS V4 method or Canvas mixin in V5. + */ + LinkedListContainer.prototype.renderCanvas = function (renderer) { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) { + return; + } + if (this._mask) { + renderer.maskManager.pushMask(this._mask); + } + this._renderCanvas(renderer); + var child; + var next; + for (child = this._firstChild; child; child = next) { + next = child.nextChild; + child.renderCanvas(renderer); + } + if (this._mask) { + renderer.maskManager.popMask(renderer); + } + }; + return LinkedListContainer; +}(Container)); + +export { AnimatedParticle, Emitter, GetTextureFromString, LinkedListContainer, Particle, ParticleUtils, PathParticle, PolygonalChain, PropertyList, PropertyNode }; +//# sourceMappingURL=pixi-particles.es.js.map diff --git a/packages/plugin-renderer-particles/lib/system.ts b/packages/plugin-renderer-particles/lib/system.ts index 51e6de52..ec283edf 100644 --- a/packages/plugin-renderer-particles/lib/system.ts +++ b/packages/plugin-renderer-particles/lib/system.ts @@ -2,7 +2,8 @@ import { decorators, OBSERVER_TYPE, ComponentChanged, - resource + resource, + RESOURCE_TYPE } from '@eva/eva.js'; import { @@ -15,7 +16,7 @@ import { Texture } from 'pixi.js'; import ParticleComponent from './component'; -resource.registerInstance('Particles', ({ data }) => { +resource.registerInstance(RESOURCE_TYPE.PARTICLES, ({ data }) => { try { const textures = [] for (const key in data) { diff --git a/packages/plugin-renderer-particles/package.json b/packages/plugin-renderer-particles/package.json index 157ca67d..78a7654d 100644 --- a/packages/plugin-renderer-particles/package.json +++ b/packages/plugin-renderer-particles/package.json @@ -21,7 +21,6 @@ "@eva/eva.js": "^1.1.1", "@eva/plugin-renderer": "^1.1.1", "@eva/renderer-adapter": "^1.1.1", - "pixi-particles": "^4.3.1", "pixi.js": "^4.8.9" } } \ No newline at end of file diff --git a/packages/plugin-renderer-render/package.json b/packages/plugin-renderer-render/package.json index ac39ad00..00a13be0 100644 --- a/packages/plugin-renderer-render/package.json +++ b/packages/plugin-renderer-render/package.json @@ -22,6 +22,6 @@ "@eva/plugin-renderer": "1.1.2-alpha.0", "@eva/renderer-adapter": "1.1.2-alpha.0", "@eva/eva.js": "1.1.2-alpha.0", - "pixi.js": "^4.8.7" + "pixi.js": "^4.8.9" } } diff --git a/packages/plugin-renderer-spine/package.json b/packages/plugin-renderer-spine/package.json index fadfd2ef..edee753f 100644 --- a/packages/plugin-renderer-spine/package.json +++ b/packages/plugin-renderer-spine/package.json @@ -21,6 +21,6 @@ "@eva/inspector-decorator": "^0.0.4", "@eva/plugin-renderer": "1.1.2-alpha.0", "@eva/eva.js": "1.1.2-alpha.0", - "pixi.js": "^4.8.7" + "pixi.js": "^4.8.9" } } diff --git a/packages/plugin-renderer-spine38/package.json b/packages/plugin-renderer-spine38/package.json index bc5d8d9b..97a2463f 100644 --- a/packages/plugin-renderer-spine38/package.json +++ b/packages/plugin-renderer-spine38/package.json @@ -20,6 +20,6 @@ "dependencies": { "@eva/plugin-renderer": "1.1.2-alpha.0", "@eva/eva.js": "1.1.2-alpha.0", - "pixi.js": "^4.8.7" + "pixi.js": "^4.8.9" } } diff --git a/packages/plugin-renderer-sprite-animation/package.json b/packages/plugin-renderer-sprite-animation/package.json index 9310632d..e51cadea 100644 --- a/packages/plugin-renderer-sprite-animation/package.json +++ b/packages/plugin-renderer-sprite-animation/package.json @@ -22,6 +22,6 @@ "@eva/plugin-renderer": "1.1.2-alpha.0", "@eva/renderer-adapter": "1.1.2-alpha.0", "@eva/eva.js": "1.1.2-alpha.0", - "pixi.js": "^4.8.7" + "pixi.js": "^4.8.9" } } diff --git a/packages/plugin-renderer-sprite/package.json b/packages/plugin-renderer-sprite/package.json index daf836d3..82b64112 100644 --- a/packages/plugin-renderer-sprite/package.json +++ b/packages/plugin-renderer-sprite/package.json @@ -21,6 +21,6 @@ "@eva/inspector-decorator": "^0.0.4", "@eva/plugin-renderer": "1.1.2-alpha.0", "@eva/eva.js": "1.1.2-alpha.0", - "pixi.js": "^4.8.7" + "pixi.js": "^4.8.9" } } diff --git a/packages/plugin-renderer-text/package.json b/packages/plugin-renderer-text/package.json index cbf6fdbc..d0ac914e 100644 --- a/packages/plugin-renderer-text/package.json +++ b/packages/plugin-renderer-text/package.json @@ -22,6 +22,6 @@ "@eva/plugin-renderer": "1.1.2-alpha.0", "@eva/renderer-adapter": "1.1.2-alpha.0", "@eva/eva.js": "1.1.2-alpha.0", - "pixi.js": "^4.8.7" + "pixi.js": "^4.8.9" } } diff --git a/packages/plugin-renderer/package.json b/packages/plugin-renderer/package.json index 893d1b94..a47e6d0f 100644 --- a/packages/plugin-renderer/package.json +++ b/packages/plugin-renderer/package.json @@ -22,6 +22,6 @@ "@eva/renderer-adapter": "1.1.2-alpha.0", "eventemitter3": "^3.1.2", "lodash-es": "^4.17.21", - "pixi.js": "^4.8.7" + "pixi.js": "^4.8.9" } } From 24a4e2281bdbb5faf724dcbce2d7303ee52e6708 Mon Sep 17 00:00:00 2001 From: aspsnd <46017470+aspsnd@users.noreply.github.com> Date: Thu, 16 Sep 2021 17:31:13 +0800 Subject: [PATCH 4/9] Feat/compressed (#84) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: merge * compressed examples * change some format when export a type * get compressed texture extension when needed * delete tsconfig.json useless dots * change tsconfig.josn islatedModules for publish * release: v1.1.0-beta.2 * fix: avoid compress index.ts be tree shaked * add single tsconfig.json for vite * fix: vite.config.js for es5 * release: v1.1.1-beta.0 * fix: add @pixi/constants to package.json * format * fix: calculate width and height * fix: backgroundColor type * dev mipmap * feat: support pvrtc scale * fix: 不销毁img * feat: 销毁cpu内存 * feat: export resourceLoader * fix: 兼容压缩纹理不支持webglgl的情况 * feat: webgl 才使用压缩纹理 * fix: type * release: v1.2.0-alpha.0 * fix: resourceloader * release: v1.2.0-alpha.1 * fix: 处理被放在一个对象里面的类不能被继承问题,TS4020错误 * release: v1.2.0-alpha.2 * fix: update __ENV__ * fix: update extension check * fix: remove const from renderer * fix: get fomats with system * fix: update formats * fix: update cpu memory * fix: cr bug Co-authored-by: fanmingfei --- .gitignore | 2 + examples/index.html | 1 + examples/public/back.astc.ktx | Bin 0 -> 752100 bytes examples/public/back.etc.ktx | Bin 0 -> 752100 bytes examples/public/back.png | Bin 0 -> 134941 bytes examples/public/back.pvrtc.ktx | Bin 0 -> 524388 bytes examples/public/block.astc.ktx | Bin 0 -> 65636 bytes examples/public/block.etc.ktx | Bin 0 -> 65636 bytes examples/public/block.png | Bin 0 -> 168691 bytes examples/public/block.pvrtc.ktx | Bin 0 -> 16484 bytes examples/public/block.s3tc.ktx | Bin 0 -> 32888 bytes examples/public/cat.astc.ktx | Bin 0 -> 507044 bytes examples/public/cat.etc.ktx | Bin 0 -> 507044 bytes examples/public/cat.png | Bin 0 -> 685146 bytes examples/public/cat.pvrtc.ktx | Bin 0 -> 524388 bytes examples/public/cat.s3tc.ktx | Bin 0 -> 253592 bytes examples/public/cut.astc.ktx | Bin 0 -> 407460 bytes examples/public/cut.etc.ktx | Bin 0 -> 407460 bytes examples/public/{yanhua.png => cut.png} | Bin examples/public/cut.pvrtc.ktx | Bin 0 -> 1048676 bytes examples/public/cut.s3tc.ktx | Bin 0 -> 203800 bytes examples/public/person.astc.ktx | Bin 1048676 -> 476884 bytes examples/public/person.etc.ktx | Bin 524388 -> 476884 bytes examples/public/person.png | Bin 0 -> 107539 bytes examples/public/person.pvrtc.ktx | Bin 262244 -> 262244 bytes examples/public/person.s3tc.ktx | Bin 476904 -> 238512 bytes examples/public/plane.astc.ktx | Bin 0 -> 1048676 bytes examples/public/plane.etc.ktx | Bin 0 -> 1048676 bytes examples/public/plane.png | Bin 0 -> 111675 bytes examples/public/plane.pvrtc.ktx | Bin 0 -> 262244 bytes examples/public/plane.s3tc.ktx | Bin 0 -> 524408 bytes examples/public/shannon.astc.ktx | Bin 262244 -> 262244 bytes examples/public/shannon.etc.ktx | Bin 131172 -> 131172 bytes examples/public/shannon.etc1.ktx | Bin 0 -> 131172 bytes examples/public/shannon.pvrtc.ktx | Bin 65636 -> 65636 bytes examples/public/shannon.s3tc.ktx | Bin 262264 -> 131192 bytes examples/public/spine/dance-cat/1.atlas | 322 + examples/public/spine/dance-cat/1.json | 47542 ++++++++++++++++ examples/public/yanhua.astc.ktx | Bin 407460 -> 0 bytes examples/public/yanhua.etc.ktx | Bin 203780 -> 0 bytes examples/public/yanhua.pvrtc.ktx | Bin 1048676 -> 0 bytes examples/public/yanhua.s3tc.ktx | Bin 407480 -> 0 bytes examples/src/a11y.ts | 20 +- examples/src/compressed-texture-animation.ts | 334 + examples/src/compressed-texture-mipmap.ts | 145 + examples/src/compressed-texture-test.ts | 147 + examples/src/compressed-texture.ts | 140 +- examples/src/dragonbone.ts | 3 +- examples/src/event.ts | 14 +- examples/src/loader.ts | 4 +- examples/src/lottie.ts | 4 +- examples/src/mask.ts | 2 +- examples/src/ninePatch.ts | 6 +- examples/src/sound.ts | 2 +- examples/src/spine.ts | 62 +- examples/src/spriteAnimation.ts | 2 +- examples/src/transition.ts | 2 +- examples/tsconfig.json | 56 +- examples/vite-env.d.ts | 4 + examples/vite.config.ts | 8 +- package.json | 2 +- packages/eva.js/lib/index.ts | 6 +- packages/eva.js/lib/loader/Resource.ts | 41 +- packages/eva.js/lib/loader/resourceLoader.ts | 27 + packages/eva.js/package.json | 2 +- packages/miniprogram-adapter/package.json | 4 +- packages/miniprogram-pixi/package.json | 2 +- packages/plugin-a11y/package.json | 4 +- packages/plugin-evax/package.json | 4 +- packages/plugin-matterjs/package.json | 4 +- .../plugin-renderer-dragonbone/package.json | 8 +- packages/plugin-renderer-event/package.json | 8 +- .../plugin-renderer-graphics/package.json | 6 +- packages/plugin-renderer-img/package.json | 10 +- packages/plugin-renderer-lottie/package.json | 8 +- packages/plugin-renderer-mask/package.json | 10 +- .../plugin-renderer-nine-patch/package.json | 8 +- packages/plugin-renderer-render/package.json | 10 +- .../plugin-renderer-spine/lib/SpineData.ts | 1 - .../plugin-renderer-spine/lib/TexCache.ts | 3 - packages/plugin-renderer-spine/package.json | 8 +- packages/plugin-renderer-spine38/package.json | 8 +- .../package.json | 10 +- packages/plugin-renderer-sprite/package.json | 8 +- packages/plugin-renderer-test/package.json | 2 +- packages/plugin-renderer-text/package.json | 10 +- .../package.json | 8 +- packages/plugin-renderer/lib/System.ts | 12 +- .../lib/compressedTexture/ability.ts | 55 + .../lib/compressedTexture/const.ts | 186 + .../compressedTexture/fix/BaseTextureMixin.ts | 26 + .../compressedTexture/fix/GLTextureMixin.ts | 35 + .../compressedTexture/fix/KTXLoadStrategy.ts | 11 + .../lib/compressedTexture/fix/TextureMixin.ts | 13 + .../compressedTexture/fix/XhrLoadStrategy.ts | 6 + .../lib/compressedTexture/fix/loader.ts | 31 + .../lib/compressedTexture/global.d.ts | 89 + .../lib/compressedTexture/index.ts | 17 + .../resource/CompressedTextureResource.ts | 70 + .../resource/KTXTextureResource.ts | 142 + packages/plugin-renderer/lib/index.ts | 3 + packages/plugin-renderer/lib/mixin.ts | 20 + packages/plugin-renderer/package.json | 11 +- packages/plugin-sound/package.json | 4 +- packages/plugin-stats/package.json | 4 +- packages/plugin-transition/package.json | 4 +- packages/renderer-adapter/package.json | 2 +- tsconfig.json | 2 +- 108 files changed, 49548 insertions(+), 239 deletions(-) create mode 100644 examples/public/back.astc.ktx create mode 100644 examples/public/back.etc.ktx create mode 100644 examples/public/back.png create mode 100644 examples/public/back.pvrtc.ktx create mode 100644 examples/public/block.astc.ktx create mode 100644 examples/public/block.etc.ktx create mode 100644 examples/public/block.png create mode 100644 examples/public/block.pvrtc.ktx create mode 100644 examples/public/block.s3tc.ktx create mode 100644 examples/public/cat.astc.ktx create mode 100644 examples/public/cat.etc.ktx create mode 100644 examples/public/cat.png create mode 100644 examples/public/cat.pvrtc.ktx create mode 100644 examples/public/cat.s3tc.ktx create mode 100644 examples/public/cut.astc.ktx create mode 100644 examples/public/cut.etc.ktx rename examples/public/{yanhua.png => cut.png} (100%) create mode 100644 examples/public/cut.pvrtc.ktx create mode 100644 examples/public/cut.s3tc.ktx create mode 100644 examples/public/person.png create mode 100644 examples/public/plane.astc.ktx create mode 100644 examples/public/plane.etc.ktx create mode 100644 examples/public/plane.png create mode 100644 examples/public/plane.pvrtc.ktx create mode 100644 examples/public/plane.s3tc.ktx create mode 100644 examples/public/shannon.etc1.ktx create mode 100644 examples/public/spine/dance-cat/1.atlas create mode 100644 examples/public/spine/dance-cat/1.json delete mode 100644 examples/public/yanhua.astc.ktx delete mode 100644 examples/public/yanhua.etc.ktx delete mode 100644 examples/public/yanhua.pvrtc.ktx delete mode 100644 examples/public/yanhua.s3tc.ktx create mode 100644 examples/src/compressed-texture-animation.ts create mode 100644 examples/src/compressed-texture-mipmap.ts create mode 100644 examples/src/compressed-texture-test.ts create mode 100644 packages/eva.js/lib/loader/resourceLoader.ts create mode 100644 packages/plugin-renderer/lib/compressedTexture/ability.ts create mode 100644 packages/plugin-renderer/lib/compressedTexture/const.ts create mode 100644 packages/plugin-renderer/lib/compressedTexture/fix/BaseTextureMixin.ts create mode 100644 packages/plugin-renderer/lib/compressedTexture/fix/GLTextureMixin.ts create mode 100644 packages/plugin-renderer/lib/compressedTexture/fix/KTXLoadStrategy.ts create mode 100644 packages/plugin-renderer/lib/compressedTexture/fix/TextureMixin.ts create mode 100644 packages/plugin-renderer/lib/compressedTexture/fix/XhrLoadStrategy.ts create mode 100644 packages/plugin-renderer/lib/compressedTexture/fix/loader.ts create mode 100644 packages/plugin-renderer/lib/compressedTexture/global.d.ts create mode 100644 packages/plugin-renderer/lib/compressedTexture/index.ts create mode 100644 packages/plugin-renderer/lib/compressedTexture/resource/CompressedTextureResource.ts create mode 100644 packages/plugin-renderer/lib/compressedTexture/resource/KTXTextureResource.ts create mode 100644 packages/plugin-renderer/lib/mixin.ts diff --git a/.gitignore b/.gitignore index 5371c838..0a9cc494 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,8 @@ temp/ api/ stats.html +# vite +vite-test/dist examples/spine38.html examples/loader.html package-lock.json \ No newline at end of file diff --git a/examples/index.html b/examples/index.html index 72e74c73..092dc155 100644 --- a/examples/index.html +++ b/examples/index.html @@ -7,6 +7,7 @@ Eva.js Examples