Skip to content

Instantly share code, notes, and snippets.

@denisemauldin
Last active October 4, 2017 21:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save denisemauldin/2976a6db6944861de6478535594f7c2a to your computer and use it in GitHub Desktop.
Save denisemauldin/2976a6db6944861de6478535594f7c2a to your computer and use it in GitHub Desktop.
globe with Inertia dragging with versor
license: gpl-3.0

Add inertia issue #27 to versor rotation of the globe.

Includes a modified versor.js, allowing the multiplication of the final rotation by a delta to the power alpha.

Original research by Philippe Rivière for visionscarto.net.

forked from Fil's block: Inertia dragging with versor

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="https://unpkg.com/d3"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script src="versor.js"></script>
<script>
var width = 960,
height = 500;
var projection = d3.geoOrthographic();
var canvas = d3.select("body").append("canvas")
.attr("width", width)
.attr("height", height);
var context = canvas.node().getContext("2d");
var path = d3.geoPath()
.projection(projection)
.context(context);
canvas.call(
d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
var render = function() {},
v0, // Mouse position in Cartesian coordinates at start of drag gesture.
r0, // Projection rotation as Euler angles at start.
q0; // Projection rotation as versor at start.
var inertia = {};
function dragstarted() {
v0 = versor.cartesian(projection.invert(d3.mouse(this)));
r0 = projection.rotate();
q0 = versor(r0);
if (inertia.timer) inertia.timer.stop();
inertia = {};
}
function dragged() {
var inv = projection.rotate(r0).invert(d3.mouse(this));
if (isNaN(inv[0])) return;
var v1 = versor.cartesian(inv),
q1 = versor.multiply(q0, versor.delta(v0, v1)),
r1 = versor.rotation(q1);
projection.rotate(r1);
render();
inertia.q1 = q1;
inertia.v0 = inertia.v1;
inertia.v1 = v1;
inertia.date = inertia.datenew;
inertia.datenew = performance.now();
}
function dragended() {
if (!inertia.v0) return;
var inv = projection.rotate(r0).invert(d3.mouse(this));
if (isNaN(inv[0])) return;
var v2 = versor.cartesian(inv);
var A = 10;
var K = A * (performance.now() - inertia.date);
inertia.timer = d3.timer(function(e) {
var ee = Math.exp( - e / K ); // eased time
var q2 = versor.multiply(inertia.q1, versor.delta(inertia.v0, v2, A * (1 - ee))),
r2 = versor.rotation(q2);
projection.rotate(r2);
render();
if (ee < 0.05) inertia.timer.stop();
}, 50);
}
d3.json("https://unpkg.com/world-atlas@1/world/110m.json", function(error, world) {
if (error) throw error;
var land = topojson.feature(world, world.objects.land);
render = function() {
context.clearRect(0, 0, width, height);
context.beginPath();
path(land);
context.fill();
context.strokeStyle = 'black';
context.beginPath();
path({type:"Sphere"});
context.lineWidth = 2.5;
context.stroke();
var p = projection.rotate().map(d => Math.floor(10*d)/10);
context.fillText(`λ = ${p[0]}, φ = ${p[1]}, γ = ${p[2]}`, 10, 10 )
};
render();
});
d3.select(self.frameElement).style("height", height + "px");
</script>
// Version 0.0.0. Copyright 2017 Mike Bostock.
(function(global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.versor = factory());
}(this, (function() {'use strict';
var acos = Math.acos,
asin = Math.asin,
atan2 = Math.atan2,
cos = Math.cos,
max = Math.max,
min = Math.min,
PI = Math.PI,
sin = Math.sin,
sqrt = Math.sqrt,
radians = PI / 180,
degrees = 180 / PI;
// Returns the unit quaternion for the given Euler rotation angles [λ, φ, γ].
function versor(e) {
var l = e[0] / 2 * radians, sl = sin(l), cl = cos(l), // λ / 2
p = e[1] / 2 * radians, sp = sin(p), cp = cos(p), // φ / 2
g = e[2] / 2 * radians, sg = sin(g), cg = cos(g); // γ / 2
return [
cl * cp * cg + sl * sp * sg,
sl * cp * cg - cl * sp * sg,
cl * sp * cg + sl * cp * sg,
cl * cp * sg - sl * sp * cg
];
}
// Returns Cartesian coordinates [x, y, z] given spherical coordinates [λ, φ].
versor.cartesian = function(e) {
var l = e[0] * radians, p = e[1] * radians, cp = cos(p);
return [cp * cos(l), cp * sin(l), sin(p)];
};
// Returns the Euler rotation angles [λ, φ, γ] for the given quaternion.
versor.rotation = function(q) {
return [
atan2(2 * (q[0] * q[1] + q[2] * q[3]), 1 - 2 * (q[1] * q[1] + q[2] * q[2])) * degrees,
asin(max(-1, min(1, 2 * (q[0] * q[2] - q[3] * q[1])))) * degrees,
atan2(2 * (q[0] * q[3] + q[1] * q[2]), 1 - 2 * (q[2] * q[2] + q[3] * q[3])) * degrees
];
};
// Returns the quaternion to rotate between two cartesian points on the sphere.
versor.delta = function(v0, v1, alpha = 1) {
var w = cross(v0, v1), l = sqrt(dot(w, w));
if (!l) return [1, 0, 0, 0];
var t = alpha * acos(max(-1, min(1, dot(v0, v1)))) / 2, s = sin(t); // t = θ / 2
return [cos(t), w[2] / l * s, -w[1] / l * s, w[0] / l * s];
};
// Returns the quaternion that represents q0 * q1.
versor.multiply = function(q0, q1) {
return [
q0[0] * q1[0] - q0[1] * q1[1] - q0[2] * q1[2] - q0[3] * q1[3],
q0[1] * q1[0] + q0[0] * q1[1] + q0[2] * q1[3] - q0[3] * q1[2],
q0[0] * q1[2] - q0[1] * q1[3] + q0[2] * q1[0] + q0[3] * q1[1],
q0[0] * q1[3] + q0[1] * q1[2] - q0[2] * q1[1] + q0[3] * q1[0]
];
};
function cross(v0, v1) {
return [
v0[1] * v1[2] - v0[2] * v1[1],
v0[2] * v1[0] - v0[0] * v1[2],
v0[0] * v1[1] - v0[1] * v1[0]
];
}
function dot(v0, v1) {
return v0[0] * v1[0] + v0[1] * v1[1] + v0[2] * v1[2];
}
return versor;
})));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment