An update to a previous block, but with collision detection and Canvas. This block uses Geometric.js for geometric calculations.
TODO:
xxxxxxxxxx
<html>
<head>
<style>
body {
margin: 0;
}
</style>
</head>
<body>
<div id="simulation"></div>
<script src="https://d3js.org/d3-random.v1.min.js"></script>
<script src="https://unpkg.com/geometric@2/build/geometric.min.js"></script>
<script>
// The Simulation class
class Simulation {
init(opts){
this.width = opts && opts.width ? opts.width : innerWidth;
this.height = opts && opts.height ? opts.height : innerHeight;
this.center = [this.width / 2, this.height / 2];
this.data = [];
return this;
}
add(datum){
const d = datum || {};
d.pos = d.pos || this.center;
d.radius = d.radius || 5;
d.angle = d.angle || 0;
d.speed = d.speed || 1;
this.data.push(d);
return this;
}
tick(){
// Loop through the data
for (let i = 0; i < this.data.length; i++){
const d = this.data[i];
d.collided = false;
// Detect collisions
for (let i0 = 0; i0 < this.data.length; i0++){
const d0 = this.data[i0];
d0.collided = false;
// Collision!
if (i !== i0 && geometric.lineLength([d.pos, d0.pos]) < d.radius + d0.radius && !d.collided && !d0.collided){
// To avoid having them stick to each other,
// test if moving them in each other's angles will bring them closer or farther apart
const keep = geometric.lineLength([
geometric.pointTranslate(d.pos, d.angle, d.speed),
geometric.pointTranslate(d0.pos, d0.angle, d0.speed)
]),
swap = geometric.lineLength([
geometric.pointTranslate(d.pos, d0.angle, d0.speed),
geometric.pointTranslate(d0.pos, d.angle, d.speed)
]);
if (keep < swap) {
const dc = JSON.parse(JSON.stringify(d));
d.angle = d0.angle;
d.speed = d0.speed;
d0.angle = dc.angle;
d0.speed = dc.speed;
d.collided = true;
d0.collided = true;
}
break;
}
}
// Detect sides
const wallVertical = d.pos[0] <= d.radius || d.pos[0] >= this.width - d.radius,
wallHorizontal = d.pos[1] <= d.radius || d.pos[1] >= this.height - d.radius;
if (wallVertical || wallHorizontal){
// Is it moving more towards the middle or away from it?
const t0 = geometric.pointTranslate(d.pos, d.angle, d.speed);
const l0 = geometric.lineLength([this.center, t0]);
const reflected = geometric.angleReflect(d.angle, wallVertical ? 90 : 0);
const t1 = geometric.pointTranslate(d.pos, reflected, d.speed);
const l1 = geometric.lineLength([this.center, t1]);
if (l1 < l0) d.angle = reflected;
}
d.pos = geometric.pointTranslate(d.pos, d.angle, d.speed);
}
}
}
// Initiate a simulation
const mySimulation = (_ => {
const simulation = new Simulation;
// Initialize this simulation with simulation.init
// You can pass an optional configuration object to init with the properties:
// - width
// - height
simulation.init();
// We'll create 100 circles of random radii, moving in random directions at random speeds.
for (let i = 0; i < 100; i++){
const radius = d3.randomUniform(4, 10)();
// Add a circle to your simulation with simulation.add
simulation.add({
speed: d3.randomUniform(1, 3)(),
angle: d3.randomUniform(0, 360)(),
pos: [
d3.randomUniform(radius, simulation.width - radius)(),
d3.randomUniform(radius, simulation.height - radius)()
],
radius
});
}
return simulation;
})();
// Draw the simulation
const wrapper = document.getElementById("simulation");
const canvas = document.createElement("canvas");
canvas.width = mySimulation.width;
canvas.height = mySimulation.height;
canvas.style.background = "black";
wrapper.appendChild(canvas);
const ctx = canvas.getContext("2d");
ctx.fillStyle = "steelblue";
ctx.strokeStyle = "white";
function tick(){
requestAnimationFrame(tick);
ctx.clearRect(0, 0, mySimulation.width, mySimulation.height);
// The simulation.tick method advances the simulation one tick
mySimulation.tick();
for (let i = 0, l = mySimulation.data.length; i < l; i++){
const d = mySimulation.data[i];
ctx.beginPath();
ctx.arc(...d.pos, d.radius, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
}
}
tick();
</script>
</body>
</html>
https://d3js.org/d3-random.v1.min.js
https://unpkg.com/geometric@2/build/geometric.min.js