Files
cellxgene/client/src/util/camera.js
Bruce Martin eaae6df5e3 TS Revert (1) (#2402)
* revert all commits to before Typescript migration

* update compat workflow to match latest deps (#2335)

* update compat workflow to match latest deps

* attempt to debug

* attempt to debug

* remove debugging code

* typo

* update deps to match desktop (#2340)

* fix: don't run lint with `--fix` on push tests (#2273)

* fix: don't run lint with `--fix` on push tests

* npx

Co-authored-by: maniarathi <mani.arathi@gmail.com>
Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com>

* rename X_approx_distribution to X_approximate_distribution (#2337)

* Correctly handle non-finite numbers in heuristic determination of X distribution (#2342)

* handle non-finites explicitly

* improve and test edge case handling for distribution estimation

* revert debugging changes

* code readability

* clean up type inferencing (#2332)

* unit tests for 64 bit conversion

* clean up type handling

* type inference tests

* more type inference fixes

* use schema to determine user intent for data typing

* stop using deprecated API

* fbs type encoding test

* add missing test

* add more tests

* correctly infer X type for CXG adaptor

* lint

* fix typo

* ts migration

* cleanup from PR review

* lint

* PR review changes

* remove unused packages from client (#2359)

* remove unused packages from client

* add missing peer dep

* fix: disable FE auth testing on compatibility tests (#2377)

* update: release process (#2277)

Co-authored-by: maniarathi <mani.arathi@gmail.com>

* fix: remove spaces in param setup (#2380)

* delete deploy workflow (#2396)

* undo reformatting which now does not pass lint

* fix snapshots which changed due to npm dep changes

* add missing quoting to snapshot

* another snapshot typo fix

* TS Revert (2) - replay PR #2347 and #2354 (#2403)

* replay edits from PR 2347

* TS Revert (3) - replay edits in PR #2327 (#2404)

* replay edits in PR 2327

* TS Revert (4) - replay PR #2355 (#2405)

* replay edits in PR 2355

* add additional babel config

* reformat with new prettier config

Co-authored-by: Severiano Badajoz <sbadajoz@chanzuckerberg.com>
Co-authored-by: maniarathi <mani.arathi@gmail.com>
Co-authored-by: Madison Dunitz <madison.dunitz@chanzuckerberg.com>
2021-08-23 15:01:36 -07:00

181 lines
4.0 KiB
JavaScript

import { vec2, mat3 } from "gl-matrix";
const EPSILON = 0.000001;
const scaleSpeed = 0.5;
const scaleMax = 3.0;
const scaleMin = 0.5;
const panBound = 0.8;
// private
const scratch0 = new Float32Array(16);
const scratch1 = new Float32Array(16);
function clamp(val, rng) {
return Math.max(Math.min(val, rng[1]), rng[0]);
}
class Camera {
constructor(canvas) {
this.prevEvent = {
clientX: 0,
clientY: 0,
type: 0,
};
this.canvas = canvas;
this.viewMatrix = mat3.create();
this.viewMatrixInv = mat3.create();
}
view() {
return this.viewMatrix;
}
invView() {
return this.viewMatrixInv;
}
distance() {
return this.viewMatrix[0];
}
pan(dx, dy) {
const m = this.viewMatrix;
const dyRange = [
-panBound - (m[7] + 1) / m[4],
panBound - (m[7] - 1) / m[4],
];
const dxRange = [
-panBound - (m[6] + 1) / m[0],
panBound - (m[6] - 1) / m[0],
];
const dxClamped = clamp(dx, dxRange);
const dyClamped = clamp(dy, dyRange);
if (Math.abs(dxClamped) <= EPSILON && Math.abs(dyClamped) <= EPSILON)
return;
mat3.translate(m, m, [dxClamped, dyClamped]);
mat3.invert(this.viewMatrixInv, m);
}
zoomAt(d, x = 0, y = 0) {
/*
Camera zoom at [x,y]
*/
const m = this.viewMatrix;
const bounds = [-panBound, panBound];
x = clamp(x, bounds);
y = clamp(y, bounds);
const dClamped = clamp(d * m[0], [scaleMin, scaleMax]) / m[0];
if (Math.abs(1 - dClamped) <= EPSILON) return; // noop request
mat3.translate(m, m, [x, y]);
mat3.scale(m, m, [dClamped, dClamped]);
mat3.translate(m, m, [-x, -y]);
mat3.invert(this.viewMatrixInv, m);
}
/*
Event handling
*/
flush(e) {
this.prevEvent.type = e.type;
this.prevEvent.clientX = e.clientX;
this.prevEvent.clientY = e.clientY;
}
localPosition(target, canvasX, canvasY, projectionInvTF) {
/*
Convert mouse position to local
*/
const { height, width } = target;
const targetRect = target.getBoundingClientRect();
canvasX -= targetRect.left;
canvasY -= targetRect.top;
const pos = vec2.fromValues(
2 * (canvasX / width) - 1,
-2 * (canvasY / height) + 1
);
if (projectionInvTF) {
vec2.transformMat3(pos, pos, projectionInvTF);
}
vec2.transformMat3(pos, pos, this.invView());
return pos;
}
mousePan(e, projectionTF) {
const projectionInvTF = mat3.invert(scratch0, projectionTF);
const pos = this.localPosition(
this.canvas,
e.clientX,
e.clientY,
projectionInvTF
);
const prev = this.localPosition(
this.canvas,
this.prevEvent.clientX,
this.prevEvent.clientY,
projectionInvTF
);
const delta = vec2.sub(scratch1, pos, prev);
this.pan(delta[0], delta[1]);
return true;
}
wheelZoom(e, projectionTF) {
const { height } = this.canvas;
const { deltaY, deltaMode, clientX, clientY } = e;
const scale = scaleSpeed * (deltaMode === 1 ? 12 : 1) * (deltaY || 0);
const projectionInvTF = mat3.invert(scratch0, projectionTF);
const pos = this.localPosition(
this.canvas,
clientX,
clientY,
projectionInvTF
);
this.zoomAt(1 / Math.exp(scale / height), pos[0], pos[1]);
return true;
}
handleEvent(e, projectionTF) {
/*
process the event, and return true if camera view changed
*/
let viewChanged = false;
switch (e.type) {
case "mousemove": {
/* eslint-disable-next-line no-bitwise --- MouseEvent.buttons exposes a bitmask, best acted on with bitops */
if (e.buttons & 0x1) {
viewChanged = this.mousePan(e, projectionTF);
}
this.flush(e);
break;
}
case "wheel": {
viewChanged = this.wheelZoom(e, projectionTF);
this.flush(e);
break;
}
default:
// noop
break;
}
return viewChanged;
}
}
function attachCamera(canvas) {
return new Camera(canvas);
}
export default attachCamera;