Skip to content

Commit

Permalink
fix(core): improve mouse wheel inertia
Browse files Browse the repository at this point in the history
It now uses lerp smoothing and behaves well, regardless of the frame
rate.

Closes #166
  • Loading branch information
tuner committed Feb 15, 2024
1 parent 0005f05 commit 5dc308c
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 76 deletions.
28 changes: 15 additions & 13 deletions packages/core/src/genomeSpy.js
Original file line number Diff line number Diff line change
Expand Up @@ -669,25 +669,31 @@ export default class GenomeSpy {
// that would also contain state-related stuff that currently pollute the
// GenomeSpy class.

let lastWheelEvent = performance.now();

/** @param {Event} event */
const listener = (event) => {
const now = performance.now();
const wheeling = now - lastWheelEvent < 200;

if (event instanceof MouseEvent) {
if (event.type == "mousemove") {
const rect = canvas.getBoundingClientRect();
const point = new Point(
event.clientX - rect.left - canvas.clientLeft,
event.clientY - rect.top - canvas.clientTop
);

if (event.type == "mousemove" && !wheeling) {
this.tooltip.handleMouseMove(event);
this._tooltipUpdateRequested = false;

if (event.buttons == 0) {
// Disable during dragging
this.renderPickingFramebuffer();
this._handlePicking(point.x, point.y);
}
}

const rect = canvas.getBoundingClientRect();
const point = new Point(
event.clientX - rect.left - canvas.clientLeft,
event.clientY - rect.top - canvas.clientTop
);

/**
* @param {MouseEvent} event
*/
Expand All @@ -705,14 +711,10 @@ export default class GenomeSpy {
this._wheelInertia.cancel();
}

if (event.type == "mousemove") {
this._handlePicking(point.x, point.y);
} else if (
event.type == "mousedown" ||
event.type == "mouseup"
) {
if (event.type == "mousedown" || event.type == "mouseup") {
this.renderPickingFramebuffer();
} else if (event.type == "wheel") {
lastWheelEvent = now;
this._tooltipUpdateRequested = false;

const wheelEvent = /** @type {WheelEvent} */ (event);
Expand Down
91 changes: 28 additions & 63 deletions packages/core/src/utils/inertia.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { lerp } from "vega-util";
import { makeLerpSmoother } from "./animator.js";
import clamp from "./clamp.js";

/**
* Creates some inertia, mainly for zooming with a mechanical mouse wheel
Expand All @@ -11,35 +13,32 @@ export default class Inertia {
constructor(animator, disabled) {
this.animator = animator;
this.disabled = !!disabled;
this.damping = 0.015;
this.acceleration = 0.3; // per event
/** Use acceleration if the momentum step is greater than X */
this.accelerationThreshold = 100;
this.lowerLimit = 0.5; // When to stop updating
this.loop = false;

this.momentum = 0;
this.timestamp = 0;
// Limit the velocity by setting the maximum distance the value can travel
this.maxDistance = 500;

/** @type {function(number):void} */
this.callback = null;

this._transitionCallback = this.animate.bind(this);
this.clear();
}

clear() {
/** @type {number} */
this.momentum = 0;
this.timestamp = null;
this.loop = null;
this.callback = null;
this.targetValue = 0;
this.lastValue = 0;

this.smoother = makeLerpSmoother(
animator,
(value) => {
const delta = value - this.lastValue;
this.lastValue = value;
this.callback?.(delta);
},
40,
0.1
);
}

cancel() {
if (this.loop) {
this.animator.cancelTransition(this._transitionCallback);
this.clear();
}
// decelelerate rapidly
this.targetValue = lerp([this.lastValue, this.targetValue], 0.3);
this.smoother(this.targetValue);
}

/**
Expand All @@ -53,50 +52,16 @@ export default class Inertia {
return;
}

// This may have some use in the future to improve the behavior of
// a mechanical mouse wheel:
// https://github.com/w3c/uievents/issues/181

if (value * this.momentum < 0) {
this.momentum = 0; // Stop if the direction changes
} else if (Math.abs(value) > this.accelerationThreshold) {
this.momentum = lerp([this.momentum, value], this.acceleration);
} else {
this.momentum = value;
}

this.callback = callback;

if (!this.loop) {
this.animate();
}
}

/**
*
* @param {number} [timestamp]
*/
animate(timestamp) {
this.callback(this.momentum); // TODO: This is actually a delta, should take the elapsed time into account
const delta = clamp(
this.targetValue + value - this.lastValue,
-this.maxDistance,
this.maxDistance
);
this.targetValue = this.lastValue + delta;

const timeDelta = timestamp - this.timestamp || 0;
this.timestamp = timestamp;

const velocity = Math.abs(this.momentum);

this.momentum =
Math.sign(this.momentum) *
Math.max(
0,
velocity - ((velocity * this.damping) ** 1.5 + 0.04) * timeDelta
);

if (Math.abs(this.momentum) > this.lowerLimit) {
this.loop = true;
this.animator.requestTransition(this._transitionCallback);
} else {
this.clear();
}
this.smoother(this.targetValue);
}
}

Expand Down

0 comments on commit 5dc308c

Please sign in to comment.