gravity/panning.js
2026-01-03 13:10:38 -06:00

118 lines
3.3 KiB
JavaScript

import {add, copy, div, mult, sub, zero} from "./vector.js";
export class Panning {
sim = undefined;
touchStart = undefined;
touchLatest = undefined;
paused = false;
velocity = zero;
constructor(sim) {
this.sim = sim;
}
handlePointerDown({x, y}) {
this.initializeTouch({x, y});
}
initializeTouch({x, y}) {
this.touchStart = {
x,
y,
t: this.sim.rawTime,
viewOrigin: copy(this.sim.display.viewOrigin),
};
this.touchLatest = {
...this.touchStart,
dx: 0,
dy: 0,
dt: 0,
};
}
// With fast panning, panning velocity calculation happens every move;
// With normal panning, calculation only happens at pointer up.
handlePointerMove({x, y}) {
if (this.touchStart) {
this.touchLatest = {
x,
y,
t: this.sim.rawTime,
dx: x - this.touchStart.x,
dy: x - this.touchStart.y,
dt: this.sim.rawTime - this.touchStart.t,
};
if (this.sim.getOption('compensate.fastPanning')) {
this.updateVelocity();
}
}
}
handlePointerUp() {
if (this.touchStart && this.touchLatest) {
if (this.touchLatest.dt === 0) {
this.velocity = zero;
}
this.touchStart = undefined;
if (this.sim.getOption('compensate.fastPanning')) {
this.velocity = zero;
} else {
this.updateVelocity();
}
}
}
frame(elapsedTime) {
const {touchStart: start, touchLatest: latest} = this;
const {display} = this.sim;
// Direct translate, unless using fast panning
if (start && latest && !this.sim.getOption('compensate.fastPanning')) {
// start and latest are in screen coordinates, need to convert to sim scale
const delta = div(sub(latest, start), display.scale);
display.viewOrigin = sub(start.viewOrigin, delta);
}
// Apply update to viewOrigin based on panning
if (!this.paused) {
// elapsedTime is scaled by time scale, is that what we want?
// Yes because if panning.velocity == obj.velocity, object should stay in view
const delta = mult(this.velocity, elapsedTime);
display.viewOrigin = add(display.viewOrigin, delta);
}
// Update what's considered start
if (start && latest) {
this.initializeTouch(this.touchLatest);
}
if (this.sim.getOption('debug.panningInfo')) {
const {x, y} = this.sim.panning?.velocity ?? {};
this.sim.info['Panning Velocity'] = [`${x?.toPrecision(6)}, `, y?.toPrecision(6)];
const {centerOfMass} = this.sim.system.computeSystemCenter();
this.sim.info['Center of Mass'] = [`${centerOfMass.x.toPrecision(6)}, `, centerOfMass.y.toPrecision(6)];
this.sim.info['Net Angular Momentum'] = this.sim.system.computeSystemAngularMomentum().toPrecision(6);
}
}
updateVelocity() {
// Convert pointer velocity to simulation scale, and multiply by -1
// because the camera is panning opposite to the pointer velocity.
let velocity = div(this.sim.pointer.latestVelocity, -this.sim.display.scale);
if (this.sim.getOption('compensate.timeScale')) {
velocity = div(velocity, this.sim.timeScale);
}
// Also add current panning
velocity = add(velocity, this.velocity);
this.velocity = velocity;
}
setVelocity(velocity) {
this.velocity = velocity;
if (!this.sim.playing) {
this.paused = true;
}
}
}