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.
Moving average for velocity (exponential decay)
Trying to plug it in as cleanly as possible into d3.drag
Original research by Philippe Rivière for visionscarto.net.
Try it on mobile!
forked from Fil's block: Inertia dragging with versor
xxxxxxxxxx
<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);
var inertia = {
start: function(started) {
return function() {
var position = d3.mouse(this);
if (inertia.timer) inertia.timer.stop();
inertia.velocity = [0,0];
started(inertia.position = position);
};
},
move: function(moved) {
return function() {
var position = d3.mouse(this),
time = performance.now(),
decay = 1 - Math.exp(- (time - inertia.time) / 1000);
inertia.velocity = inertia.velocity.map(function(d,i) {
return 1000 * (1 - decay) * (position[i] - inertia.position[i]) / (time - inertia.time) + d * decay;
});
inertia.time = time;
moved(inertia.position = position);
};
},
end: function(started, moved, ended) {
var A = 5000; // reference time in ms
var limit = 1.0001,
B = -Math.log(1 - 1 / limit);
return function() {
var v = inertia.velocity;
if (v[0]*v[0] + v[1]*v[1] < 100) return;
console.log('ended at position', inertia.position, 'with velocity', inertia.velocity);
started(inertia.position);
// versor velocity
var p0 = inertia.position.map((d,i) => d - inertia.velocity[i] / 1000),
v0 = versor.cartesian(projection.invert(p0)),
r0 = projection.rotate(),
q0 = versor(r0),
p1 = inertia.position,
v1 = versor.cartesian(projection.invert(p1));
inertia.timer = d3.timer(function(e) {
inertia.t = limit * (1 - Math.exp( - B * e / A )); // eased time
// standard position = positionstart + t * velocity
// var position = inertia.position.map((d,i) => d + t * inertia.velocity[i])
// moved(position);
// versor position = quaternionstart * rotation^t
var q1 = versor.multiply(q0, versor.delta(v0, v1, inertia.t * 1000)),
r1 = versor.rotation(q1);
projection.rotate(r1);
render();
if (inertia.t > 1) {
inertia.timer.stop();
inertia.velocity = [0,0];
inertia.t = 1;
}
});
if (ended) ended(inertia.position);
};
},
position: [0,0],
velocity: [0,0], // in pixels/s
time: 0,
timer: null,
};
canvas.call(
d3.drag()
.on("start", inertia.start(dragstarted))
.on("drag", inertia.move(dragged))
.on("end", inertia.end(dragstarted, dragged, 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.
function dragstarted(position) {
v0 = versor.cartesian(projection.invert(position));
r0 = projection.rotate();
q0 = versor(r0);
}
function dragged(position) {
var inv = projection.rotate(r0).invert(position);
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();
}
function dragended() {}
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();
context.beginPath();
context.moveTo(
inertia.position[0] + inertia.velocity[0] / 10,
inertia.position[1] + inertia.velocity[1] / 10
);
context.lineTo(
inertia.position[0] + inertia.velocity[0] * inertia.t / 10,
inertia.position[1] + inertia.velocity[1] * inertia.t / 10
);
context.strokeStyle = "red";
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>
https://unpkg.com/d3
https://d3js.org/topojson.v2.min.js