{
const width = 800, height = 200;
const context = DOM.context2d(width, height);
const maxFrames = 2000;
let curTick = 0
const blendMode = "color-burn"
context.globalCompositeOperation = blendMode
const gScale = d3.scaleLinear()
.domain([1, 4])
.range([3, 10]);
const gravObjects = [
]
var line = d3.line()
.defined(function(d) { return d; })
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.context(context);
const genParticle = () => {
const angle = d3.randomUniform(-Math.PI/4, Math.PI/4)()
const vx = Math.cos(angle)
const vy = Math.sin(angle)
return {
id: (new Date()).getTime(),
position: new Vector(0, d3.randomUniform(0, height)()),
velocity: new Vector(vx, vy).scaleTo(1),
acceleration: new Vector(),
path: [],
active: true,
color: d3.interpolateYlOrRd(Math.random())
}
}
const particles = []
function tick() {
context.clearRect(0, 0, width, height);
// context.globalCompositeOperation = blendMode
if (Math.random() >= 0.97 ) {
particles.push(genParticle())
}
// Draw border
context.beginPath();
context.strokeStyle = '#ccc';
context.strokeRect(0, 0, width, height);
particles.filter(p => p.active).forEach(function(p){
p.acceleration = new Vector()
// Deal with the Fixed Objects
gravObjects.forEach(g => {
var diff = g.position.clone().subtract(p.position),
distance = diff.length();
// const f1 = new Vector()
// f1.add(diff.clone().scaleTo(g.mass/distance)).active = true;
// p.acceleration.add(f1);
if (distance > 20) {
const f1 = new Vector()
f1.add(diff.clone().scaleTo(g.mass/distance)).active = true;
p.acceleration.add(f1);
}
if (distance < 10) {
const f1 = new Vector()
f1.add(diff.clone().scaleTo(-3*g.mass/distance)).active = true;
p.acceleration.add(f1);
}
// const f2 = new Vector()
// f2.add(diff.clone().scaleTo(-distance)).active = true;
// p.acceleration.add(f2);
})
// Cross Forces
const alignmentForce = new Vector(),
cohesionForce = new Vector(),
separationForce = new Vector()
particles.filter(p => p.active).forEach(function(p2){
if (p === p2) return;
var diff = p2.position.clone().subtract(p.position),
distance = diff.length();
if (distance && distance < 30) {
separationForce.add(diff.clone().scaleTo(-1 / distance)).active = true;
}
if (distance < 60) {
cohesionForce.add(diff).active = true;
alignmentForce.add(p2.velocity).active = true;
}
});
if (alignmentForce.active) {
alignmentForce.scaleTo(2)
.subtract(p.velocity)
.truncate(0.03);
p.acceleration.add(alignmentForce);
}
if (cohesionForce.active) {
cohesionForce.scaleTo(2)
.subtract(p.velocity)
.truncate(0.03);
p.acceleration.add(cohesionForce);
}
if (separationForce.active) {
separationForce.scaleTo(2)
.subtract(p.velocity)
.truncate(0.05);
p.acceleration.add(separationForce);
}
});
particles.forEach(updateParticle);
gravObjects.forEach(updateGravObjs);
context.font = '18px';
context.fillStyle = 'black';
context.fillText(`Frame: ${curTick}`, 10, 10);
if (curTick++ < maxFrames) requestAnimationFrame(tick);
}
function updateParticle(b) {
if (b.active) {
b.position.add(b.velocity.add(b.acceleration).truncate(2));
}
if (b.position.y > height) {
b.active = false
} else if (b.position.y < 0) {
b.active = false
}
if (b.position.x > width) {
b.active = false
} else if (b.position.x < 0) {
b.active = false
}
b.path.push({x: b.position.x, y: b.position.y})
context.beginPath();
line(b.path);
context.lineWidth = 3;
context.strokeStyle = b.color;
context.stroke();
context.beginPath();
context.fillStyle = 'white';
context.arc(b.position.x, b.position.y, 4, 0, 2 * Math.PI);
context.fill();
}
function updateGravObjs(g) {
context.beginPath();
context.fillStyle = 'red';
context.arc(g.position.x, g.position.y, gScale(g.mass), 0, 2 * Math.PI);
context.fill();
}
requestAnimationFrame(tick);
return context.canvas
}