<!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>