201 lines
6.1 KiB
JavaScript
201 lines
6.1 KiB
JavaScript
import {
|
|
DISPLAY_CURSOR_INFO,
|
|
DRAGGABLE_ELEMENT_CLASSNAME,
|
|
MODE_MASS_GENERATION,
|
|
MODE_PAN_VIEW,
|
|
POINTER_HISTORY_SIZE,
|
|
ZOOM_IN_FACTOR,
|
|
ZOOM_OUT_FACTOR,
|
|
} from './config.js';
|
|
|
|
export class Pointer {
|
|
sim = undefined;
|
|
|
|
pointerHistory = [];
|
|
draggingElement = undefined;
|
|
panning = undefined;
|
|
suppressClick = false;
|
|
|
|
constructor(sim) {
|
|
this.sim = sim;
|
|
|
|
// Monitor mouse movements
|
|
const el = window;
|
|
|
|
el.addEventListener('pointermove', e => {
|
|
if (DISPLAY_CURSOR_INFO) {
|
|
this.sim.info['pointermove'] = [`${e.clientX}, `, `${e.clientY}`];
|
|
}
|
|
|
|
if (this.draggingElement) {
|
|
this.draggingElement.dragging.pointerEnd = {
|
|
x: e.clientX,
|
|
y: e.clientY,
|
|
};
|
|
} else {
|
|
// const {x, y} = this.sim.screenToSim(e.clientX, e.clientY);
|
|
this.handlePointerMove({x: e.clientX, y: e.clientY});
|
|
}
|
|
});
|
|
|
|
el.addEventListener('pointerdown', e => {
|
|
// If this is a child of a draggable element, handle dragging
|
|
let target = e.target;
|
|
while (target && !target.classList.contains(DRAGGABLE_ELEMENT_CLASSNAME)) {
|
|
target = target.parentElement;
|
|
}
|
|
|
|
if (target?.classList.contains(DRAGGABLE_ELEMENT_CLASSNAME)) {
|
|
this.draggingElement = target;
|
|
this.draggingElement.dragging = {
|
|
elementStart: {
|
|
x: parseInt(this.draggingElement.style.left),
|
|
y: parseInt(this.draggingElement.style.top),
|
|
},
|
|
pointerStart: {
|
|
x: e.clientX,
|
|
y: e.clientY,
|
|
},
|
|
pointerEnd: {
|
|
x: e.clientX,
|
|
y: e.clientY,
|
|
},
|
|
};
|
|
} else {
|
|
this.handlePointerDown({x: e.clientX, y: e.clientY});
|
|
}
|
|
});
|
|
|
|
el.addEventListener('pointerup', e => {
|
|
this.clearPointerHistory();
|
|
if (this.draggingElement) {
|
|
this.draggingElement.dragging = undefined;
|
|
this.draggingElement = undefined;
|
|
this.lastPosition = {x: undefined, y: undefined};
|
|
} else {
|
|
this.handlePointerUp({x: e.clientX, y: e.clientY});
|
|
}
|
|
});
|
|
|
|
// Monitor wheel events
|
|
el.addEventListener('wheel', e => {
|
|
const factor = e.deltaY < 0 ? ZOOM_IN_FACTOR : ZOOM_OUT_FACTOR;
|
|
const {x, y} = this.sim.screenToSim(e.clientX, e.clientY);
|
|
this.sim.scheduleZoom({x, y}, factor);
|
|
});
|
|
}
|
|
|
|
getPointerVelocity() {
|
|
// Average over pointer history
|
|
if (this.pointerHistory.length < 2) {
|
|
return {x: 0, y: 0, dt: 1};
|
|
}
|
|
const start = this.pointerHistory[0];
|
|
const end = this.pointerHistory[this.pointerHistory.length - 1];
|
|
const dt = (end.t - start.t) / 1000;
|
|
// Bonus scale factor for pointer power
|
|
return {
|
|
x: (end.x - start.x) / dt * this.sim.display.scale,
|
|
y: (end.y - start.y) / dt * this.sim.display.scale,
|
|
dt
|
|
};
|
|
}
|
|
|
|
getPointerAcceleration() {
|
|
// Average over pointer history
|
|
if (this.pointerHistory.length < 2) {
|
|
return {x: 0, y: 0, dt: 1};
|
|
}
|
|
const start = this.pointerHistory[0];
|
|
const end = this.pointerHistory[this.pointerHistory.length - 1];
|
|
const dt = (end.t - start.t) / 1000;
|
|
return {
|
|
x: (end.v.x - start.v.x) / dt,
|
|
y: (end.v.y - start.v.y) / dt,
|
|
dt
|
|
};
|
|
}
|
|
|
|
clearPointerHistory() {
|
|
this.pointerHistory = [];
|
|
}
|
|
|
|
updatePointer({x, y}) {
|
|
const t = document.timeline.currentTime;
|
|
while (this.pointerHistory.length >= POINTER_HISTORY_SIZE) {
|
|
this.pointerHistory.shift();
|
|
}
|
|
const v = this.getPointerVelocity();
|
|
const a = this.getPointerAcceleration();
|
|
this.pointerHistory.push({t, x, y, v, a});
|
|
}
|
|
|
|
handlePointerDown({x: clientX, y: clientY}) {
|
|
this.updatePointer({x: clientX, y: clientY});
|
|
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
|
|
const {x, y} = this.sim.screenToSim(clientX, clientY)
|
|
this.sim.objects.handlePointerDown({x, y});
|
|
|
|
} else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) {
|
|
this.panning = this.panning || {};
|
|
this.panning.gathering = true;
|
|
this.panning.velocity = {x: 0, y: 0};
|
|
}
|
|
}
|
|
|
|
handlePointerUp({x: clientX, y: clientY}) {
|
|
this.clearPointerHistory();
|
|
|
|
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
|
|
const {x, y} = this.sim.screenToSim(clientX, clientY);
|
|
this.sim.objects.handlePointerUp({x, y});
|
|
|
|
} else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) {
|
|
if (this.panning?.gathering) {
|
|
this.panning.gathering = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle cursor (mouse or touch) movement
|
|
// TODO: If e.touches.length > 1, user may be engaging pinch to zoom
|
|
handlePointerMove({x: clientX, y: clientY}) {
|
|
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
|
|
this.updatePointer({x: clientX, y: clientY});
|
|
const {x, y} = this.sim.screenToSim(clientX, clientY);
|
|
const velocity = this.getPointerVelocity();
|
|
// Convert pointer velocity to sim internal scale
|
|
const vx = velocity.x / this.sim.display.scale;
|
|
const vy = velocity.y / this.sim.display.scale;
|
|
this.sim.objects.handlePointerMove({x, y, vx, vy});
|
|
|
|
} else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) {
|
|
if (this.panning?.gathering) {
|
|
this.updatePointer({x: clientX, y: clientY});
|
|
const velocity = this.getPointerVelocity();
|
|
const acceleration = this.getPointerAcceleration();
|
|
// Convet to sim coordinates
|
|
// Let's try incorporating pointer acceleration
|
|
this.panning.velocity.x = velocity.x + acceleration.x * velocity.dt;
|
|
this.panning.velocity.y = velocity.y + acceleration.y * velocity.dt;
|
|
}
|
|
}
|
|
}
|
|
|
|
computeFrame(elapsedTime) {
|
|
// Add another entry for the current pointer position
|
|
if (this.pointerHistory?.length) {
|
|
const currentPointer = this.pointerHistory[this.pointerHistory.length - 1];
|
|
this.updatePointer(currentPointer);
|
|
}
|
|
|
|
// Apply update to viewOrigin based on panning
|
|
if (this.panning && !this.panning.paused) {
|
|
const {velocity} = this.panning;
|
|
// Convert pointer velocity to sim internal scale
|
|
this.sim.display.viewOrigin.x -= velocity.x * elapsedTime;
|
|
this.sim.display.viewOrigin.y -= velocity.y * elapsedTime;
|
|
}
|
|
}
|
|
}
|