|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<script src="https://d3js.org/d3.v5.min.js"></script> |
|
|
|
<!-- |
|
|
|
|
|
|
|
*Note to self: do not edit this block on blockbuilder* |
|
|
|
|
|
|
|
--> |
|
|
|
<style> |
|
|
|
body { |
|
margin: 0; |
|
overflow: hidden; |
|
background: black; |
|
font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; |
|
} |
|
|
|
canvas { |
|
cursor: move; |
|
} |
|
|
|
svg, canvas { |
|
position:absolute; |
|
top:0; |
|
} |
|
|
|
.missions path { fill: white } |
|
|
|
.polygons path { |
|
fill: rgba(0,0,0,0.001); /* to receive mouseover events */ |
|
stroke: rgba(200,255,220,0.1); |
|
stroke-width: 1.5; |
|
} |
|
|
|
.links path { |
|
fill: none; |
|
stroke: rgba(180,90,90,0.9); |
|
stroke-width: 3; |
|
} |
|
|
|
path.glow { |
|
fill: none; |
|
stroke: rgba(255,255,255,0.7); |
|
stroke-width: 1; |
|
} |
|
|
|
</style> |
|
<canvas></canvas> |
|
<svg> |
|
<defs> |
|
<radialGradient id="grad1" cx="50%" cy="50%" r="50%" fx="50%" fy="50%"> |
|
<stop offset="0%" style="stop-color:black;stop-opacity:0" /> |
|
<stop offset="94%" style="stop-color:black;stop-opacity:0.01" /> |
|
<stop offset="97%" style="stop-color:black;stop-opacity:0.2" /> |
|
<stop offset="110%" style="stop-color:black;stop-opacity:0.6" /> |
|
</radialGradient> |
|
</defs> |
|
</svg> |
|
|
|
<script id="vertex-shader" type="x-shader/x-vertex"> |
|
|
|
attribute vec2 a_position; |
|
|
|
void main(void) { |
|
gl_Position = vec4(a_position, 0.0, 1.0); |
|
} |
|
|
|
</script> |
|
<script id="fragment-shader" type="x-shader/x-fragment"> |
|
|
|
precision mediump float; |
|
|
|
uniform sampler2D u_image; |
|
uniform vec2 u_translate; /* width/2, height/2 */ |
|
uniform float u_scale; /* in pixels ! */ |
|
uniform vec3 u_rotate; /* rotation in degrees ! */ |
|
|
|
const float c_pi = 3.14159265358979323846264; |
|
const float c_halfPi = c_pi * 0.5; |
|
const float c_twoPi = c_pi * 2.0; |
|
|
|
// Inclination of the equator on Mars = 25.19° (earth= 23.44) |
|
const float declination = 25.19 / 90.0 * c_halfPi; |
|
|
|
float phi0 = -u_rotate.y / 90.0 * c_halfPi; |
|
|
|
float cosphi0 = cos(phi0); |
|
float sinphi0 = sin(phi0); |
|
|
|
void main(void) { |
|
float x = (gl_FragCoord.x - u_translate.x) / u_scale; |
|
float y = (u_translate.y - gl_FragCoord.y) / u_scale; |
|
|
|
// inverse orthographic projection |
|
float rho = sqrt(x * x + y * y); |
|
|
|
// color if the point (px, py) does not exist in the texture |
|
if (rho >= 1.0) { |
|
gl_FragColor = texture2D(u_image, vec2(0.0, 0.0)); |
|
gl_FragColor[0] = 0.1*(rho-1.0+0.1); |
|
gl_FragColor[1] = 0.06*(rho-1.0+0.1); |
|
gl_FragColor[2] = 0.2*(rho-1.0+0.1); |
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
float cosc = cos(asin(rho)); |
|
float lambda = atan(x, cosc); |
|
float phi = asin(y); |
|
|
|
// inverse rotation |
|
float cosphi = cos(phi); |
|
float x0 = cos(lambda) * cosphi; |
|
float y0 = sin(lambda) * cosphi; |
|
float cosgamma = cos(u_rotate.z / 90.0 * c_halfPi); |
|
float singamma = sin(u_rotate.z / 90.0 * c_halfPi); |
|
float x1 = x0 * cosgamma - y0 * singamma; |
|
float y1 = y0 * cosgamma + x0 * singamma; |
|
float z1 = y; |
|
lambda = atan(y1, x1 * cosphi0 + z1 * sinphi0) - u_rotate.x / 90.0 * c_halfPi; |
|
phi = asin(z1 * cosphi0 - x1 * sinphi0); |
|
|
|
// pixels |
|
float px = (lambda + c_pi) / c_twoPi; |
|
float py = (phi + c_halfPi) / c_pi; |
|
|
|
gl_FragColor = texture2D(u_image, vec2(px, py)); |
|
|
|
// terminator ?? see https://github.com/joergdietrich/Leaflet.Terminator/blob/master/L.Terminator.js |
|
// float sinh = sin(lambda)*sin(declination) + cos(lambda)*cos(declination)*cos(1.0); |
|
// float intensity = (sinh > 0.0) ? 1.0 + 0.1*sinh : 0.2 + 0.8 * exp(6.0*sinh); |
|
|
|
float intensity = 1.1; // boost the pixel by some factor |
|
gl_FragColor[0] = intensity * gl_FragColor[0] * (1.3 - 0.3 * sqrt(gl_FragColor[0])); |
|
gl_FragColor[1] = intensity * gl_FragColor[1]; |
|
gl_FragColor[2] = intensity * gl_FragColor[2]; |
|
|
|
} |
|
} |
|
|
|
</script> |
|
<script> |
|
// Select the canvas from the document. |
|
var canvas = document.querySelector("canvas"); |
|
|
|
var width = +canvas.getAttribute('width') || 600, |
|
height = +canvas.getAttribute('height') || 400; |
|
width = Math.max(width, self.innerWidth); |
|
height = Math.max(height, self.innerHeight); |
|
canvas.setAttribute('width', width); |
|
canvas.setAttribute('height', height); |
|
|
|
// Create the WebGL context, with fallback for experimental support. |
|
var context = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); |
|
|
|
// Compile the vertex shader. |
|
var vertexShader = context.createShader(context.VERTEX_SHADER); |
|
context.shaderSource(vertexShader, document.querySelector("#vertex-shader").textContent); |
|
context.compileShader(vertexShader); |
|
if (!context.getShaderParameter(vertexShader, context.COMPILE_STATUS)) throw new Error(context.getShaderInfoLog(vertexShader)); |
|
|
|
// Compile the fragment shader. |
|
var fragmentShader = context.createShader(context.FRAGMENT_SHADER); |
|
context.shaderSource(fragmentShader, document.querySelector("#fragment-shader").textContent); |
|
context.compileShader(fragmentShader); |
|
if (!context.getShaderParameter(fragmentShader, context.COMPILE_STATUS)) throw new Error(context.getShaderInfoLog(fragmentShader)); |
|
|
|
// Link and use the program. |
|
var program = context.createProgram(); |
|
context.attachShader(program, vertexShader); |
|
context.attachShader(program, fragmentShader); |
|
context.linkProgram(program); |
|
if (!context.getProgramParameter(program, context.LINK_STATUS)) throw new Error(context.getProgramInfoLog(program)); |
|
context.useProgram(program); |
|
|
|
// Define the positions (as vec2) of the square that covers the canvas. |
|
var positionBuffer = context.createBuffer(); |
|
context.bindBuffer(context.ARRAY_BUFFER, positionBuffer); |
|
context.bufferData(context.ARRAY_BUFFER, new Float32Array([ |
|
-1.0, -1.0, |
|
+1.0, -1.0, |
|
+1.0, +1.0, |
|
-1.0, +1.0 |
|
]), context.STATIC_DRAW); |
|
|
|
// Bind the position buffer to the position attribute. |
|
var positionAttribute = context.getAttribLocation(program, "a_position"); |
|
context.enableVertexAttribArray(positionAttribute); |
|
context.vertexAttribPointer(positionAttribute, 2, context.FLOAT, false, 0, 0); |
|
|
|
// Extract the projection parameters. |
|
var translateUniform = context.getUniformLocation(program, "u_translate"), |
|
scaleUniform = context.getUniformLocation(program, "u_scale"), |
|
rotateUniform = context.getUniformLocation(program, "u_rotate"); |
|
|
|
// Load the reference image. |
|
var image = new Image; |
|
image.crossOrigin="anonymous"; |
|
image.src = "https://gist.githubusercontent.com/Fil/358e889380bfc9d8e4871cc9dc338cf9/raw/745cff357604fd7780b6efafec56dd7455a4a8d3/Mars_Viking_MDIM21_ClrMosaic_global_1024.jpg"; |
|
image.onload = readySoon; |
|
|
|
var projection = d3.geoOrthographic() |
|
.translate([width / 2, height / 2]) |
|
.scale(0.95 * height / 2); |
|
|
|
var path = d3.geoPath() |
|
.projection(projection); |
|
|
|
|
|
var svg = d3.select('svg') |
|
.attr('width', width) |
|
.attr('height', height); |
|
|
|
svg._defs = svg.append("defs"); |
|
svg._clip = svg._defs.append("path") |
|
.datum({ |
|
type: "Sphere" |
|
}) |
|
.attr("id", "sphere"); |
|
|
|
svg._defs.append("clipPath") |
|
.attr("id", "clip") |
|
.append("use") |
|
.attr("xlink:href", "#sphere"); |
|
|
|
svg._earth = svg.append('g') |
|
.attr("xlink:href", "#sphere") |
|
.style('cursor', '-webkit-grab'); |
|
|
|
svg._earth.append('use') |
|
.attr("xlink:href", "#sphere") |
|
.attr('fill', 'rgba(0,0,0,0.001)'); /* to receive mouseover events */ |
|
|
|
svg._shade = svg.append("use") |
|
.attr("class", "stroke") |
|
.attr("xlink:href", "#sphere") |
|
.attr("stroke", "black") |
|
.attr("stroke-width", 3) |
|
.attr("fill", "url(#grad1)") |
|
.attr('pointer-events', 'none'); |
|
|
|
// Hack to ensure correct inference of window dimensions. |
|
function readySoon() { |
|
setTimeout(function () { |
|
resize(); |
|
ready(); |
|
}, 10); |
|
} |
|
|
|
function resize() { |
|
canvas.setAttribute("width", width); |
|
canvas.setAttribute("height", height); |
|
context.uniform2f(translateUniform, width / 2, height / 2); |
|
context.viewport(0, 0, width, height); |
|
} |
|
|
|
function ready() { |
|
|
|
|
|
// Create a texture and a mipmap for accurate minification. |
|
var texture = context.createTexture(); |
|
context.bindTexture(context.TEXTURE_2D, texture); |
|
context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MAG_FILTER, context.LINEAR); |
|
context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MIN_FILTER, context.LINEAR_MIPMAP_LINEAR); |
|
context.texImage2D(context.TEXTURE_2D, 0, context.RGBA, context.RGBA, context.UNSIGNED_BYTE, image); |
|
context.generateMipmap(context.TEXTURE_2D); |
|
|
|
// The current rotation |
|
var scale = scale0 = projection.scale(), |
|
rotate = [0, 0, 0]; |
|
|
|
// Rotate and redraw! |
|
function redraw() { |
|
projection.scale(scale).rotate(rotate); |
|
svg._clip.attr('d', path); |
|
|
|
context.uniform1f(scaleUniform, scale); |
|
context.uniform3fv(rotateUniform, rotate); |
|
context.bindTexture(context.TEXTURE_2D, texture); // XXX Safari |
|
context.drawArrays(context.TRIANGLE_FAN, 0, 4); |
|
|
|
} |
|
|
|
svg._earth |
|
.on('mousemove', function () { |
|
var p = d3.mouse(this), |
|
c = projection.invert(p); |
|
/* do something with coords c */ |
|
}); |
|
|
|
|
|
var lambda = d3.scaleLinear() |
|
.domain([-width / 2, width / 2]) |
|
.range([-180, 180]); |
|
|
|
var phi = d3.scaleLinear() |
|
.domain([0, height]) |
|
.range([90, -90]); |
|
|
|
var q, r, transform, d; |
|
|
|
zoom = d3.zoom() |
|
.scaleExtent([.8, 1.5]) |
|
.on("start", function () { |
|
q = rotate, d = [0, 0, 0]; // accumulate change in d |
|
r = d3.mouse(this); |
|
svg._earth.style('cursor', '-webkit-grabbing'); |
|
}) |
|
.on("zoom.redraw", function () { |
|
scale = scale0 * d3.event.transform.k; |
|
var p = d3.mouse(this); |
|
var dr = [lambda(p[0]) - lambda(r[0]), phi(p[1]) - phi(r[1])]; |
|
r = p; |
|
|
|
// inverse dr[0] if the mouse is beyond one of the poles |
|
var a = (phi(p[1]) - rotate[1]) * Math.PI / 180, |
|
ca = Math.cos(a), |
|
sa = Math.sin(a); |
|
|
|
d = [d[0] + dr[0] * (ca < 0 ? -1 : 1), |
|
d[1] + dr[1], d[2] + dr[0] * -sa]; |
|
|
|
rotate = [q[0] + d[0], q[1] + d[1], q[2] + 0 * d[2]]; |
|
|
|
redraw(); |
|
}) |
|
.on("end", function() { |
|
svg._earth.style('cursor', '-webkit-grab'); |
|
}); |
|
|
|
d3.select("svg") |
|
.call(zoom); |
|
|
|
redraw(); |
|
|
|
var elapsed = null; |
|
|
|
function animate(t) { |
|
elapsed = t; |
|
//d3.select("canvas").transition().call(zoom.transform, d3.zoomIdentity); |
|
requestAnimationFrame(animate); |
|
} |
|
//animate(); |
|
|
|
} |
|
|
|
// A polyfill for requestAnimationFrame. |
|
if (!self.requestAnimationFrame) requestAnimationFrame = |
|
self.webkitRequestAnimationFrame || self.mozRequestAnimationFrame || self.msRequestAnimationFrame || self.oRequestAnimationFrame || function (f) { |
|
setTimeout(f, 17); |
|
}; |
|
</script> |