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.
xxxxxxxxxx
<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>
https://cdnjs.cloudflare.com/ajax/libs/d3/4.7.0/d3.min.js