Skip to content

Instantly share code, notes, and snippets.

@ZJONSSON
Last active October 25, 2023 07:04
Show Gist options
  • Save ZJONSSON/1706523 to your computer and use it in GitHub Desktop.
Save ZJONSSON/1706523 to your computer and use it in GitHub Desktop.
Newton's cradle

This is a very rudimentary implementation of 2D elastic collisions.

Each node supposed to collide must contain a "radius" and a "mass". Fixed nodes are assumed to have "infinite mass"

The equations should maintain constant momentum in the system (less any friction)

/*
Newtonian collision checking (draft)
based on the paper: http://www.vobarian.com/collisions/2dcollisions2.pdf
[email protected]
*/
if (typeof d3.z != "object") d3.z = {};
(function() {
// Initiates fully elastic collision
d3.z.collide = function(force) {
force.on("tick.collide", function(d) {
var candidates = force.nodes().filter(function(d) { return d.radius && (d.mass > 0 || d.fixed)}), // Fixed nodes are always candidates
q = d3.geom.quadtree(candidates),
i = 0,
n = candidates.length;
while (++i < n) {
node = candidates[i];
if (node.mass >0) q.visit(d3.z.check_collision(node));
}
})
}
d3.z.deflect = function(force,x1,y1,x2,y2) {
force.on("tick.deflect", function(e) {
var nodes = force.nodes(),n=nodes.length,i=0
console.log(n)
while(++i<n) {
var node = nodes[i],radius = n.radius || 0;
if ( ((node.x-radius) < x1) || ((node.x+radius) > x2) ) {node.px = 2*node.x - node.px}
if ( ((node.y-radius) < y1) || ((node.y+radius) > y2) ) {node.py = 2*node.y - node.py}
}
})
}
vector = function(x,y) {
this.x = x; this.y = y
}
vector.prototype.add = function(d) {
return (typeof d == "object") ? new vector(this.x+d.x,this.y+d.y) : new vector(this.x+d,this.y+d)
}
vector.prototype.subtract = function(d) {
return (typeof d == "object") ? new vector(this.x-d.x,this.y-d.y) : new vector(this.x-d,this.y-d)
}
vector.prototype.mult = function(d) {
return (typeof d == "object") ? new vector(this.x*d.x,this.y*d.y) : new vector(this.x*d,this.y*d)
}
vector.prototype.dot = function(d) {
return (typeof d == "object") ? this.x*d.x+this.y*d.y : this.x*d+this.y*d
}
vector.prototype.length = function() {
return Math.sqrt(this.dot(this))
}
vector.prototype.norm = function() {
var length = this.length();
return new vector(this.x/length,this.y/length)
}
vector.prototype.tangent = function() {
return new vector(this.y,-this.x)
}
vector.prototype.change = function(d,x,y) {
d[x ? x : "x"] = this.x, d[y ? y : "y"] = this.y
}
d3.z.check_collision = function(node) {
var radius = node.radius;
nx1 = node.x - radius,
nx2 = node.x + radius,
ny1 = node.y - radius,
ny2 = node.y + radius;
return function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== node)) {
var b1 = node,
b2 = quad.point,
r = b1.radius+b2.radius,
_p1 = new vector(b1.x,b1.y),
_p2 = new vector(b2.x,b2.y),
_n = _p2.subtract(_p1),
l = _n.length()
if (l < r) {
var m1 = b1.fixed ? 1e99 : b1.mass,
m2 = b2.fixed ? 1e99 : b2.mass,
totalmass = m1+m2,
mT = _n.norm().mult(r-l)
_v1 = new vector(b1.x-b1.px,b1.y-b1.py),
_v2 = new vector(b2.x-b2.px,b2.y-b2.py),
_un = _n.norm(),
_ut = new vector(-_un.y,_un.x),
v1n = _un.dot(_v1),
v1t = _ut.dot(_v1),
v2n = _un.dot(_v2),
v2t = _ut.dot(_v2),
V1n = (v1n * (m1-m2) + 2*m2 * v2n ) / (totalmass),
V2n = (v2n * (m2-m1) + 2*m1 * v1n ) / (totalmass),
_V1 = _un.mult(V1n).add(_ut.mult(v1t)),
_V2 = _un.mult(V2n).add(_ut.mult(v2t)),
_p1 = _p1.subtract(mT.mult(m2/totalmass))
_p2 = _p2.add(mT.mult(m1/totalmass))
b1.x = _p1.x;
b1.y = _p1.y;
b2.x = _p2.x;
b2.y = _p2.y
b1.px = b1.x - _V1.x
b1.py = b1.y - _V1.y
b2.px = b2.x - _V2.x
b2.py = b2.y - _V2.y
}
}
return x1 > nx2
|| x2 < nx1
|| y1 > ny2
|| y2 < ny1;
};
}
})()
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="https://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.geom.js"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.layout.js"></script>
<script type="text/javascript" src="d3.z.collide.js"></script>
<style>
.ball { fill:steelblue;stroke:black}
.wire {stroke:black;stroke-widh:1;fill:none}
</style>
</head>
<body>
<script type="text/javascript">
var w=960,h=500,nodes=[], wires=[], links=[],
no_strands = 7, no_links = 5;
svg=d3.select("body")
.append("svg:svg")
.attr("height",h)
.attr("width",w)
d3.range(no_strands).forEach(function(strand) {
var n = d3.range(no_links).map(function(d,i) { return {x:100+strand*20,y:100+d*25,fixed:false}})
nodes=nodes.concat(n)
wires.push(n)
d3.range(n.length-1).forEach(function(d,i) { links.push({target:n[i],source:n[i+1]})})
n[0].fixed=true // fix the top circle
n[n.length-1].mass=10 // and give the last ball mass and radius
n[n.length-1].radius=10
})
balls = svg.selectAll(".ball")
.data(nodes.filter(function(d) { return d.radius > 0}))
balls.enter()
.append("circle")
.attr("class","ball")
.attr("r",function(d) { return d.radius})
lines = svg.selectAll("path")
.data(wires)
lines.enter()
.append("path")
.attr("class","wire")
force = d3.layout.force()
.linkStrength(10)
.linkDistance(25)
.friction(1)
.nodes(nodes).links(links)
.charge(0)
.on("tick.redraw",redraw)
.size([200,9999]).gravity(0.001)
.start()
balls.call(force.drag)
d3.z.collide(force)
function redraw() {
balls.attr("cx",function(d) { return d.x})
balls.attr("cy",function(d) { return d.y})
lines.attr("d", d3.svg.line().interpolate("cardinal")
.x(function(d) { return d.x})
.y(function(d) { return d.y}))
force.resume()
}
</script>
</body>
</html>
@Rnhatch
Copy link

Rnhatch commented Apr 30, 2013

This is a scream. I wonder if any of Newton's findings would have emerged had his cradle had stretchy strings and he had as much fun as I did playing around with it... I think the serious and scientific would be restored if you forced string lenght to a constant. But I'm such a Hack I don't know how to do that...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment