Adapting d3.forceCollide to do elastic collisions (like billiards). You can click and drag the balls.
High school physics refresher from Wikipedia. Inspired by chatting with Robert Monfera and Chris Given's riffing on d3.forceCollide.
To-do:
forked from tophtucker's block: Elastic collisions
xxxxxxxxxx
<meta charset="utf-8">
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="forceCollideElastic.js"></script>
<script>
var width = 960
var height = 500
var numParticles = 4
var color = function() { return '#'+Math.floor(Math.random()*16777215).toString(16); }
var nodes = Array.apply(null, Array(numParticles)).map(function (_, i) {
var r = Math.random() * 60 + 20
var velocity = Math.random() * 2 + 1
var angle = Math.random() * 360
return {
x: Math.random() * (width - r),
y: Math.random() * (height - r),
vx: velocity * Math.cos(angle * Math.PI / 180),
vy: velocity * Math.sin(angle * Math.PI / 180),
r: r,
fill: color(i)
}
})
var drag = d3.drag()
.on('start', dragStarted)
.on('drag', dragged)
.on('end', dragEnded)
var svg = d3.select('body').append('svg')
var ball = svg
.attr('width', width)
.attr('height', height)
.selectAll('circle')
.data(nodes)
.enter().append('circle')
.style('fill', function (d) { return d.fill })
.attr('r', function (d) { return d.r })
.attr('cx', function (d) { return d.x })
.attr('cy', function (d) { return d.y })
.call(drag)
var collisionForce = forceCollideElastic()
.radius(function (d) { return d.r })
var boxForce = boundedBox()
.bounds([[0, 0], [width, height]])
.size(function (d) { return d.r })
d3.forceSimulation()
.velocityDecay(0)
.alphaTarget(1)
.on('tick', ticked)
.force('box', boxForce)
.force('collision', collisionForce)
.nodes(nodes)
function boundedBox() {
var nodes, sizes
var bounds
var size = constant(0)
function force() {
var node, size
var xi, x0, x1, yi, y0, y1
var i = -1
while (++i < nodes.length) {
node = nodes[i]
size = sizes[i]
xi = node.x + node.vx
x0 = bounds[0][0] - (xi - size)
x1 = bounds[1][0] - (xi + size)
yi = node.y + node.vy
y0 = bounds[0][1] - (yi - size)
y1 = bounds[1][1] - (yi + size)
if (x0 > 0 || x1 < 0) {
node.x += node.vx
node.vx = -node.vx
if (node.vx < x0) { node.x += x0 - node.vx }
if (node.vx > x1) { node.x += x1 - node.vx }
}
if (y0 > 0 || y1 < 0) {
node.y += node.vy
node.vy = -node.vy
if (node.vy < y0) { node.vy += y0 - node.vy }
if (node.vy > y1) { node.vy += y1 - node.vy }
}
}
}
force.initialize = function (_) {
sizes = (nodes = _).map(size)
}
force.bounds = function (_) {
return (arguments.length ? (bounds = _, force) : bounds)
}
force.size = function (_) {
return (arguments.length
? (size = typeof _ === 'function' ? _ : constant(_), force)
: size)
}
return force
}
function ticked() {
ball
.attr('cx', function (d) { return d.x })
.attr('cy', function (d) { return d.y })
}
var px, py, vx, vy, offsetX, offsetY
function dragStarted(d) {
vx = 0
vy = 0
offsetX = (px = d3.event.x) - (d.fx = d.x)
offsetY = (py = d3.event.y) - (d.fy = d.y)
}
function dragged(d) {
d.vx = vx = d3.event.x - px
d.vy = vy = d3.event.y - py
d.fx = Math.max(Math.min((px = d3.event.x) - offsetX, width - d.r), d.r)
d.fy = Math.max(Math.min((py = d3.event.y) - offsetY, height - d.r), d.r)
}
function dragEnded(d) {
d.fx = null
d.fy = null
d.vx = vx;
d.vy = vy;
}
function constant(_) {
return function () { return _ }
}
</script>
</body>
https://d3js.org/d3.v4.min.js