Note: Best viewed with Google Chrome; not tested on any other browser. If you view with another browser or on a smartphone the animation may not be smooth due to the computations of trajectories happening as they are drawn.
This demonstrates the power of numerical integration of nonlinear equations using Javascript in the browser. I would have never considered using Javascript for such a task if I had not seen the benchmark tests for Julia and Numeric.js.
The animation is based on an influential movie from my childhood, Wargames. I must have watched it a hundred times as a kid. Since I've been trying to learn D3, I thought I'd try to recreate a portion of the final scene (you can watch here) using javascript. Some thoughts on the scene:
I used the following tools:
My repository contains the earth model and a basic trajectory creation tool for a missile from a launch and impact point (many realistic effects are ignored like atmospheric drag and the need to boost a rocket to the desired velocity). All calculations are done in the browser. The initial point to the trajectories are created on load and the trajectories are flown during the animation.
Sorry, this animation does not support chess or tic-tac-toe :)
xxxxxxxxxx
<meta charset="utf-8">
<style>
svg {
background: #222;
}
.graticule {
fill: none;
stroke: none;
stroke-opacity: .5;
stroke-width: .5px;
}
.land {
fill: none;
stroke: #fff;
stroke-width: 0.5px;
}
.boundary {
fill: none;
stroke: #ccc;
stroke-width: .5px;
}
.bomb {
fill: none;
stroke: #fff;
stroke-width: 2.5px;
stroke-opacity: 0.7;
}
circle.bomb {
fill: #fff;
fill-opacity: 1.0;
stroke: none;
filter: url(#glowimpact);
}
p {
position: absolute;
top: 350px;
color: #fff;
font-family: "Courier New", Courier, monospace;
font-weight: 100;
font-size: 70px;
text-align: center;
width: 960px;
}
</style>
<body>
<p id="text"></p>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://d3js.org/topojson.v0.min.js"></script>
<script src="https://d3js.org/queue.v1.min.js"></script>
<script src="math.js"></script>
<script src="numeric.js"></script>
<script>
var width = 960,
height = 500;
// Scenario Data
var USSR=643,
USA=840,
russia = [[30,50],[140,70]],
usa = [[-120,25],[-67,50]],
launch_intervals = 1,
reaction_time = 200,
randloc = function (extent){
var lon = extent[0][0] + (extent[1][0]-extent[0][0])*Math.random(),
lat = extent[0][1] + (extent[1][1]-extent[0][1])*Math.random();
return [lat,lon,0];
};
// Functions for orbital calculations
var earth = math.geo.earth("spherical inertial"),
orbital = math.geo.orbital();
var title = d3.select("p");
var projection = d3.geo.mercator()
.scale((width + 1) / 2 / Math.PI)
.translate([width / 2, 400])
.precision(.1);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("filter")
.attr("id", "glow")
.append("feGaussianBlur")
.attr("stdDeviation", 1);
svg.append("filter")
.attr("id", "glowimpact")
.append("feGaussianBlur")
.attr("stdDeviation", 3);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
queue()
.defer(d3.json,"/d/4090846/world-50m.json")
.defer(scenario,russia,usa)
.defer(scenario,usa, russia)
.await(plot);
function scenario(aggressor, defender, callback){
var launches = [], impacts = [], trajectories=[];
for (var i=0;i<20;i++){
var aloc = randloc(aggressor),
dloc = randloc(defender);
launches.push(aloc);
impacts.push(dloc);
var traj = orbital(aloc,dloc);
trajectories.push({time: Math.random()*launch_intervals, state: traj, history:[], impact: false});
}
callback(null, trajectories)
}
function plot(error, world, rlocs, ulocs){
var land = topojson.object(world, world.objects.land),
countries = topojson.object(world, world.objects.countries).geometries,
borders = topojson.mesh(world, world.objects.countries, function(a, b) { return a.id !== b.id; });
var aggressors = countries.filter(function (a){ return a.id==USSR || a.id==USA;});
svg.selectAll("path.aggressors", ".graticule")
.data(aggressors).enter().append("path")
.attr("class", "aggressors")
.attr("fill","none")
.attr("stroke",function (d) { return d.id==USSR ? "red" : "blue";})
.attr("d", path);
svg.insert("path", ".graticule")
.datum(land)
.attr("class", "land")
.attr("d", path);
svg.insert("path", ".graticule")
.datum(borders)
.attr("class", "boundary")
.attr("d", path);
d3.transition()
.duration(5000)
.each("start", function() {
// title.text("USSR FIRST STRIKE");
})
.tween("rotate", function() {
var time = d3.interpolate(0,8000),
radius = d3.interpolate(1,50);
tlast = 0;
return function(k) {
var tk = time(k);
if ((tk-tlast) < 20) return;
tlast = tk;
var features = [];
var impacts = [];
rlocs.forEach(function (d,i){
propagate(d,tk);
features.push({type: "LineString", coordinates:d.history});
if (d.impact)
impacts.push(d.history[d.history.length-1]);
});
if (tk>reaction_time){
ulocs.forEach(function (d){
propagate(d,tk);
features.push({type: "LineString", coordinates:d.history});
if (d.impact)
impacts.push(d.history[d.history.length-1]);
});
}
var bombs = svg
.selectAll("path.bomb")
.data(features);
bombs.enter().append("path")
.attr("class", "bomb")
.attr("d", path);
bombs
.attr("d", path);
// var craters = svg
// .selectAll("circle.bomb")
// .data(impacts,function (d){return d[0];});
//
// craters.enter().append("circle")
// .attr("class", "bomb")
// .attr("r", 1.0)
// .attr("cx",function (d) { return projection(d)[0];})
// .attr("cy",function (d) {
// return projection(d)[1];
// });
//
// craters
// .attr("r", function (d) { return radius(k);});
};
})
.transition()
.each("end", function (){
// title.text("WINNER: NONE");
});
}
function propagate(d,tk){
if (!d.impact & tk >= d.time){
var sol = numeric.dopri(d.time,tk, d.state,function (t,x){
var g = earth(x);
return [x[3],x[4],x[5],g[0],g[1],g[2]];
},
1e-8,
1000,
function (t,y){
var gc = earth.toGeodetic(y);
return 0 - gc[2];
});
d.time = sol.x[sol.x.length-1];
d.state = sol.y[sol.x.length-1];
var gc = earth.toGeodetic(d.state);
d.impact = gc[2] < 10 && tk>100 ? true : false;
d.history.push([gc[1],gc[0]]);
}
}
</script>
Modified http://d3js.org/d3.v3.min.js to a secure url
Modified http://d3js.org/topojson.v0.min.js to a secure url
Modified http://d3js.org/queue.v1.min.js to a secure url
https://d3js.org/d3.v3.min.js
https://d3js.org/topojson.v0.min.js
https://d3js.org/queue.v1.min.js