Compare commits
No commits in common. "2dc55377d38168b170d33a3d9e76b56637cb676a" and "2fb8a8fb7150bbccb9fb4167bde53f2582553e97" have entirely different histories.
2dc55377d3
...
2fb8a8fb71
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1 @@
|
||||
*.sw[po]
|
||||
*.swp
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
export const MASS_CREATION_RATE = 0.001;
|
||||
export const DISPLAY_OBJECTS_INFO = false;
|
||||
export const DISPLAY_CURSOR_INFO = false;
|
||||
export const DISPLAY_CANVAS_SIZE = true;
|
||||
|
||||
export const DISPLAY_VELOCITY_VECTORS = true;
|
||||
export const MASS_CREATION_RATE = 0.001;
|
||||
export const POINTER_HISTORY_SIZE = 10;
|
||||
export const POINTER_HISTORY_SIZE = 20;
|
||||
export const VELOCITY_VECTOR_SCALE = 0.2;
|
||||
export const VELOCITY_VECTOR_COLOR = 'rgb(150, 150, 150)'; // optionally set to 'object color'
|
||||
export const VELOCITY_VECTOR_WIDTH = 1.5;
|
||||
@ -20,6 +18,3 @@ export const ZOOM_OUT_FACTOR = 0.5;
|
||||
export const SCALE_MAX = 256;
|
||||
export const SCALE_MIN = 1/256;
|
||||
export const DRAGGABLE_ELEMENT_CLASSNAME = 'lhg-draggable-element';
|
||||
|
||||
export const MODE_MASS_GENERATION = 'mass-gen';
|
||||
export const MODE_PAN_VIEW = 'pan-view';
|
||||
|
||||
@ -8,7 +8,6 @@ import {
|
||||
OFFSCREEN_OBJECT_LINE_SCALE,
|
||||
OFFSCREEN_OBJECT_LINE_WIDTH,
|
||||
OFFSCREEN_OBJECT_ARROWHEAD_LENGTH,
|
||||
DISPLAY_CANVAS_SIZE,
|
||||
} from './config.js';
|
||||
|
||||
export class Display {
|
||||
@ -56,9 +55,7 @@ export class Display {
|
||||
fullscreen() {
|
||||
this.canvas.width = document.documentElement.clientWidth;
|
||||
this.canvas.height = document.documentElement.clientHeight;
|
||||
if (DISPLAY_CANVAS_SIZE) {
|
||||
this.sim.info['Canvas'] = `${this.canvas.width} x ${this.canvas.height}`;
|
||||
}
|
||||
// this.info['Canvas'] = `${this.canvas.width} x ${this.canvas.height}`;
|
||||
}
|
||||
|
||||
fillCanvas() {
|
||||
|
||||
11
index.html
11
index.html
@ -20,17 +20,6 @@ div[id=simulator] {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 8em;
|
||||
padding-left: 0.5em;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
button.wide {
|
||||
width: 16em;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script type="module">
|
||||
import { Sim } from './simulator.js';
|
||||
|
||||
12
objects.js
12
objects.js
@ -78,18 +78,6 @@ export class Objects {
|
||||
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) {
|
||||
// If we're creating an object, increment its mass
|
||||
// with the mass creation rate accelerating over time
|
||||
|
||||
19
pointer.js
19
pointer.js
@ -4,7 +4,6 @@ import {
|
||||
ZOOM_OUT_FACTOR,
|
||||
DISPLAY_CURSOR_INFO,
|
||||
DRAGGABLE_ELEMENT_CLASSNAME,
|
||||
MODE_MASS_GENERATION,
|
||||
} from './config.js';
|
||||
|
||||
function dispatchEvent(target, eventType, data) {
|
||||
@ -88,8 +87,7 @@ export class Pointer {
|
||||
// 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);
|
||||
this.sim.scheduleZoom(this.sim.screenToSim(e.clientX, e.clientY), factor);
|
||||
});
|
||||
|
||||
}
|
||||
@ -125,16 +123,13 @@ export class Pointer {
|
||||
this.clearPointerHistory();
|
||||
this.updatePointer({x, y});
|
||||
|
||||
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
|
||||
this.sim.objects.handlePointerDown({x, y});
|
||||
}
|
||||
}
|
||||
|
||||
handlePointerUp({x, y}) {
|
||||
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
|
||||
// TODO: Conditional?
|
||||
this.sim.objects.handlePointerUp({x, y});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle cursor (mouse or touch) movement
|
||||
// TODO: If e.touches.length > 1, user may be engaging pinch to zoom
|
||||
@ -142,8 +137,14 @@ export class Pointer {
|
||||
this.updatePointer({x, y});
|
||||
const {x: vx, y: vy} = this.getPointerVelocity();
|
||||
|
||||
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
|
||||
this.sim.objects.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
simulator.js
15
simulator.js
@ -3,9 +3,7 @@ import { Overlay } from './overlay.js';
|
||||
import { Pointer } from './pointer.js';
|
||||
import { Objects } from './objects.js';
|
||||
import { Toolbar } from './toolbar.js';
|
||||
import { PlayPause } from './tool/play-pause.js';
|
||||
import { Zoom } from './tool/zoom.js';
|
||||
import { ModeSwitch } from './tool/modes.js';
|
||||
import { PlayPause } from './tools/play-pause.js';
|
||||
import { SCALE_MAX, SCALE_MIN} from './config.js';
|
||||
|
||||
export class Sim {
|
||||
@ -19,8 +17,6 @@ export class Sim {
|
||||
pointer = undefined;
|
||||
objects = undefined;
|
||||
|
||||
isCurrentMode = () => false;
|
||||
|
||||
init(divId) {
|
||||
this.divId = divId;
|
||||
const div = document.getElementById(this.divId);
|
||||
@ -28,28 +24,23 @@ export class Sim {
|
||||
|
||||
this.display = new Display(this);
|
||||
this.overlay = new Overlay(this);
|
||||
this.objects = new Objects(this);
|
||||
this.pointer = new Pointer(this);
|
||||
this.objects = new Objects(this);
|
||||
this.toolbar = new Toolbar(this);
|
||||
|
||||
// Set up toolbar
|
||||
this.toolbar.addTool(new Zoom(this.toolbar));
|
||||
this.toolbar.addTool(new PlayPause(this.toolbar));
|
||||
this.toolbar.addTool(new ModeSwitch(this.toolbar));
|
||||
|
||||
// Initiate main loop
|
||||
this.time = document.timeline.currentTime;
|
||||
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) {
|
||||
this.nextZoom = {x, y, factor};
|
||||
}
|
||||
|
||||
zoom({x: screenX, y: screenY, factor}) {
|
||||
const {x, y} = this.screenToSim(screenX, screenY);
|
||||
zoom({x, y, factor}) {
|
||||
// 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
|
||||
// compute new scale
|
||||
|
||||
4
tool.js
4
tool.js
@ -17,8 +17,8 @@ export class Tool {
|
||||
div.style.top = 0;
|
||||
div.style.left = 0;
|
||||
div.style.border = '1px #0fb solid';
|
||||
div.style.margin = '25px';
|
||||
div.style.padding = '25px';
|
||||
div.style.margin = '5px';
|
||||
div.style.padding = '5px';
|
||||
div.classList.add(DRAGGABLE_ELEMENT_CLASSNAME);
|
||||
}
|
||||
|
||||
|
||||
@ -1,54 +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'],
|
||||
];
|
||||
|
||||
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');
|
||||
modesDiv.appendChild(button);
|
||||
button.innerHTML = `<h3>${modeTitle}</h3>`;
|
||||
button.classList.add('wide');
|
||||
button.style.opacity = modeID === currentModeID ? '50%' : '100%';
|
||||
|
||||
button.addEventListener('click', (e) => {
|
||||
if (this.currentMode !== modeID) {
|
||||
this.currentMode = modeID;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add global method to get current mode / check mode
|
||||
this.sim.getCurrentMode = () => this.currentMode;
|
||||
this.sim.isCurrentMode = (modeID) => modeID === this.currentMode;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
43
tool/zoom.js
43
tool/zoom.js
@ -1,43 +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;
|
||||
console.log(`zoom out, x`, x, 'y', y);
|
||||
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;
|
||||
console.log(`zoom in, x`, x, 'y', y);
|
||||
this.sim.scheduleZoom({x, y}, ZOOM_IN_FACTOR);
|
||||
});
|
||||
}
|
||||
}
|
||||
41
tools/play-pause.js
Normal file
41
tools/play-pause.js
Normal file
@ -0,0 +1,41 @@
|
||||
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 = '100px';
|
||||
button.style.height = '50px';
|
||||
button.style.margin = '25px';
|
||||
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