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