avocado-old/packages/physics/traits/emitter.trait.js
2020-06-23 22:08:53 -05:00

273 lines
6.6 KiB
JavaScript

import K from 'kefir';
import {compose, merge} from '@avocado/core';
import {Entity, StateProperty, Trait} from '@avocado/entity';
import {Vector} from '@avocado/math';
import {Ticker} from '@avocado/timing';
import {Proton} from '../proton';
const PI_180 = Math.PI / 180;
const decorate = compose(
);
export default class Emitter extends decorate(Trait) {
static behaviorTypes() {
return {
emitParticleEntity: {
cycle: true,
type: 'entity',
label: 'Create particle.',
args: [
['entity', {
type: 'entity',
}],
],
},
emitParticleJson: {
type: 'stream',
label: 'Create particle.',
args: [
['json', {
type: 'object',
}],
],
},
emitParticle: {
type: 'stream',
label: 'Create particle.',
args: [
['key', {
type: 'string',
options: (entity) => (
Object.keys(entity.traitInstance('emitter').particles)
.reduce((r, key) => ({...r, [key]: key}), {})
),
}],
['json', {
type: 'object',
}],
],
},
};
}
static defaultParams() {
return {
particles: {},
};
}
static describeParams() {
return {
particles: {
type: 'object',
label: 'Particles',
},
};
}
static type() {
return 'emitter';
}
constructor(entity, params, state) {
super(entity, params, state);
this.emissions = [];
this.emitter = new Proton.Emitter();
this.particles = {};
this.proton = new Proton();
this.proton.addEmitter(this.emitter);
this.onParticleDead = this.onParticleDead.bind(this);
this.onParticleUpdate = this.onParticleUpdate.bind(this);
this.emitter.bindEvent = true;
this.emitter.addEventListener('PARTICLE_DEAD', this.onParticleDead);
this.emitter.addEventListener('PARTICLE_UPDATE', this.onParticleUpdate);
}
gatherParticles() {
const particles = {};
const hookParticles = this.entity.invokeHookFlat('particles');
for (let i = 0; i < hookParticles.length; i++) {
const traitParticles = hookParticles[i];
for (const key in traitParticles) {
particles[key] = traitParticles[key];
}
}
for (const key in this.params.particles) {
if (particles[key]) {
particles[key] = merge({}, particles[key], this.params.particles[key]);
}
else {
particles[key] = this.params.particles[key];
}
}
this.particles = particles;
}
onParticleDead(particle) {
if (particle.body.isTransientParticle) {
particle.body.destroy();
}
}
onParticleUpdate(particle) {
particle.body.setPosition([particle.p.x, particle.p.y]);
particle.body.opacity = particle.alpha;
particle.body.visibleScale = [particle.scale, particle.scale];
particle.body.rotation = particle.rotation * PI_180;
}
hooks() {
const hooks = {};
hooks.afterDestructionTickers = () => {
return (elapsed) => {
this.tick(elapsed);
if (0 === this.emitter.particles.length) {
this.emitter.destroy();
return true;
}
};
};
return hooks;
}
emitParticleJson(json, emitter) {
Entity.loadOrInstance(json).then((particle) => {
this.entity.emitParticleEntity(particle);
emitter.emit(particle);
});
}
listeners() {
return {
traitAdded: () => {
this.gatherParticles();
},
};
}
methods() {
return {
emitParticleEntity: (entity) => {
const particle = entity.particle();
const position = particle.position ?
particle.position
:
this.entity.position
;
const initializers = [
new Proton.Body(entity),
new Proton.Position(new Proton.PointZone(
position[0],
position[1]
)),
new Proton.Mass(particle.mass),
new Proton.Life(particle.ttl),
new Proton.Velocity(
particle.velocity.magnitude,
particle.velocity.angle,
'polar'
),
];
const behaviors = [
new Proton.Alpha(
particle.alpha.start,
particle.alpha.end
),
new Proton.Force(
particle.force[0],
particle.force[1]
),
new Proton.Rotate(
particle.rotation.start,
particle.rotation.add,
'add'
),
new Proton.Scale(
particle.scale.start,
particle.scale.end
),
];
const protonParticle = this.emitter.createParticle(
initializers,
behaviors
);
entity.on('destroy', () => {
this.emitter.removeParticle(protonParticle);
});
if (particle.listed) {
this.entity.list.addEntity(entity);
}
// Prime.
this.onParticleUpdate(protonParticle);
return entity;
},
emitParticleJson: (json) => {
let {
count = 1,
rate = 0,
} = json;
const stream = K.stream((emitter) => {
if (0 === rate) {
for (let i = 0; i < count; ++i) {
this.emitParticleJson(json, emitter);
}
}
else {
const ticker = new Ticker(rate);
this.emitParticleJson(json, emitter);
count -= 1;
if (count > 0) {
const removeEmission = () => {
const index = this.emissions.indexOf(ticker);
if (-1 !== index) {
this.emissions.splice(index, 1);
}
emitter.end();
};
this.entity.on('destroy', removeEmission);
ticker.on('tick', () => {
this.emitParticleJson(json, emitter);
if (0 >= --count) {
removeEmission();
}
});
this.emissions.push(ticker);
return;
}
}
emitter.end();
});
// Always make sure something listens...
stream.onEnd(() => {});
return stream;
},
emitParticle: (key, json = {}) => {
const particleJson = this.particles[key]
if (!particleJson) {
return;
}
const mergedJson = merge({}, particleJson, json);
return this.entity.emitParticleJson(mergedJson);
},
};
}
tick(elapsed) {
this.emitter.update(elapsed);
for (let i = 0; i < this.emissions.length; i++) {
this.emissions[i].tick(elapsed);
}
}
}