gravity/tool/zoom.js
2026-01-03 18:40:04 -06:00

104 lines
3.3 KiB
JavaScript

import {
TOOL_INFO_CLASSNAME,
WIDE_CLASSNAME,
ZOOM_IN_FACTOR,
ZOOM_OUT_FACTOR,
} from '../config.js';
import {Tool} from '../tool.js';
import {zero} from '../vector.js';
export class Zoom extends Tool {
currentScaleEl = undefined;
get displayScaleText() {
return `Scale: ${this.sim.getScaleDisplay()}`;
}
setContainer(container) {
super.setContainer(container);
this.currentScaleEl.innerHTML = this.displayScaleText;
this.sim.onZoom(() => {
this.currentScaleEl.innerHTML = this.displayScaleText;
});
}
constructor() {
super();
const currentScale = document.createElement('button')
const zoomOut = document.createElement('button');
const zoomIn = document.createElement('button');
const zoomAll = document.createElement('button');
const zeroVelocity = document.createElement('button');
this.currentScaleEl = currentScale;
this.div.appendChild(currentScale);
this.div.appendChild(zoomOut);
this.div.appendChild(zoomIn);
this.div.appendChild(zoomAll);
this.div.appendChild(zeroVelocity);
currentScale.classList.add(WIDE_CLASSNAME);
currentScale.classList.add(TOOL_INFO_CLASSNAME);
zoomOut.innerHTML = 'Zoom<br>Out';
zoomIn.innerHTML = 'Zoom<br>In';
zoomAll.innerHTML = 'Zoom to Fit';
zeroVelocity.innerHTML = 'Zero Momentum';
zoomOut.addEventListener('click', () => {
// 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;
this.sim.scheduleZoom(this.sim.screenToSim(x, y), ZOOM_OUT_FACTOR);
});
zoomIn.addEventListener('click', () => {
// 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;
this.sim.scheduleZoom(this.sim.screenToSim(x, y), ZOOM_IN_FACTOR);
});
zoomAll.addEventListener('click', () => {
// Determine bounding box
const box = this.sim.system.boundingBox;
const x = (box.start.x + box.end.x) / 2;
const y = (box.start.y + box.end.y) / 2;
const widthRatio = Math.abs(box.start.x - box.end.x) / this.sim.display.width;
const heightRatio = Math.abs(box.start.y - box.end.y) / this.sim.display.height;
const ratio = Math.max(widthRatio, heightRatio) * 4;
const factor = Math.ceil(Math.log2(1 / ratio));
// Determine average momentum and set panning velocity to match
const {netMomentum, totalMass} = this.sim.system.computeSystemCenter();
const netVelocity = {
x: netMomentum.x / totalMass,
y: netMomentum.y / totalMass,
};
this.sim.scheduleZoom({x, y}, factor, netVelocity)
});
zeroVelocity.addEventListener('click', () => {
// Determine center of mass and average momentum
const {totalMass, netMomentum} = this.sim.system.computeSystemCenter();
const netVelocity = {
x: netMomentum.x / totalMass,
y: netMomentum.y / totalMass,
};
// Apply offset to all object velocities
this.sim.system.forEachObject(obj => {
obj.velocity.x -= netVelocity.x;
obj.velocity.y -= netVelocity.y;
});
// Cancel panning
this.sim.panning.setVelocity(zero);
});
}
}