Skip to content

Instantly share code, notes, and snippets.

@vasturiano
Last active January 30, 2018 22:22
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save vasturiano/148adfad4f05bf1e64ec1cf0bea9380c to your computer and use it in GitHub Desktop.
ForceLink based Hierarchical Orbits

Attempt at simulation of multiple particles orbitting around each other in a hierarchical system (e.g. sun > planet > moon).

Uses D3 force-directed physics engine's forceLink to connect particles to their moving gravitational centers via fixed position anchor nodes.

The system fails to reach equilibrium due to the "spring" nature of forceLink, in which contrary to gravitational forces the intensity decreases with proximity, causing bodies to inevitably fall onto their attraction center. The forceMagnetic force is a better fit for modelling this type of motion.

Compare with the forceMagnetic version.

<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/4.7.0/d3.min.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<svg id="canvas">
<g id="trails"></g>
<g id="bodies"></g>
</svg>
<script>
const width = window.innerWidth, height = window.innerHeight;
const gravity = 0.00005, // Regulates mechanics speed
bodies = [
{ id: 'sun', r: 20, initialState: { x: 0, y: 0 } },
{ id: 'planet', r: 8, initialState: { y: -height / 4, vx: 0.5 * height * Math.sqrt(gravity) } },
{ id: 'moon', r: 2, initialState: { y: -height * 3/8, vx: 0.25 * height * Math.sqrt(gravity * 100) } }
],
links = [
{ source: 'planet', target: 'sun-anchor', attraction: gravity },
{ source: 'moon', target: 'planet-anchor', attraction: gravity * 100 } // Higher attraction = more revolutions
];
// Generate body anchors
const anchors = bodies.map(body => { return { id: body.id + '-anchor', body: body } });
d3.forceSimulation()
.alphaDecay(0)
.velocityDecay(0)
.nodes([...bodies, ...anchors])
.force("gravitate-around", d3.forceLink(links)
.id(d => d.id)
.distance(0)
.strength(link => link.attraction || 0.1)
)
.on("tick", ticked);
// Set initial states
bodies.forEach(body => {
for (let k in body.initialState) {
body[k] = body.initialState[k];
}
});
// Add orbit trails
d3.timer(() => {
d3.selectAll('g.trail')
.append('circle')
.attr('r', 1.5)
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.transition().duration(10000)
.style('opacity', 0)
.remove();
});
// Size canvas
d3.select('#canvas')
.attr('width', width)
.attr('height', height)
.attr('viewBox', `${-width/2} ${-height/2} ${width} ${height}`);
//
function ticked() {
var body = d3.select('#bodies').selectAll('.body')
.data(bodies);
body.exit().remove();
body.merge(
body.enter().append('circle')
.attr('class', 'body')
.attr('id', d => d.id)
.attr('r', d => d.r)
)
.attr('cx', d => d.x)
.attr('cy', d => d.y);
// Add trail elements
var trails = d3.select('#trails').selectAll('.trail')
.data(bodies);
trails.exit().remove();
trails.enter().append('g').attr('class', 'trail');
fixAnchors();
}
function fixAnchors() {
anchors.forEach(anchor => {
anchor.fx = anchor.body.x;
anchor.fy = anchor.body.y;
});
}
</script>
</body>
body {
text-align: center;
font-family: Sans-serif;
margin: 0;
}
#sun {
fill: #ff4e2d;
}
#planet {
fill: #203390;
}
#moon {
fill: slategrey;
}
#trails circle {
fill: darkgrey;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment