renderMovers = function*(
{
visibility,
width = 200,
height = 200,
speed = 1,
overflow,
trace = false,
forces = [],
liquids = [],
attractors = [],
drawBkg = (ctx, width, height) => {}
},
...movers
) {
const checkEdges = overflow || wrapMover({ width, height });
const ctx = DOM.context2d(width, height);
let trailsCtx;
const srcDim = [0, 0, width * devicePixelRatio, height * devicePixelRatio];
const destDim = [0, 0, width, height];
if (trace) {
trailsCtx = DOM.context2d(width, height);
trailsCtx.strokeStyle = '#00f';
trailsCtx.lineWidth = 1;
trailsCtx.globalAlpha = 0.2;
}
if (attractors.length === 0) {
attractors.push(...movers.filter(m => !!m.attractionForce));
}
const mouse = {
pos: null,
down: false,
shift: false
};
ctx.canvas.onmousemove = e => {
mouse.pos = new THREE.Vector2(e.offsetX, e.offsetY);
};
ctx.canvas.onmouseleave = e => {
mouse.pos = null;
mouse.down = false;
mouse.shift = false;
};
ctx.canvas.onmousedown = e => {
mouse.down = true;
mouse.shift = e.shiftKey;
};
ctx.canvas.onmouseup = e => {
mouse.down = false;
mouse.shift = false;
};
const update = mover => {
const start = [mover.x, mover.y];
forces.forEach(force => mover.applyForce(force));
liquids.forEach(
liquid => liquid.contains(mover) && applyDrag(mover, liquid)
);
attractors.forEach(attractor => {
if (attractor !== mover) {
mover.applyForce(attractor.attractionForce(mover));
}
});
mover.update({
mouse
});
const end = [mover.x, mover.y];
checkEdges(mover);
if (trace) {
trailsCtx.beginPath();
trailsCtx.moveTo(...start);
trailsCtx.lineTo(...end);
trailsCtx.stroke();
trailsCtx.closePath();
}
};
while (true) {
repeat(() => movers.forEach(update), speed);
ctx.clearRect(0, 0, width, height);
drawBkg(ctx, width, height);
liquids.forEach(liquid => liquid.display(ctx));
attractors
.filter(attractor => !movers.includes(attractor))
.forEach(attractor => attractor.display(ctx));
if (trace) {
ctx.drawImage(trailsCtx.canvas, ...srcDim, ...destDim);
}
movers.forEach(mover => mover.display(ctx));
yield visibility(ctx.canvas);
}
}