Skip to content

Instantly share code, notes, and snippets.

@vasturiano
Last active October 22, 2017 04:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vasturiano/2da88fb89cc75d18b20d8a7776fd6860 to your computer and use it in GitHub Desktop.
Save vasturiano/2da88fb89cc75d18b20d8a7776fd6860 to your computer and use it in GitHub Desktop.
Orbital Trajectory

A simulation of orbital trajectories using the d3-force simulation engine with the gravity-like d3-force-magnetic attraction force.

The initial tangential velocity of the blue orbiting body can be set relative to its orbital speed, calculated as √(GM/d). A factor of 1x (default) results in a perfectly circular orbit. Approaching the factor of √2x reaches the body's escape velocity, causing the body to drift away from the central object's gravity pull in an hyperbolic trajectory. Factors below 1 will cause a quicker fall of the blue body towards the attracting center, yielding elliptic orbits with increasing eccentricity.

The length of the pre-estimated trajectory can be manipulated by changing the number of samples (represented as dots).

See also Force-simulated Solar System and Hierarchical Orbits.

<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/4.9.1/d3.min.js"></script>
<script src="//unpkg.com/d3-force-magnetic"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<canvas id="trails"></canvas>
<svg id="scaffold">
<circle r="10" style="fill: red"></circle>
<circle id="satellite" r="5" style="fill: rgba(0, 0, 255, .6)"></circle>
<circle id="ghost" r="5" style="fill: rgba(0, 0, 255, .6)"></circle>
</svg>
<div id="controls">
Velocity:
<input class="slider-control" type="range" min="0.01" max="2" value="1" step="0.001" oninput="onVelocityChange(this.value)">
<span id="velocity-val">1</span>x orbital velocity
<br/>
<input type="number" min="0" max="50000" value="5000" step="1000" oninput="onNumSamplesChange(this.value)" style="margin-top: 5px;">
samples
</div>
<script src="index.js"></script>
</body>
const width = window.innerWidth, height = window.innerHeight;
const orbitDistance = height / 4,
G = 1e-3 * Math.pow(orbitDistance, 3), // Proportional to cube of orbit distance to maintain behavior over different heights
centralMass = 1,
orbitalV = Math.sqrt(G * centralMass / orbitDistance);
let initialV = orbitalV, numPnts = 5000;
// Draw scaffold canvas
d3.select('#scaffold')
.attr('width', width)
.attr('height', height)
.attr('viewBox', `${-width/2} ${-height/2} ${width} ${height}`);
d3.select('#ghost').attr('cy', -orbitDistance);
simulateTrajectory(orbitalV, numPnts);
//
function simulateTrajectory(initV, numTicks) {
const satellite = {
mass: 0,
x: 0,
y: -orbitDistance,
vx: initV,
vy: 0
},
forceSim = d3.forceSimulation()
.alphaDecay(0)
.velocityDecay(0)
.stop()
.force('gravity', d3.forceMagnetic()
.strength(G)
.charge(d => d.mass)
)
.nodes([
{ mass: centralMass },
satellite
]);
// Clear canvas
const ctx = d3.select('canvas#trails')
.attr('width', width)
.attr('height', height)
.node()
.getContext('2d');
ctx.translate(width/2, height/2);
ctx.fillStyle = 'rgba(0, 0, 75, .35)';
d3.range(numTicks).forEach(() => {
forceSim.tick();
ctx.beginPath();
ctx.fillRect(satellite.x, satellite.y, 1, 1);
ctx.fill();
});
// Animate satellite
const elSatellite = d3.select('#satellite').datum(satellite);
satellite.x = 0;
satellite.y = -orbitDistance;
satellite.vx = initV;
satellite.vy = 0;
forceSim.restart()
.on('tick', () => {
elSatellite.attr('cx', d => d.x)
.attr('cy', d => d.y);
});
}
// Event handlers
function onVelocityChange(relV) {
d3.select('#velocity-val').text(relV);
initialV = relV * orbitalV;
simulateTrajectory(initialV, numPnts);
}
function onNumSamplesChange(num) {
d3.select('#samples-val').text(num);
numPnts = num;
simulateTrajectory(initialV, numPnts);
}
body {
text-align: center;
font-family: Sans-serif;
margin: 0;
}
#scaffold, #controls {
position: absolute;
top: 0;
left: 0;
}
#controls {
font-size: 14px;
text-align: left;
margin: 8px;
padding: 1px 5px 5px 5px;
background: rgba(230, 230, 250, 0.7);
opacity: 0.5;
border-radius: 3px;
z-index: 1000;
}
#controls:hover {
opacity: 1;
}
.slider-control {
width: 350px;
position: relative;
top: 3px;
cursor: grab;
cursor: -webkit-grab;
}
.slider-control:active {
cursor: grabbing;
cursor: -webkit-grabbing;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment