2024-07-30 09:56:53 -05:00
|
|
|
import K from 'kefir';
|
|
|
|
|
2024-08-01 00:39:54 -05:00
|
|
|
import * as easings from '@/util/easing.js';
|
2024-08-01 14:32:20 -05:00
|
|
|
import {TAU} from '@/util/math.js';
|
2024-08-01 00:39:54 -05:00
|
|
|
|
2024-07-30 09:56:53 -05:00
|
|
|
export default class Emitter {
|
|
|
|
constructor(ecs) {
|
|
|
|
this.ecs = ecs;
|
|
|
|
this.scheduled = [];
|
|
|
|
}
|
2024-08-03 15:25:41 -05:00
|
|
|
async configure(entityId, {fields, shape}) {
|
|
|
|
const entity = this.ecs.get(entityId);
|
2024-07-30 09:56:53 -05:00
|
|
|
if (shape) {
|
|
|
|
switch (shape.type) {
|
2024-08-01 14:32:20 -05:00
|
|
|
case 'circle': {
|
|
|
|
const r = Math.random() * TAU;
|
2024-08-03 15:25:41 -05:00
|
|
|
entity.Position.x += Math.cos(r) * shape.payload.radius;
|
|
|
|
entity.Position.y += Math.sin(r) * shape.payload.radius;
|
2024-08-01 14:32:20 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'filledCircle': {
|
|
|
|
const r = Math.random() * TAU;
|
2024-08-03 15:25:41 -05:00
|
|
|
entity.Position.x += Math.cos(r) * Math.random() * shape.payload.radius;
|
|
|
|
entity.Position.y += Math.sin(r) * Math.random() * shape.payload.radius;
|
2024-08-01 14:32:20 -05:00
|
|
|
break;
|
|
|
|
}
|
2024-07-30 09:56:53 -05:00
|
|
|
case 'filledRect': {
|
2024-08-03 15:25:41 -05:00
|
|
|
entity.Position.x += Math.random() * shape.payload.width - (shape.payload.width / 2);
|
|
|
|
entity.Position.y += Math.random() * shape.payload.height - (shape.payload.height / 2);
|
2024-07-30 09:56:53 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-08-01 00:39:54 -05:00
|
|
|
if (fields) {
|
|
|
|
for (const {easing = 'linear', path, value} of fields) {
|
2024-08-03 15:25:41 -05:00
|
|
|
let walk = entity;
|
2024-08-01 00:39:54 -05:00
|
|
|
const pathCopy = path.slice(0);
|
|
|
|
const final = pathCopy.pop();
|
|
|
|
for (const key of pathCopy) {
|
|
|
|
walk = walk[key];
|
|
|
|
}
|
|
|
|
const c = Math.random() * (value.length - 1);
|
|
|
|
const i = Math.floor(c);
|
|
|
|
walk[final] = easings[easing](c - i, value[i], value[i + 1] - value[i], 1);
|
|
|
|
}
|
|
|
|
}
|
2024-08-03 15:25:41 -05:00
|
|
|
return entity;
|
2024-07-30 09:56:53 -05:00
|
|
|
}
|
|
|
|
emit(particle) {
|
|
|
|
particle = {
|
|
|
|
...particle,
|
|
|
|
entity: {
|
|
|
|
Position: {},
|
|
|
|
Sprite: {},
|
|
|
|
...particle.entity,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
let {count = 1} = particle;
|
2024-08-03 16:02:09 -05:00
|
|
|
const {entity, frequency = 0, spurt = 1, ttl = 0} = particle;
|
|
|
|
if (ttl > 0) {
|
|
|
|
count = Math.floor(ttl / frequency);
|
|
|
|
}
|
2024-08-03 15:25:41 -05:00
|
|
|
const specifications = Array(count);
|
2024-08-03 16:02:09 -05:00
|
|
|
for (let i = 0; i < count * spurt; ++i) {
|
2024-08-03 15:25:41 -05:00
|
|
|
specifications[i] = entity;
|
|
|
|
}
|
|
|
|
const stream = K.stream(async (emitter) => {
|
|
|
|
const entityIds = await this.ecs.createManyDetached(specifications);
|
2024-07-30 09:56:53 -05:00
|
|
|
if (0 === frequency) {
|
2024-08-03 15:25:41 -05:00
|
|
|
this.ecs.attach(entityIds);
|
|
|
|
for (const entityId of entityIds) {
|
|
|
|
emitter.emit(this.configure(entityId, particle));
|
2024-07-30 09:56:53 -05:00
|
|
|
}
|
2024-08-03 15:25:41 -05:00
|
|
|
emitter.end();
|
2024-07-30 09:56:53 -05:00
|
|
|
return;
|
|
|
|
}
|
2024-08-03 15:25:41 -05:00
|
|
|
const it = entityIds.values();
|
2024-08-03 16:02:09 -05:00
|
|
|
const batch = new Set();
|
|
|
|
for (let i = 0; i < spurt; ++i) {
|
|
|
|
batch.add(it.next().value);
|
|
|
|
}
|
|
|
|
this.ecs.attach(batch);
|
|
|
|
for (const entityId of batch) {
|
|
|
|
emitter.emit(this.configure(entityId, particle));
|
|
|
|
}
|
2024-07-30 09:56:53 -05:00
|
|
|
count -= 1;
|
|
|
|
if (0 === count) {
|
2024-08-03 15:25:41 -05:00
|
|
|
emitter.end();
|
2024-07-30 09:56:53 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
let accumulated = 0;
|
|
|
|
const scheduled = (elapsed) => {
|
|
|
|
accumulated += elapsed;
|
|
|
|
while (accumulated > frequency && count > 0) {
|
2024-08-03 16:02:09 -05:00
|
|
|
const batch = new Set();
|
|
|
|
for (let i = 0; i < spurt; ++i) {
|
|
|
|
batch.add(it.next().value);
|
|
|
|
}
|
|
|
|
this.ecs.attach(batch);
|
|
|
|
for (const entityId of batch) {
|
|
|
|
emitter.emit(this.configure(entityId, particle));
|
|
|
|
}
|
2024-07-30 09:56:53 -05:00
|
|
|
accumulated -= frequency;
|
|
|
|
count -= 1;
|
|
|
|
}
|
|
|
|
if (0 === count) {
|
|
|
|
this.scheduled.splice(this.scheduled.indexOf(scheduled), 1);
|
2024-08-03 15:25:41 -05:00
|
|
|
emitter.end();
|
2024-07-30 09:56:53 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
this.scheduled.push(scheduled);
|
|
|
|
});
|
|
|
|
return stream;
|
|
|
|
}
|
|
|
|
|
|
|
|
tick(elapsed) {
|
|
|
|
for (const ticker of this.scheduled) {
|
|
|
|
ticker(elapsed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|