161 lines
3.9 KiB
JavaScript
161 lines
3.9 KiB
JavaScript
import {
|
|
EVENT_ZOOM,
|
|
FRAMERATE_SAMPLE_DURATION,
|
|
} from './config.js';
|
|
import {Display} from './display.js';
|
|
import {System} from './system.js';
|
|
import {Overlay} from './overlay.js';
|
|
import {Pointer} from './pointer.js';
|
|
import {Options} from './options.js';
|
|
import {Zoom} from './zoom.js';
|
|
import {Select} from './select.js';
|
|
import {simOptions} from './sim-options.js';
|
|
import {initializeTools} from './sim-tools.js';
|
|
|
|
export class Sim {
|
|
info = {};
|
|
rawTime = undefined;
|
|
time = undefined;
|
|
timeScale = undefined;
|
|
nextZoom = undefined;
|
|
playing = true;
|
|
recentFrames = [];
|
|
panning = undefined;
|
|
frameRate = 0;
|
|
|
|
system = undefined;
|
|
display = undefined;
|
|
overlay = undefined;
|
|
pointer = undefined;
|
|
zoom = undefined;
|
|
select = undefined;
|
|
|
|
toolbarGroups = {};
|
|
toolbars = {};
|
|
|
|
isCurrentMode = () => undefined;
|
|
getCurrentMode = () => undefined;
|
|
setCurrentMode = () => undefined;
|
|
getOption = () => undefined;
|
|
setOption = () => undefined;
|
|
onModeEnter = () => undefined;
|
|
onModeLeave = () => undefined;
|
|
|
|
constructor(divId) {
|
|
this.divId = divId;
|
|
const div = document.getElementById(this.divId);
|
|
this.div = div;
|
|
|
|
this.options = new Options(this, simOptions);
|
|
this.display = new Display(this);
|
|
this.system = new System(this);
|
|
this.overlay = new Overlay(this);
|
|
this.pointer = new Pointer(this);
|
|
this.zoom = new Zoom(this);
|
|
this.select = new Select(this);
|
|
|
|
initializeTools(this);
|
|
|
|
// Initiate main loop
|
|
this.rawTime = document.timeline.currentTime;
|
|
this.time = 0;
|
|
requestAnimationFrame(t => this.frame(t));
|
|
}
|
|
|
|
// Main loop
|
|
frame(currentTime) {
|
|
const early = this.markFrame(currentTime);
|
|
if (early) {
|
|
// Slow down :)
|
|
requestAnimationFrame(t => this.frame(t));
|
|
return;
|
|
}
|
|
this.timeScale = this.getOption('param.timeScale');
|
|
const elapsedTime = (currentTime - this.rawTime) * this.timeScale;
|
|
this.rawTime = currentTime;
|
|
if (this.playing) {
|
|
this.time += elapsedTime;
|
|
}
|
|
if (this.getOption('debug.currentMode')) {
|
|
this.info['Mode'] = this.getCurrentMode();
|
|
}
|
|
if (this.getOption('debug.frameRate')) {
|
|
this.info['Frame Rate'] = this.frameRate.toPrecision(3);
|
|
}
|
|
this.zoom.frame();
|
|
this.pointer.frame();
|
|
this.display.frame(elapsedTime);
|
|
this.select.frame();
|
|
this.system.frame(elapsedTime);
|
|
this.overlay.frame();
|
|
for (const group in this.toolbarGroups) {
|
|
this.toolbarGroups[group].frame();
|
|
}
|
|
requestAnimationFrame(t => this.frame(t));
|
|
}
|
|
|
|
markFrame(t) {
|
|
const {recentFrames: rfs} = this;
|
|
if (!rfs.length) {
|
|
rfs.push(t);
|
|
return;
|
|
}
|
|
let oldest = rfs[0];
|
|
let duration = t - oldest;
|
|
const count = rfs.length + 1;
|
|
const frameRate = 1000 * count / duration;
|
|
const targetFrameRate = this.getOption('display.targetFrameRate');
|
|
if (frameRate > targetFrameRate + 1) {
|
|
return true;
|
|
}
|
|
this.frameRate = frameRate;
|
|
rfs.push(t);
|
|
while (duration >= FRAMERATE_SAMPLE_DURATION) {
|
|
rfs.shift();
|
|
oldest = rfs[0];
|
|
duration = t - oldest;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// velocity should be in Sim coordinate scale
|
|
scheduleZoom({x, y}, factor, velocity) {
|
|
this.zoom.scheduleZoom({x, y}, factor, velocity);
|
|
}
|
|
|
|
// Transform display coordinates to simulator coordinates using scale and viewOrigin
|
|
screenToSim(x, y) {
|
|
return this.display.screenToSim(x, y);
|
|
}
|
|
|
|
play() {
|
|
this.playing = true;
|
|
|
|
if (this.panning?.paused) {
|
|
this.panning.paused = false;
|
|
}
|
|
}
|
|
|
|
pause() {
|
|
this.playing = false;
|
|
|
|
if (this.panning?.velocity) {
|
|
this.panning.paused = true;
|
|
}
|
|
}
|
|
|
|
getScaleDisplay() {
|
|
const scale = 2 ** Math.abs(this.display.scalePower);
|
|
const scaleText = this.display.scalePower >= 0 ? `${scale}` : `1/${scale}`;
|
|
return `${scaleText} (${this.display.scalePower})`;
|
|
}
|
|
|
|
// cb: () => undefined
|
|
onZoom(cb) {
|
|
this.div.addEventListener(EVENT_ZOOM, () => {
|
|
cb();
|
|
});
|
|
}
|
|
|
|
}
|