Compare commits
No commits in common. "4bb91d664d71a10c6df626d3383ba5d7160b77fe" and "02b3f9a913ff5ffdde505c29ad0e3f90a73023e7" have entirely different histories.
4bb91d664d
...
02b3f9a913
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1 @@
|
|||||||
*.sw[po]
|
*.swp
|
||||||
|
|||||||
24
config.js
24
config.js
@ -1,13 +1,9 @@
|
|||||||
|
export const MASS_CREATION_RATE = 0.001;
|
||||||
export const DISPLAY_OBJECTS_INFO = false;
|
export const DISPLAY_OBJECTS_INFO = false;
|
||||||
export const DISPLAY_CURSOR_INFO = false;
|
export const DISPLAY_CURSOR_INFO = false;
|
||||||
export const DISPLAY_CANVAS_SIZE = false;
|
|
||||||
export const DISPLAY_CURRENT_SCALE = true;
|
|
||||||
export const DISPLAY_CURRENT_MODE = false;
|
|
||||||
export const DISPLAY_VELOCITY_VECTORS = true;
|
export const DISPLAY_VELOCITY_VECTORS = true;
|
||||||
|
export const POINTER_HISTORY_SIZE = 20;
|
||||||
export const MASS_CREATION_RATE = 0.001;
|
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_COLOR = 'rgb(150, 150, 150)'; // optionally set to 'object color'
|
||||||
export const VELOCITY_VECTOR_WIDTH = 1.5;
|
export const VELOCITY_VECTOR_WIDTH = 1.5;
|
||||||
export const VELOCITY_VECTOR_ARROWHEAD = true;
|
export const VELOCITY_VECTOR_ARROWHEAD = true;
|
||||||
@ -17,14 +13,8 @@ export const MOTION_TIME_SCALE = 0.001;
|
|||||||
export const OFFSCREEN_OBJECT_LINE_SCALE = 7;
|
export const OFFSCREEN_OBJECT_LINE_SCALE = 7;
|
||||||
export const OFFSCREEN_OBJECT_LINE_WIDTH = 2;
|
export const OFFSCREEN_OBJECT_LINE_WIDTH = 2;
|
||||||
export const OFFSCREEN_OBJECT_ARROWHEAD_LENGTH = 15;
|
export const OFFSCREEN_OBJECT_ARROWHEAD_LENGTH = 15;
|
||||||
export const ZOOM_IN_FACTOR = 1;
|
export const ZOOM_IN_FACTOR = 2;
|
||||||
export const ZOOM_OUT_FACTOR = -1;
|
export const ZOOM_OUT_FACTOR = 0.5;
|
||||||
export const SCALE_POWER_MAX = 8;
|
export const SCALE_MAX = 256;
|
||||||
export const SCALE_POWER_MIN = -8;
|
export const SCALE_MIN = 1/256;
|
||||||
export const PAN_VELOCITY_SCALE_FACTOR = 1E-3;
|
|
||||||
export const PAN_DRAG = 1;
|
|
||||||
|
|
||||||
export const DRAGGABLE_ELEMENT_CLASSNAME = 'lhg-draggable-element';
|
export const DRAGGABLE_ELEMENT_CLASSNAME = 'lhg-draggable-element';
|
||||||
|
|
||||||
export const MODE_MASS_GENERATION = 'mass-gen';
|
|
||||||
export const MODE_PAN_VIEW = 'pan-view';
|
|
||||||
|
|||||||
11
display.js
11
display.js
@ -8,12 +8,11 @@ import {
|
|||||||
OFFSCREEN_OBJECT_LINE_SCALE,
|
OFFSCREEN_OBJECT_LINE_SCALE,
|
||||||
OFFSCREEN_OBJECT_LINE_WIDTH,
|
OFFSCREEN_OBJECT_LINE_WIDTH,
|
||||||
OFFSCREEN_OBJECT_ARROWHEAD_LENGTH,
|
OFFSCREEN_OBJECT_ARROWHEAD_LENGTH,
|
||||||
DISPLAY_CANVAS_SIZE,
|
|
||||||
} from './config.js';
|
} from './config.js';
|
||||||
|
|
||||||
export class Display {
|
export class Display {
|
||||||
sim = undefined;
|
sim = undefined;
|
||||||
scalePower = 0;
|
scale = 1;
|
||||||
viewOrigin = {x: 0, y: 0};
|
viewOrigin = {x: 0, y: 0};
|
||||||
|
|
||||||
constructor(sim) {
|
constructor(sim) {
|
||||||
@ -30,10 +29,6 @@ export class Display {
|
|||||||
window.addEventListener('resize', () => this.fullscreen());
|
window.addEventListener('resize', () => this.fullscreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
get scale() {
|
|
||||||
return 2 ** this.scalePower;
|
|
||||||
}
|
|
||||||
|
|
||||||
get ctx() {
|
get ctx() {
|
||||||
const ctx = this.canvas.getContext("2d");
|
const ctx = this.canvas.getContext("2d");
|
||||||
ctx.resetTransform();
|
ctx.resetTransform();
|
||||||
@ -60,9 +55,7 @@ export class Display {
|
|||||||
fullscreen() {
|
fullscreen() {
|
||||||
this.canvas.width = document.documentElement.clientWidth;
|
this.canvas.width = document.documentElement.clientWidth;
|
||||||
this.canvas.height = document.documentElement.clientHeight;
|
this.canvas.height = document.documentElement.clientHeight;
|
||||||
if (DISPLAY_CANVAS_SIZE) {
|
// this.info['Canvas'] = `${this.canvas.width} x ${this.canvas.height}`;
|
||||||
this.sim.info['Canvas'] = `${this.canvas.width} x ${this.canvas.height}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fillCanvas() {
|
fillCanvas() {
|
||||||
|
|||||||
11
index.html
11
index.html
@ -20,17 +20,6 @@ div[id=simulator] {
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
|
||||||
width: 8em;
|
|
||||||
padding-left: 0.5em;
|
|
||||||
padding-left: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.wide {
|
|
||||||
width: 16em;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { Sim } from './simulator.js';
|
import { Sim } from './simulator.js';
|
||||||
|
|||||||
13
objects.js
13
objects.js
@ -60,6 +60,7 @@ export class Objects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
handlePointerDown({x, y}) {
|
handlePointerDown({x, y}) {
|
||||||
// If pointer is touching an object, select the object
|
// If pointer is touching an object, select the object
|
||||||
const touchingObject = this.objectAtLocation(x, y);
|
const touchingObject = this.objectAtLocation(x, y);
|
||||||
@ -77,18 +78,6 @@ export class Objects {
|
|||||||
this.deselect();
|
this.deselect();
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePointerMove({x, y, vx, vy}) {
|
|
||||||
// If the cursor moves while creating an object, or while an object is selected,
|
|
||||||
// update the position and velocity of the object
|
|
||||||
const obj = this.sim.objects.getSelectedOrCreating();
|
|
||||||
if (obj !== undefined) {
|
|
||||||
obj.position.x = x;
|
|
||||||
obj.position.y = y;
|
|
||||||
obj.velocity.x = vx;
|
|
||||||
obj.velocity.y = vy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
computeFrame(elapsedTime) {
|
computeFrame(elapsedTime) {
|
||||||
// If we're creating an object, increment its mass
|
// If we're creating an object, increment its mass
|
||||||
// with the mass creation rate accelerating over time
|
// with the mass creation rate accelerating over time
|
||||||
|
|||||||
101
pointer.js
101
pointer.js
@ -4,9 +4,6 @@ import {
|
|||||||
ZOOM_OUT_FACTOR,
|
ZOOM_OUT_FACTOR,
|
||||||
DISPLAY_CURSOR_INFO,
|
DISPLAY_CURSOR_INFO,
|
||||||
DRAGGABLE_ELEMENT_CLASSNAME,
|
DRAGGABLE_ELEMENT_CLASSNAME,
|
||||||
MODE_MASS_GENERATION,
|
|
||||||
MODE_PAN_VIEW,
|
|
||||||
PAN_VELOCITY_SCALE_FACTOR,
|
|
||||||
} from './config.js';
|
} from './config.js';
|
||||||
|
|
||||||
function dispatchEvent(target, eventType, data) {
|
function dispatchEvent(target, eventType, data) {
|
||||||
@ -19,8 +16,6 @@ export class Pointer {
|
|||||||
|
|
||||||
pointerHistory = [];
|
pointerHistory = [];
|
||||||
draggingElement = undefined;
|
draggingElement = undefined;
|
||||||
panning = undefined;
|
|
||||||
suppressClick = false;
|
|
||||||
|
|
||||||
constructor(sim) {
|
constructor(sim) {
|
||||||
this.sim = sim;
|
this.sim = sim;
|
||||||
@ -32,26 +27,26 @@ export class Pointer {
|
|||||||
if (DISPLAY_CURSOR_INFO) {
|
if (DISPLAY_CURSOR_INFO) {
|
||||||
this.sim.info['pointermove'] = [`${e.clientX}, `, `${e.clientY}`];
|
this.sim.info['pointermove'] = [`${e.clientX}, `, `${e.clientY}`];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.draggingElement) {
|
if (this.draggingElement) {
|
||||||
|
// e.preventDefault();
|
||||||
this.draggingElement.dragging.pointerEnd = {
|
this.draggingElement.dragging.pointerEnd = {
|
||||||
x: e.clientX,
|
x: e.clientX,
|
||||||
y: e.clientY,
|
y: e.clientY,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// const {x, y} = this.sim.screenToSim(e.clientX, e.clientY);
|
const {x, y} = this.sim.screenToSim(e.clientX, e.clientY);
|
||||||
this.handlePointerMove({x: e.clientX, y: e.clientY});
|
this.handlePointerMove({x, y});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
el.addEventListener('pointerdown', e => {
|
el.addEventListener('pointerdown', e => {
|
||||||
// If this is a child of a draggable element, handle dragging
|
// e.preventDefault();
|
||||||
let target = e.target;
|
let target = e.target;
|
||||||
while (target && !target.classList.contains(DRAGGABLE_ELEMENT_CLASSNAME)) {
|
while (target && !target.classList.contains(DRAGGABLE_ELEMENT_CLASSNAME)) {
|
||||||
target = target.parentElement;
|
target = target.parentElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target?.classList.contains(DRAGGABLE_ELEMENT_CLASSNAME)) {
|
if (target?.classList.contains(DRAGGABLE_ELEMENT_CLASSNAME)) {
|
||||||
|
// e.preventDefault();
|
||||||
this.draggingElement = target;
|
this.draggingElement = target;
|
||||||
this.draggingElement.dragging = {
|
this.draggingElement.dragging = {
|
||||||
elementStart: {
|
elementStart: {
|
||||||
@ -73,7 +68,7 @@ export class Pointer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
el.addEventListener('pointerup', e => {
|
el.addEventListener('pointerup', e => {
|
||||||
this.clearPointerHistory();
|
// e.preventDefault();
|
||||||
if (this.draggingElement) {
|
if (this.draggingElement) {
|
||||||
this.draggingElement.dragging = undefined;
|
this.draggingElement.dragging = undefined;
|
||||||
this.draggingElement = undefined;
|
this.draggingElement = undefined;
|
||||||
@ -83,10 +78,16 @@ export class Pointer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
el.addEventListener('click', e => {
|
||||||
|
// e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
// Monitor wheel events
|
// Monitor wheel events
|
||||||
el.addEventListener('wheel', e => {
|
el.addEventListener('wheel', e => {
|
||||||
|
// e.preventDefault();
|
||||||
|
// Wheel scroll down => positive deltaY => ZOOM IN
|
||||||
const factor = e.deltaY > 0 ? ZOOM_IN_FACTOR : ZOOM_OUT_FACTOR;
|
const factor = e.deltaY > 0 ? ZOOM_IN_FACTOR : ZOOM_OUT_FACTOR;
|
||||||
this.sim.scheduleZoom({x: e.clientX, y: e.clientY}, factor);
|
this.sim.scheduleZoom(this.sim.screenToSim(e.clientX, e.clientY), factor);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -119,73 +120,31 @@ export class Pointer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handlePointerDown({x, y}) {
|
handlePointerDown({x, y}) {
|
||||||
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
|
this.clearPointerHistory();
|
||||||
this.sim.objects.handlePointerDown({x, y});
|
this.updatePointer({x, y});
|
||||||
} else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) {
|
|
||||||
this.panning = {
|
this.sim.objects.handlePointerDown({x, y});
|
||||||
gathering: true,
|
|
||||||
viewOriginStart: this.sim.display.viewOrigin,
|
|
||||||
pointerStart: {x, y},
|
|
||||||
pointerCurrent: {x, y},
|
|
||||||
velocity: this.getPointerVelocity(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePointerUp({x, y}) {
|
handlePointerUp({x, y}) {
|
||||||
this.clearPointerHistory();
|
// TODO: Conditional?
|
||||||
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
|
this.sim.objects.handlePointerUp({x, y});
|
||||||
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
|
// Handle cursor (mouse or touch) movement
|
||||||
// TODO: If e.touches.length > 1, user may be engaging pinch to zoom
|
// TODO: If e.touches.length > 1, user may be engaging pinch to zoom
|
||||||
handlePointerMove({x: clientX, y: clientY}) {
|
handlePointerMove({x, y}) {
|
||||||
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
|
this.updatePointer({x, y});
|
||||||
this.updatePointer({ x: clientX, y: clientY });
|
const {x: vx, y: vy} = this.getPointerVelocity();
|
||||||
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 the cursor moves while creating an object, or while an object is selected,
|
||||||
if (this.panning?.gathering) {
|
// update the position and velocity of the object
|
||||||
this.updatePointer({ x: clientX, y: clientY });
|
const obj = this.sim.objects.getSelectedOrCreating();
|
||||||
this.panning.pointerCurrent = {x: clientX, y: clientY};
|
if (obj !== undefined) {
|
||||||
this.panning.velocity = this.getPointerVelocity();
|
obj.position.x = x;
|
||||||
}
|
obj.position.y = y;
|
||||||
}
|
obj.velocity.x = vx;
|
||||||
}
|
obj.velocity.y = vy;
|
||||||
|
|
||||||
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
|
|
||||||
const vx = velocity.x / this.sim.display.scale;
|
|
||||||
const vy = 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 -= vx * elapsedTime * PAN_VELOCITY_SCALE_FACTOR;
|
|
||||||
this.sim.display.viewOrigin.y -= vy * elapsedTime * PAN_VELOCITY_SCALE_FACTOR;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
54
simulator.js
54
simulator.js
@ -3,15 +3,8 @@ import { Overlay } from './overlay.js';
|
|||||||
import { Pointer } from './pointer.js';
|
import { Pointer } from './pointer.js';
|
||||||
import { Objects } from './objects.js';
|
import { Objects } from './objects.js';
|
||||||
import { Toolbar } from './toolbar.js';
|
import { Toolbar } from './toolbar.js';
|
||||||
import { PlayPause } from './tool/play-pause.js';
|
import { PlayPause } from './tools/play-pause.js';
|
||||||
import { Zoom } from './tool/zoom.js';
|
import { SCALE_MAX, SCALE_MIN} from './config.js';
|
||||||
import { ModeSwitch } from './tool/modes.js';
|
|
||||||
import {
|
|
||||||
SCALE_POWER_MAX,
|
|
||||||
SCALE_POWER_MIN,
|
|
||||||
DISPLAY_CURRENT_SCALE,
|
|
||||||
DISPLAY_CURRENT_MODE,
|
|
||||||
} from './config.js';
|
|
||||||
|
|
||||||
export class Sim {
|
export class Sim {
|
||||||
info = {};
|
info = {};
|
||||||
@ -24,8 +17,6 @@ export class Sim {
|
|||||||
pointer = undefined;
|
pointer = undefined;
|
||||||
objects = undefined;
|
objects = undefined;
|
||||||
|
|
||||||
isCurrentMode = () => false;
|
|
||||||
|
|
||||||
init(divId) {
|
init(divId) {
|
||||||
this.divId = divId;
|
this.divId = divId;
|
||||||
const div = document.getElementById(this.divId);
|
const div = document.getElementById(this.divId);
|
||||||
@ -33,43 +24,32 @@ export class Sim {
|
|||||||
|
|
||||||
this.display = new Display(this);
|
this.display = new Display(this);
|
||||||
this.overlay = new Overlay(this);
|
this.overlay = new Overlay(this);
|
||||||
this.objects = new Objects(this);
|
|
||||||
this.pointer = new Pointer(this);
|
this.pointer = new Pointer(this);
|
||||||
|
this.objects = new Objects(this);
|
||||||
this.toolbar = new Toolbar(this);
|
this.toolbar = new Toolbar(this);
|
||||||
|
|
||||||
// Set up toolbar
|
// Set up toolbar
|
||||||
this.toolbar.addTool(new Zoom(this.toolbar));
|
|
||||||
this.toolbar.addTool(new PlayPause(this.toolbar));
|
this.toolbar.addTool(new PlayPause(this.toolbar));
|
||||||
this.toolbar.addTool(new ModeSwitch(this.toolbar));
|
|
||||||
|
|
||||||
// Initiate main loop
|
// Initiate main loop
|
||||||
this.time = document.timeline.currentTime;
|
this.time = document.timeline.currentTime;
|
||||||
requestAnimationFrame(t => this.loop(t));
|
requestAnimationFrame(t => this.loop(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's better not to change the scale in the middle of possible frame calculations,
|
|
||||||
// so use this to schedule it and let the event loop pick it up.
|
|
||||||
scheduleZoom({x, y}, factor) {
|
scheduleZoom({x, y}, factor) {
|
||||||
this.nextZoom = {x, y, factor};
|
this.nextZoom = {x, y, factor};
|
||||||
}
|
}
|
||||||
|
|
||||||
zoom({x: screenX, y: screenY, factor}) {
|
zoom({x, y, factor}) {
|
||||||
const {x, y} = this.screenToSim(screenX, screenY);
|
|
||||||
// x, y are the mouse coordinates, which should be the center of the new view frame
|
// x, y are the mouse coordinates, which should be the center of the new view frame
|
||||||
// the new view origin should be x, y minus half the new view width and height
|
// the new view origin should be x, y minus half the new view width and height
|
||||||
// compute new scale
|
// compute new scale
|
||||||
this.display.scalePower += factor > 0 ? 1 : -1;
|
this.display.scale = this.display.scale * factor;
|
||||||
if (this.display.scalePower > SCALE_POWER_MAX) this.display.scalePower = SCALE_POWER_MAX;
|
if (this.display.scale > SCALE_MAX) this.display.scale = SCALE_MAX;
|
||||||
if (this.display.scalePower < SCALE_POWER_MIN) this.display.scalePower = SCALE_POWER_MIN;
|
if (this.display.scale < SCALE_MIN) this.display.scale = SCALE_MIN;
|
||||||
// compute coordinates of new view frame
|
// compute coordinates of new view frame
|
||||||
this.display.viewOrigin.x = x - this.display.width / 2;
|
this.display.viewOrigin.x = x - this.display.width / 2;
|
||||||
this.display.viewOrigin.y = y - this.display.height / 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
|
// Transform display coordinates to simulator coordinates using scale and viewOrigin
|
||||||
@ -89,30 +69,16 @@ export class Sim {
|
|||||||
loop(currentTime) {
|
loop(currentTime) {
|
||||||
const elapsedTime = currentTime - this.time;
|
const elapsedTime = currentTime - this.time;
|
||||||
this.time = currentTime;
|
this.time = currentTime;
|
||||||
|
|
||||||
if (DISPLAY_CURRENT_MODE) {
|
|
||||||
this.info['Mode'] = this.getCurrentMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.nextZoom) {
|
if (this.nextZoom) {
|
||||||
this.zoom(this.nextZoom);
|
this.zoom(this.nextZoom);
|
||||||
this.nextZoom = undefined;
|
this.nextZoom = undefined;
|
||||||
}
|
}
|
||||||
|
this.info['scale'] = this.display.scale;
|
||||||
if (DISPLAY_CURRENT_SCALE) {
|
|
||||||
const scale = 2 ** Math.abs(this.display.scalePower);
|
|
||||||
this.info['Scale'] = this.display.scalePower >= 0 ? `${scale}` : `1/${scale}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pointer.computeFrame(elapsedTime);
|
|
||||||
this.objects.computeFrame(elapsedTime);
|
this.objects.computeFrame(elapsedTime);
|
||||||
|
|
||||||
this.overlay.updateDraggable();
|
|
||||||
this.overlay.renderInfo();
|
|
||||||
|
|
||||||
this.display.fillCanvas();
|
this.display.fillCanvas();
|
||||||
this.display.drawObjects();
|
this.display.drawObjects();
|
||||||
|
this.overlay.updateDraggable();
|
||||||
|
this.overlay.renderInfo();
|
||||||
requestAnimationFrame(t => this.loop(t));
|
requestAnimationFrame(t => this.loop(t));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
tool.js
4
tool.js
@ -17,8 +17,8 @@ export class Tool {
|
|||||||
div.style.top = 0;
|
div.style.top = 0;
|
||||||
div.style.left = 0;
|
div.style.left = 0;
|
||||||
div.style.border = '1px #0fb solid';
|
div.style.border = '1px #0fb solid';
|
||||||
div.style.margin = '25px';
|
div.style.margin = '5px';
|
||||||
div.style.padding = '25px';
|
div.style.padding = '5px';
|
||||||
div.classList.add(DRAGGABLE_ELEMENT_CLASSNAME);
|
div.classList.add(DRAGGABLE_ELEMENT_CLASSNAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,66 +0,0 @@
|
|||||||
// Mode Switcher
|
|
||||||
import { Tool } from '../tool.js';
|
|
||||||
import {
|
|
||||||
MODE_MASS_GENERATION,
|
|
||||||
MODE_PAN_VIEW,
|
|
||||||
} from '../config.js';
|
|
||||||
|
|
||||||
export class ModeSwitch extends Tool {
|
|
||||||
currentMode = undefined;
|
|
||||||
modes = [
|
|
||||||
[MODE_MASS_GENERATION, 'Generate Mass'],
|
|
||||||
[MODE_PAN_VIEW, 'Pan View'],
|
|
||||||
];
|
|
||||||
buttons = [];
|
|
||||||
|
|
||||||
constructor(toolbar) {
|
|
||||||
super(toolbar);
|
|
||||||
|
|
||||||
const [[currentModeID, _]] = this.modes;
|
|
||||||
this.currentMode = currentModeID;
|
|
||||||
|
|
||||||
const modesDiv = document.createElement('div');
|
|
||||||
const titleDiv = document.createElement('div');
|
|
||||||
|
|
||||||
this.div.appendChild(titleDiv);
|
|
||||||
this.div.appendChild(modesDiv);
|
|
||||||
|
|
||||||
titleDiv.innerHTML = `<h2>Modes</h2>`;
|
|
||||||
|
|
||||||
// Since we have a heading, we need to reduce top padding
|
|
||||||
this.div.style.paddingTop = '0px';
|
|
||||||
|
|
||||||
modesDiv.style.display = 'flex';
|
|
||||||
modesDiv.style.flexDirection = 'column';
|
|
||||||
|
|
||||||
for (let [modeID, modeTitle] of this.modes) {
|
|
||||||
const button = document.createElement('button');
|
|
||||||
this.buttons.push(button);
|
|
||||||
button.modeID = modeID;
|
|
||||||
modesDiv.appendChild(button);
|
|
||||||
button.innerHTML = `<h3>${modeTitle}</h3>`;
|
|
||||||
button.classList.add('wide');
|
|
||||||
|
|
||||||
button.addEventListener('click', (e) => {
|
|
||||||
if (this.currentMode !== modeID) {
|
|
||||||
this.currentMode = modeID;
|
|
||||||
this.setModesOpacity();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.setModesOpacity();
|
|
||||||
|
|
||||||
// Add global method to get current mode / check mode
|
|
||||||
this.sim.getCurrentMode = () => this.currentMode;
|
|
||||||
this.sim.isCurrentMode = (modeID) => modeID === this.currentMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
setModesOpacity() {
|
|
||||||
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
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
import { Tool } from '../tool.js';
|
|
||||||
|
|
||||||
export class PlayPause extends Tool {
|
|
||||||
playHTML = '<h2>Play</h2>';
|
|
||||||
pauseHTML = '<h2>Pause</h2>';
|
|
||||||
|
|
||||||
constructor(toolbar) {
|
|
||||||
super(toolbar);
|
|
||||||
|
|
||||||
const pauseButton = document.createElement('button');
|
|
||||||
const playButton = document.createElement('button');
|
|
||||||
|
|
||||||
this.div.appendChild(pauseButton);
|
|
||||||
this.div.appendChild(playButton);
|
|
||||||
|
|
||||||
pauseButton.innerHTML = this.pauseHTML;
|
|
||||||
playButton.innerHTML = this.playHTML;
|
|
||||||
|
|
||||||
pauseButton.style.opacity = this.sim.playing ? '100%' : '50%';
|
|
||||||
playButton.style.opacity = this.sim.playing ? '50%' : '100%';
|
|
||||||
|
|
||||||
pauseButton.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (this.playing) {
|
|
||||||
this.playing = false;
|
|
||||||
pauseButton.style.opacity = '50%';
|
|
||||||
playButton.style.opacity = '100%';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
playButton.addEventListener('click', (e) => {
|
|
||||||
if (!this.playing) {
|
|
||||||
this.playing = true;
|
|
||||||
pauseButton.style.opacity = '100%';
|
|
||||||
playButton.style.opacity = '50%';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get playing() {
|
|
||||||
return this.sim.playing;
|
|
||||||
}
|
|
||||||
|
|
||||||
set playing(playing) {
|
|
||||||
return this.sim.playing = playing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
41
tool/zoom.js
41
tool/zoom.js
@ -1,41 +0,0 @@
|
|||||||
import { Tool } from '../tool.js';
|
|
||||||
import {
|
|
||||||
ZOOM_IN_FACTOR,
|
|
||||||
ZOOM_OUT_FACTOR,
|
|
||||||
} from '../config.js';
|
|
||||||
|
|
||||||
export class Zoom extends Tool {
|
|
||||||
constructor(toolbar) {
|
|
||||||
super(toolbar);
|
|
||||||
|
|
||||||
const zoomOut = document.createElement('button');
|
|
||||||
const zoomIn = document.createElement('button');
|
|
||||||
|
|
||||||
for (let b of [zoomIn, zoomOut]) {
|
|
||||||
// b.style.width = '100px';
|
|
||||||
// b.style.height = '50px';
|
|
||||||
// b.style['padding-left'] = '25px';
|
|
||||||
// b.style['padding-right'] = '25px';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.div.appendChild(zoomOut);
|
|
||||||
this.div.appendChild(zoomIn);
|
|
||||||
|
|
||||||
zoomOut.innerHTML = '<h2>Zoom<br>-<br>Out</h2>';
|
|
||||||
zoomIn.innerHTML = '<h2>Zoom<br>+<br>In</h2>';
|
|
||||||
|
|
||||||
zoomOut.addEventListener('click', (e) => {
|
|
||||||
// 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({x, y}, ZOOM_OUT_FACTOR);
|
|
||||||
});
|
|
||||||
|
|
||||||
zoomIn.addEventListener('click', (e) => {
|
|
||||||
// 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({x, y}, ZOOM_IN_FACTOR);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
40
tools/play-pause.js
Normal file
40
tools/play-pause.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Tool } from '../tool.js';
|
||||||
|
|
||||||
|
export class PlayPause extends Tool {
|
||||||
|
playHTML = 'Play';
|
||||||
|
pauseHTML = 'Pause';
|
||||||
|
|
||||||
|
constructor(toolbar) {
|
||||||
|
super(toolbar);
|
||||||
|
|
||||||
|
// For now, use a regular button
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.style.width = '50px';
|
||||||
|
button.style.height = '50px';
|
||||||
|
this.div.appendChild(button);
|
||||||
|
if (this.playing) {
|
||||||
|
button.innerHTML = this.pauseHTML;
|
||||||
|
} else {
|
||||||
|
button.innerHTML = this.playHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (this.playing) {
|
||||||
|
button.innerHTML = this.playHTML;
|
||||||
|
this.playing = false;
|
||||||
|
} else {
|
||||||
|
button.innerHTML = this.pauseHTML;
|
||||||
|
this.playing = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get playing() {
|
||||||
|
return this.sim.playing;
|
||||||
|
}
|
||||||
|
|
||||||
|
set playing(playing) {
|
||||||
|
return this.sim.playing = playing;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user