refinements to pan and zoom

This commit is contained in:
Lentil Hoffman 2025-12-26 19:03:17 -06:00
parent b76adc6941
commit c53a4e9393
Signed by: lentil
GPG Key ID: 0F5B99F3F4D0C087
6 changed files with 69 additions and 28 deletions

View File

@ -5,8 +5,8 @@ export const DISPLAY_CURRENT_SCALE = true;
export const DISPLAY_VELOCITY_VECTORS = true;
export const MASS_CREATION_RATE = 0.001;
export const POINTER_HISTORY_SIZE = 10;
export const VELOCITY_VECTOR_SCALE = 0.2;
export const POINTER_HISTORY_SIZE = 15;
export const VELOCITY_VECTOR_SCALE = 0.1;
export const VELOCITY_VECTOR_COLOR = 'rgb(150, 150, 150)'; // optionally set to 'object color'
export const VELOCITY_VECTOR_WIDTH = 1.5;
export const VELOCITY_VECTOR_ARROWHEAD = true;
@ -20,6 +20,8 @@ export const ZOOM_IN_FACTOR = 1;
export const ZOOM_OUT_FACTOR = -1;
export const SCALE_POWER_MAX = 8;
export const SCALE_POWER_MIN = -8;
export const PAN_VELOCITY_SCALE_FACTOR = 1E-3;
export const PAN_DRAG = 1;
export const DRAGGABLE_ELEMENT_CLASSNAME = 'lhg-draggable-element';

View File

@ -60,7 +60,6 @@ export class Objects {
}
}
handlePointerDown({x, y}) {
// If pointer is touching an object, select the object
const touchingObject = this.objectAtLocation(x, y);

View File

@ -6,6 +6,7 @@ import {
DRAGGABLE_ELEMENT_CLASSNAME,
MODE_MASS_GENERATION,
MODE_PAN_VIEW,
PAN_VELOCITY_SCALE_FACTOR,
} from './config.js';
function dispatchEvent(target, eventType, data) {
@ -19,6 +20,7 @@ export class Pointer {
pointerHistory = [];
draggingElement = undefined;
panning = undefined;
suppressClick = false;
constructor(sim) {
this.sim = sim;
@ -30,26 +32,26 @@ export class Pointer {
if (DISPLAY_CURSOR_INFO) {
this.sim.info['pointermove'] = [`${e.clientX}, `, `${e.clientY}`];
}
if (this.draggingElement) {
// e.preventDefault();
this.draggingElement.dragging.pointerEnd = {
x: e.clientX,
y: e.clientY,
};
} else {
const {x, y} = this.sim.screenToSim(e.clientX, e.clientY);
this.handlePointerMove({x, y});
// const {x, y} = this.sim.screenToSim(e.clientX, e.clientY);
this.handlePointerMove({x: e.clientX, y: e.clientY});
}
});
el.addEventListener('pointerdown', e => {
// e.preventDefault();
// 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)) {
// e.preventDefault();
this.draggingElement = target;
this.draggingElement.dragging = {
elementStart: {
@ -71,7 +73,7 @@ export class Pointer {
});
el.addEventListener('pointerup', e => {
// e.preventDefault();
this.clearPointerHistory();
if (this.draggingElement) {
this.draggingElement.dragging = undefined;
this.draggingElement = undefined;
@ -81,16 +83,9 @@ export class Pointer {
}
});
el.addEventListener('click', e => {
// e.preventDefault();
});
// Monitor wheel events
el.addEventListener('wheel', e => {
// e.preventDefault();
// Wheel scroll down => positive deltaY => ZOOM IN
const factor = e.deltaY > 0 ? ZOOM_IN_FACTOR : ZOOM_OUT_FACTOR;
this.sim.scheduleZoom({x: e.clientX, y: e.clientY}, factor);
});
@ -124,34 +119,73 @@ export class Pointer {
}
handlePointerDown({x, y}) {
this.clearPointerHistory();
this.updatePointer({x, y});
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
this.sim.objects.handlePointerDown({x, y});
} else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) {
this.panning = {
gathering: true,
viewOriginStart: this.sim.display.viewOrigin,
pointerStart: {x, y},
pointerCurrent: {x, y},
velocity: this.getPointerVelocity(),
};
}
}
handlePointerUp({x, y}) {
this.panning = undefined;
this.clearPointerHistory();
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
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, y}) {
this.updatePointer({x, y});
const {x: vx, y: vy} = this.getPointerVelocity();
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 });
this.panning.pointerCurrent = {x: clientX, y: clientY};
this.panning.velocity = this.getPointerVelocity();
}
}
}
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.sim.isCurrentMode(MODE_PAN_VIEW)) {
this.panning = undefined;
return;
}
if (this.panning) {
const {pointerStart, pointerCurrent, viewOriginStart, velocity} = this.sim.pointer.panning;
// Convert pointer velocity to sim internal scale
velocity.x /= this.sim.display.scale;
velocity.y /= this.sim.display.scale;
this.panning.viewOriginStart = this.sim.display.viewOrigin;
this.panning.pointerStart = pointerCurrent;
// const speed = Math.sqrt(velocity.x ** 2 + velocity.y ** 2);
this.sim.display.viewOrigin.x -= velocity.x * elapsedTime * PAN_VELOCITY_SCALE_FACTOR;
this.sim.display.viewOrigin.y -= velocity.y * elapsedTime * PAN_VELOCITY_SCALE_FACTOR;
}
}
}

View File

@ -63,6 +63,12 @@ export class Sim {
// compute coordinates of new view frame
this.display.viewOrigin.x = x - this.display.width / 2;
this.display.viewOrigin.y = y - this.display.height / 2;
this.pointer.clearPointerHistory();
if (this.pointer.panning) {
this.pointer.panning = undefined;
// TODO: Maybe rescale velocity
}
}
// Transform display coordinates to simulator coordinates using scale and viewOrigin
@ -95,6 +101,7 @@ export class Sim {
this.info['Scale'] = this.display.scalePower >= 0 ? `${scale}` : `1/${scale}`;
}
this.pointer.computeFrame(elapsedTime);
this.objects.computeFrame(elapsedTime);
this.overlay.updateDraggable();

View File

@ -59,7 +59,8 @@ export class ModeSwitch extends Tool {
for (let button of this.buttons) {
button.style.opacity = button.modeID === this.currentMode ? '50%' : '100%';
}
}
// TODO: on enter / on leave mode / some sort of callbacks on mode transitions
}

View File

@ -28,7 +28,6 @@ export class Zoom extends Tool {
// Aim at center of view
const x = this.sim.display.width * this.sim.display.scale / 2;
const y = this.sim.display.height * this.sim.display.scale / 2;
console.log(`zoom out, x`, x, 'y', y);
this.sim.scheduleZoom({x, y}, ZOOM_OUT_FACTOR);
});
@ -36,7 +35,6 @@ export class Zoom extends Tool {
// Aim at center of view
const x = this.sim.display.width * this.sim.display.scale / 2;
const y = this.sim.display.height * this.sim.display.scale / 2;
console.log(`zoom in, x`, x, 'y', y);
this.sim.scheduleZoom({x, y}, ZOOM_IN_FACTOR);
});
}