time scale
This commit is contained in:
parent
6640ba9b1d
commit
4cfdfc902b
@ -29,8 +29,8 @@ export const PATH_TRACES_DASHED_OPACITY = 1.0;
|
||||
|
||||
// SCALING FACTORS
|
||||
export const MASS_CREATION_RATE = 1E1;
|
||||
export const POINTER_HISTORY_SIZE = 10;
|
||||
export const MOTION_TIME_SCALE = 1E-4;
|
||||
export const POINTER_HISTORY_SIZE = 20;
|
||||
export const MOTION_TIME_SCALE = 1;
|
||||
export const PAN_ACCELERATION = 1E1;
|
||||
export const ARROWHEAD_LENGTH = 7;
|
||||
export const ARROWHEAD_WIDTH = 5;
|
||||
@ -42,7 +42,7 @@ 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 GRAVITATIONAL_CONSTANT = 1E5;
|
||||
export const GRAVITATIONAL_CONSTANT = 1E4;
|
||||
|
||||
// CSS CLASS NAMES
|
||||
export const TOOL_CLASSNAME = 'lhg-tool';
|
||||
|
||||
56
objects.js
56
objects.js
@ -1,6 +1,5 @@
|
||||
import { MassObject } from './object.js';
|
||||
import {
|
||||
MASS_CREATION_RATE,
|
||||
DISPLAY_OBJECTS_INFO,
|
||||
ZOOM_TO_FIT_PADDING,
|
||||
} from './config.js';
|
||||
@ -195,9 +194,11 @@ export class Objects {
|
||||
computeFrame(elapsedTime) {
|
||||
// If we're creating an object, increment its mass
|
||||
// with the mass creation rate accelerating over time
|
||||
const massCreationRate = this.sim.getOption('param.massCreationRate');
|
||||
if (this.creatingObject !== undefined) {
|
||||
const obj = this.objects[this.creatingObject];
|
||||
const rate = MASS_CREATION_RATE * obj.age;
|
||||
const rate = massCreationRate * obj.age;
|
||||
// TODO: After objects merge during creation, mass creation rate can accelerate
|
||||
obj.mass += rate * elapsedTime;
|
||||
}
|
||||
|
||||
@ -224,29 +225,42 @@ export class Objects {
|
||||
const dSquared = dx ** 2 + dy ** 2;
|
||||
const d = Math.sqrt(dSquared);
|
||||
if (d < A.radius + B.radius) {
|
||||
// Merge B into A:
|
||||
let S, T;
|
||||
// Merge the older into the newer, in order to provide mass creation rate continuity
|
||||
if (A.age > B.age) {
|
||||
// A merges into B; B survives
|
||||
S = B;
|
||||
T = A;
|
||||
// If A was selected or being created, select S instead
|
||||
if (this.creatingObject === i) this.creatingObject = j;
|
||||
if (this.selectedObject === i) this.selectedObject = j;
|
||||
} else {
|
||||
// B merges into A; A survives
|
||||
S = A;
|
||||
T = B;
|
||||
// If B was selected or being created, select S instead
|
||||
if (this.creatingObject === j) this.creatingObject = i;
|
||||
if (this.selectedObject === j) this.selectedObject = i;
|
||||
}
|
||||
// Merge T into S:
|
||||
// Set position = center of mass
|
||||
// Set velocity = total momentum / total mass
|
||||
// Combine forces
|
||||
// Add masses
|
||||
// Average color
|
||||
// If B was selected or being created, select A instead
|
||||
// Remove B using splice
|
||||
A.position.x = (A.position.x * A.mass + B.position.x * B.mass) / (A.mass + B.mass);
|
||||
A.position.y = (A.position.y * A.mass + B.position.y * B.mass) / (A.mass + B.mass);
|
||||
A.velocity.x = (A.velocity.x * A.mass + B.velocity.x * B.mass) / (A.mass + B.mass);
|
||||
A.velocity.y = (A.velocity.y * A.mass + B.velocity.y * B.mass) / (A.mass + B.mass);
|
||||
A.forces.push(...B.forces);
|
||||
A.mass += B.mass;
|
||||
A.color = {
|
||||
r: (A.mass * A.color.r + B.mass * B.color.r) / (A.mass + B.mass),
|
||||
g: (A.mass * A.color.g + B.mass * B.color.g) / (A.mass + B.mass),
|
||||
b: (A.mass * A.color.b + B.mass * B.color.b) / (A.mass + B.mass),
|
||||
// Sdd masses
|
||||
// Sverage color
|
||||
S.position.x = (S.position.x * S.mass + T.position.x * T.mass) / (S.mass + T.mass);
|
||||
S.position.y = (S.position.y * S.mass + T.position.y * T.mass) / (S.mass + T.mass);
|
||||
S.velocity.x = (S.velocity.x * S.mass + T.velocity.x * T.mass) / (S.mass + T.mass);
|
||||
S.velocity.y = (S.velocity.y * S.mass + T.velocity.y * T.mass) / (S.mass + T.mass);
|
||||
S.forces.push(...T.forces);
|
||||
S.mass += T.mass;
|
||||
S.color = {
|
||||
r: (S.mass * S.color.r + T.mass * T.color.r) / (S.mass + T.mass),
|
||||
g: (S.mass * S.color.g + T.mass * T.color.g) / (S.mass + T.mass),
|
||||
b: (S.mass * S.color.b + T.mass * T.color.b) / (S.mass + T.mass),
|
||||
};
|
||||
if (this.creatingObject === j) this.creatingObject = i;
|
||||
if (this.selectedObject === j) this.selectedObject = i;
|
||||
B.alive = false;
|
||||
B.forces = [];
|
||||
T.alive = false;
|
||||
T.forces = [];
|
||||
}
|
||||
}, true, i + 1);
|
||||
});
|
||||
|
||||
45
simulator.js
45
simulator.js
@ -1,23 +1,23 @@
|
||||
import { Display } from './display.js';
|
||||
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 { Options } from './tool/options.js';
|
||||
import {
|
||||
SCALE_POWER_MAX,
|
||||
SCALE_POWER_MIN,
|
||||
DISPLAY_CURRENT_SCALE,
|
||||
import {
|
||||
DISPLAY_CURRENT_MODE,
|
||||
MOTION_TIME_SCALE,
|
||||
DISPLAY_CURRENT_SCALE,
|
||||
EVENT_ZOOM,
|
||||
SCALE_POWER_MAX,
|
||||
SCALE_POWER_MIN,
|
||||
} from './config.js';
|
||||
import {Display} from './display.js';
|
||||
import {Objects} from './objects.js';
|
||||
import {Overlay} from './overlay.js';
|
||||
import {Pointer} from './pointer.js';
|
||||
import {ModeSwitch} from './tool/modes.js';
|
||||
import {Options} from './tool/options.js';
|
||||
import {PlayPause} from './tool/play-pause.js';
|
||||
import {Zoom} from './tool/zoom.js';
|
||||
import {Toolbar} from './toolbar.js';
|
||||
|
||||
export class Sim {
|
||||
info = {};
|
||||
rawTime = undefined;
|
||||
time = undefined;
|
||||
nextZoom = undefined;
|
||||
playing = true;
|
||||
@ -59,7 +59,8 @@ export class Sim {
|
||||
this.pointer = new Pointer(this);
|
||||
|
||||
// Initiate main loop
|
||||
this.time = document.timeline.currentTime;
|
||||
this.rawTime = document.timeline.currentTime / 1000;
|
||||
this.time = 0;
|
||||
requestAnimationFrame(t => this.loop(t));
|
||||
}
|
||||
|
||||
@ -87,7 +88,7 @@ export class Sim {
|
||||
this.pointer.clearPointerHistory();
|
||||
|
||||
if (this.playing && velocity) {
|
||||
this.pointer.panning = {
|
||||
this.pointer.panning = {
|
||||
velocity: {
|
||||
x: -velocity.x,
|
||||
y: -velocity.y,
|
||||
@ -103,7 +104,7 @@ export class Sim {
|
||||
screenToSim(x, y) {
|
||||
return this.display.screenToSim(x, y);
|
||||
}
|
||||
|
||||
|
||||
play() {
|
||||
this.playing = true;
|
||||
}
|
||||
@ -114,7 +115,7 @@ export class Sim {
|
||||
|
||||
getScaleDisplay() {
|
||||
const scale = 2 ** Math.abs(this.display.scalePower);
|
||||
return this.display.scalePower >= 0 ? `${scale}` : `1/${scale}`;
|
||||
return this.display.scalePower >= 0 ? `${scale}` : `1/${scale}`;
|
||||
}
|
||||
|
||||
// cb: () => undefined
|
||||
@ -126,8 +127,11 @@ export class Sim {
|
||||
|
||||
// Main loop
|
||||
loop(currentTime) {
|
||||
const elapsedTime = (currentTime - this.time) * MOTION_TIME_SCALE;
|
||||
this.time = currentTime;
|
||||
currentTime /= 1000;
|
||||
const timeScale = this.getOption('param.timeScale');
|
||||
const elapsedTime = (currentTime - this.rawTime) * timeScale;
|
||||
this.rawTime = currentTime;
|
||||
this.time += elapsedTime;
|
||||
|
||||
if (DISPLAY_CURRENT_MODE) {
|
||||
this.info['Mode'] = this.getCurrentMode();
|
||||
@ -147,6 +151,7 @@ export class Sim {
|
||||
this.overlay.renderInfo();
|
||||
this.display.fillCanvas();
|
||||
this.display.drawObjects();
|
||||
this.toolbar.frame();
|
||||
|
||||
requestAnimationFrame(t => this.loop(t));
|
||||
}
|
||||
|
||||
28
style.css
28
style.css
@ -34,7 +34,7 @@ div.lhg-toolbar {
|
||||
border-radius: 0.5EM;
|
||||
}
|
||||
|
||||
div.lhg-toolbar div.lhg-tool {
|
||||
div.lhg-tool {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -45,6 +45,12 @@ div.lhg-toolbar div.lhg-tool {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: #282;
|
||||
text-align: middle;
|
||||
}
|
||||
|
||||
div.lhg-tool div.lhg-wide {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
div.lhg-tool button, div.lhg-tool input {
|
||||
@ -55,7 +61,7 @@ div.lhg-tool button, div.lhg-tool input {
|
||||
color: #5f5;
|
||||
border-radius: 0.5EM;
|
||||
border-color: #000;
|
||||
border-width: 4px;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
padding-top: 0.5EM;
|
||||
padding-bottom: 0.5EM;
|
||||
@ -64,10 +70,11 @@ div.lhg-tool button, div.lhg-tool input {
|
||||
text-align: center;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div.lhg-tool input {
|
||||
width: 5EM;
|
||||
width: 6EM;
|
||||
}
|
||||
|
||||
div.lhg-tool button:hover {
|
||||
@ -92,22 +99,11 @@ div.lhg-toolbar-header > * {
|
||||
|
||||
div.lhg-tool .lhg-tool-info {
|
||||
background-color: #111;
|
||||
border-color: #282;
|
||||
border-width: 0px;
|
||||
border-color: #000;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
div.lhg-tool .lhg-wide {
|
||||
width: 12em;
|
||||
}
|
||||
|
||||
div.lhg-tool input.lhg-wide {
|
||||
width: 11em;
|
||||
}
|
||||
|
||||
div.lhg-tool .lhg-tool-info.lhg-wide {
|
||||
width: 11em;
|
||||
}
|
||||
|
||||
div.lhg-tool > div {
|
||||
/* border: 2px red solid; */
|
||||
}
|
||||
|
||||
@ -3,7 +3,9 @@ import {
|
||||
DISPLAY_ACCELERATION_VECTORS,
|
||||
DISPLAY_VELOCITY_VECTORS,
|
||||
GRAVITATIONAL_CONSTANT,
|
||||
MASS_CREATION_RATE,
|
||||
MERGE_ON_COLLIDE,
|
||||
MOTION_TIME_SCALE,
|
||||
PATH_TRACES_DASHED,
|
||||
PAUSE_DURING_CREATION,
|
||||
PAUSE_DURING_SELECTION,
|
||||
@ -36,6 +38,8 @@ export class Options extends Tool {
|
||||
type: 'group', name: 'param', title: 'Parameters',
|
||||
items: [
|
||||
{type: 'number', name: 'gravity', title: 'Gravity', default: GRAVITATIONAL_CONSTANT},
|
||||
{type: 'number', name: 'timeScale', title: 'Time Scale', default: MOTION_TIME_SCALE},
|
||||
{type: 'number', name: 'massCreationRate', title: 'Mass Creation Rate', default: MASS_CREATION_RATE},
|
||||
]
|
||||
}];
|
||||
|
||||
@ -79,7 +83,7 @@ export class Options extends Tool {
|
||||
div.classList.add(WIDE_CLASSNAME);
|
||||
title.classList.add(TOOL_INFO_CLASSNAME);
|
||||
if (item.wide) {
|
||||
// title.classList.add(WIDE_CLASSNAME);
|
||||
title.classList.add(WIDE_CLASSNAME);
|
||||
input.classList.add(WIDE_CLASSNAME);
|
||||
}
|
||||
title.innerHTML = item.title;
|
||||
|
||||
@ -1,20 +1,59 @@
|
||||
import { Tool } from '../tool.js';
|
||||
import {TOOL_INFO_CLASSNAME, WIDE_CLASSNAME} from '../config.js';
|
||||
import {Tool} from '../tool.js';
|
||||
|
||||
export class PlayPause extends Tool {
|
||||
playHTML = 'Play';
|
||||
pauseHTML = 'Pause';
|
||||
currentTimeEl = undefined;
|
||||
|
||||
get timeText() {
|
||||
let time = this.sim.time;
|
||||
// Time in seconds
|
||||
const s = time % 60;
|
||||
time = (time - s) / 60;
|
||||
const m = time % 60;
|
||||
time = (time - m) / 60;
|
||||
const h = time % 24;
|
||||
time = (time - h) / 24;
|
||||
const d = time;
|
||||
time -= m * 60;
|
||||
|
||||
const ms = (s - Math.floor(s)) * 1000;
|
||||
return [
|
||||
d || undefined,
|
||||
h.toString().padStart(2, '0'),
|
||||
m.toString().padStart(2, '0'),
|
||||
[
|
||||
s.toFixed(0).padStart(2, '0'),
|
||||
ms.toFixed(0).padStart(3, '0'),
|
||||
].join('.')
|
||||
].filter(x => x !== undefined).join(':');
|
||||
}
|
||||
|
||||
frame() {
|
||||
if (this.currentTimeEl) {
|
||||
this.currentTimeEl.innerHTML = this.timeText;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(toolbar) {
|
||||
super(toolbar);
|
||||
|
||||
const currentTime = document.createElement('button');
|
||||
const pauseButton = document.createElement('button');
|
||||
const playButton = document.createElement('button');
|
||||
this.currentTimeEl = currentTime;
|
||||
|
||||
this.div.appendChild(currentTime);
|
||||
this.div.appendChild(pauseButton);
|
||||
this.div.appendChild(playButton);
|
||||
|
||||
currentTime.classList.add(TOOL_INFO_CLASSNAME);
|
||||
currentTime.classList.add(WIDE_CLASSNAME);
|
||||
|
||||
pauseButton.innerHTML = this.pauseHTML;
|
||||
playButton.innerHTML = this.playHTML;
|
||||
currentTime.innerHTML = this.timeText;
|
||||
|
||||
pauseButton.style.opacity = this.sim.playing ? '100%' : '50%';
|
||||
playButton.style.opacity = this.sim.playing ? '50%' : '100%';
|
||||
|
||||
@ -38,7 +38,7 @@ export class Zoom extends Tool {
|
||||
zoomOut.innerHTML = 'Zoom<br>Out';
|
||||
zoomIn.innerHTML = 'Zoom<br>In';
|
||||
zoomAll.innerHTML = 'Zoom to Fit';
|
||||
zeroVelocity.innerHTML = 'Zero Net Velocity';
|
||||
zeroVelocity.innerHTML = 'Zero Momentum';
|
||||
currentScale.innerHTML = this.displayScaleText;
|
||||
|
||||
this.sim.onZoom(() => {
|
||||
@ -112,6 +112,11 @@ export class Zoom extends Tool {
|
||||
obj.velocity.x -= netVelocity.x;
|
||||
obj.velocity.y -= netVelocity.y;
|
||||
});
|
||||
|
||||
// TODO: Zero net angular momentum
|
||||
|
||||
// Cancel panning
|
||||
this.sim.pointer.panning = undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,8 +34,7 @@ export class Toolbar {
|
||||
}
|
||||
|
||||
frame() {
|
||||
for (let tool in this.tools) {
|
||||
// TODO: tool.frame()
|
||||
for (let tool of this.tools) {
|
||||
tool.frame();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user