work in progress
This commit is contained in:
parent
fe6a263164
commit
f5512c1be3
@ -1,11 +1,9 @@
|
|||||||
// VELOCITY
|
// VELOCITY
|
||||||
export const VELOCITY_VECTOR_SCALE = 8E0;
|
|
||||||
export const VELOCITY_VECTOR_COLOR = 'rgba(150, 150, 150, 0.8)'; // optionally set to 'object color'
|
export const VELOCITY_VECTOR_COLOR = 'rgba(150, 150, 150, 0.8)'; // 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;
|
||||||
|
|
||||||
// ACCELERATION
|
// ACCELERATION
|
||||||
export const ACCELERATION_VECTOR_SCALE = 8E0;
|
|
||||||
export const ACCELERATION_VECTOR_COLOR = 'rgba(0, 128, 0, 0.8)'; // optionally set to 'object color'
|
export const ACCELERATION_VECTOR_COLOR = 'rgba(0, 128, 0, 0.8)'; // optionally set to 'object color'
|
||||||
export const ACCELERATION_VECTOR_WIDTH = 1.5;
|
export const ACCELERATION_VECTOR_WIDTH = 1.5;
|
||||||
export const ACCELERATION_VECTOR_ARROWHEAD = true;
|
export const ACCELERATION_VECTOR_ARROWHEAD = true;
|
||||||
@ -54,3 +52,4 @@ export const MODE_OBJECT_SELECT = 'select';
|
|||||||
|
|
||||||
// LOCAL STORAGE PREFIXES/SUFFIXES
|
// LOCAL STORAGE PREFIXES/SUFFIXES
|
||||||
export const TOOLBAR_EXPANDED_SUFFIX = 'lhg-toolbar-expanded';
|
export const TOOLBAR_EXPANDED_SUFFIX = 'lhg-toolbar-expanded';
|
||||||
|
|
||||||
|
|||||||
25
display.js
25
display.js
@ -67,10 +67,6 @@ export class Display {
|
|||||||
ctx.fillRect(this.viewOrigin.x, this.viewOrigin.y, this.width, this.height);
|
ctx.fillRect(this.viewOrigin.x, this.viewOrigin.y, this.width, this.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawObjects() {
|
|
||||||
this.sim.objects.forEachObject(obj => obj.drawObject(this.sim), {alive: null});
|
|
||||||
}
|
|
||||||
|
|
||||||
drawArrow(startX, startY, endX, endY, {style, width, arrowhead, arrowheadLength, fill, ifShort}) {
|
drawArrow(startX, startY, endX, endY, {style, width, arrowhead, arrowheadLength, fill, ifShort}) {
|
||||||
const ctx = this.ctx;
|
const ctx = this.ctx;
|
||||||
ctx.strokeStyle = style;
|
ctx.strokeStyle = style;
|
||||||
@ -139,17 +135,12 @@ export class Display {
|
|||||||
ctx.resetTransform();
|
ctx.resetTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
computePanning(elapsedTime) {
|
frame(elapsedTime) {
|
||||||
// Add another entry for the current pointer position
|
|
||||||
const {
|
const {
|
||||||
pointerHistory,
|
touchStart: start,
|
||||||
panTouchStart: start,
|
touchLatest: latest,
|
||||||
panTouchLatest: latest,
|
|
||||||
} = this.sim.pointer ?? {};
|
} = this.sim.pointer ?? {};
|
||||||
if (pointerHistory?.length) {
|
|
||||||
const currentPointer = pointerHistory[pointerHistory.length - 1];
|
|
||||||
this.sim.pointer.updatePointer(currentPointer);
|
|
||||||
}
|
|
||||||
if (start && latest) {
|
if (start && latest) {
|
||||||
// Direct translate
|
// Direct translate
|
||||||
this.viewOrigin.x = start.viewOrigin.x - (latest.x - start.x) / this.scale;
|
this.viewOrigin.x = start.viewOrigin.x - (latest.x - start.x) / this.scale;
|
||||||
@ -158,9 +149,11 @@ export class Display {
|
|||||||
// Apply update to viewOrigin based on panning
|
// Apply update to viewOrigin based on panning
|
||||||
const { velocity } = this.sim.panning;
|
const { velocity } = this.sim.panning;
|
||||||
// TODO: something with time scale? Panning is too fast.
|
// TODO: something with time scale? Panning is too fast.
|
||||||
this.viewOrigin.x -= velocity.x * elapsedTime / 1000; // millisecond conversion?
|
this.viewOrigin.x += velocity.x * elapsedTime;
|
||||||
this.viewOrigin.y -= velocity.y * elapsedTime / 1000;
|
this.viewOrigin.y += velocity.y * elapsedTime;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
// Clear canvas in preparation for other modules to render this frame
|
||||||
|
this.fillCanvas();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
object.js
40
object.js
@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
ACCELERATION_VECTOR_ARROWHEAD,
|
ACCELERATION_VECTOR_ARROWHEAD,
|
||||||
ACCELERATION_VECTOR_COLOR,
|
ACCELERATION_VECTOR_COLOR,
|
||||||
ACCELERATION_VECTOR_SCALE,
|
|
||||||
ACCELERATION_VECTOR_WIDTH,
|
ACCELERATION_VECTOR_WIDTH,
|
||||||
OFFSCREEN_OBJECT_ARROWHEAD_LENGTH,
|
OFFSCREEN_OBJECT_ARROWHEAD_LENGTH,
|
||||||
OFFSCREEN_OBJECT_LINE_SCALE,
|
OFFSCREEN_OBJECT_LINE_SCALE,
|
||||||
@ -12,11 +11,12 @@ import {
|
|||||||
PATH_TRACES_WIDTH,
|
PATH_TRACES_WIDTH,
|
||||||
VELOCITY_VECTOR_ARROWHEAD,
|
VELOCITY_VECTOR_ARROWHEAD,
|
||||||
VELOCITY_VECTOR_COLOR,
|
VELOCITY_VECTOR_COLOR,
|
||||||
VELOCITY_VECTOR_SCALE,
|
|
||||||
VELOCITY_VECTOR_WIDTH,
|
VELOCITY_VECTOR_WIDTH,
|
||||||
} from './config.js';
|
} from './config.js';
|
||||||
|
|
||||||
export class MassObject {
|
export class MassObject {
|
||||||
|
sim = undefined;
|
||||||
|
id = undefined;
|
||||||
mass = 0;
|
mass = 0;
|
||||||
density = 1;
|
density = 1;
|
||||||
position = {x: undefined, y: undefined};
|
position = {x: undefined, y: undefined};
|
||||||
@ -28,7 +28,9 @@ export class MassObject {
|
|||||||
history = [];
|
history = [];
|
||||||
alive = true;
|
alive = true;
|
||||||
|
|
||||||
constructor(x, y) {
|
constructor(sim, x, y) {
|
||||||
|
this.sim = sim;
|
||||||
|
this.id = crypto.randomUUID();
|
||||||
this.position.x = x;
|
this.position.x = x;
|
||||||
this.position.y = y;
|
this.position.y = y;
|
||||||
this.color.r = Math.random() * 256;
|
this.color.r = Math.random() * 256;
|
||||||
@ -86,7 +88,7 @@ export class MassObject {
|
|||||||
const opacity = dashedTraces ? PATH_TRACES_DASHED_OPACITY : PATH_TRACES_OPACITY;
|
const opacity = dashedTraces ? PATH_TRACES_DASHED_OPACITY : PATH_TRACES_OPACITY;
|
||||||
ctx.strokeStyle = PATH_TRACES_COLOR === 'object color' ?
|
ctx.strokeStyle = PATH_TRACES_COLOR === 'object color' ?
|
||||||
`rgba(${r}, ${g}, ${b}, ${opacity})` : PATH_TRACES_COLOR;
|
`rgba(${r}, ${g}, ${b}, ${opacity})` : PATH_TRACES_COLOR;
|
||||||
ctx.lineWidth = PATH_TRACES_WIDTH / this.scale;
|
ctx.lineWidth = PATH_TRACES_WIDTH / this.sim.display.scale;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
let dash = false;
|
let dash = false;
|
||||||
for (let i = 0; i < this.history.length; i++) {
|
for (let i = 0; i < this.history.length; i++) {
|
||||||
@ -142,8 +144,8 @@ export class MassObject {
|
|||||||
|
|
||||||
const arrowDirection = Math.atan2(py - cy, px - cx);
|
const arrowDirection = Math.atan2(py - cy, px - cx);
|
||||||
// Length of arrow based on distance (logarithmic scale)
|
// Length of arrow based on distance (logarithmic scale)
|
||||||
const distance = Math.sqrt((x - px) ** 2, (y - py) ** 2) * this.scale;
|
const distance = Math.sqrt((x - px) ** 2, (y - py) ** 2);
|
||||||
const arrowLength = Math.log(distance) * OFFSCREEN_OBJECT_LINE_SCALE / this.scale;
|
const arrowLength = Math.log(distance) * OFFSCREEN_OBJECT_LINE_SCALE / this.sim.display.scale;
|
||||||
const startAx = px - arrowLength * Math.cos(arrowDirection);
|
const startAx = px - arrowLength * Math.cos(arrowDirection);
|
||||||
const startAy = py - arrowLength * Math.sin(arrowDirection);
|
const startAy = py - arrowLength * Math.sin(arrowDirection);
|
||||||
sim.display.drawArrow(startAx, startAy, px, py, {
|
sim.display.drawArrow(startAx, startAy, px, py, {
|
||||||
@ -165,9 +167,19 @@ export class MassObject {
|
|||||||
|
|
||||||
// Draw arrow for the velocity
|
// Draw arrow for the velocity
|
||||||
if (sim.getOption('display.velocity')) {
|
if (sim.getOption('display.velocity')) {
|
||||||
const speed = Math.sqrt(vx ** 2 + vy ** 2);
|
// If this object is being dragged by the user,
|
||||||
const endVx = x + VELOCITY_VECTOR_SCALE * vx / speed * Math.log(speed);
|
// show the pointer velocity instead of object velocity
|
||||||
const endVy = y + VELOCITY_VECTOR_SCALE * vy / speed * Math.log(speed);
|
const vecScale = this.sim.getOption('param.velocityScale');
|
||||||
|
const selected = this.sim.system.getSelectedOrCreating();
|
||||||
|
const velocity = selected?.id === this.id ?
|
||||||
|
this.sim.pointer.latestVelocity ?? {x: 0, y: 0} :
|
||||||
|
{x: vx, y: vy};
|
||||||
|
const speed = Math.sqrt(velocity.x ** 2, velocity.y ** 2);
|
||||||
|
const arrowDirection = Math.atan2(velocity.y, velocity.x);
|
||||||
|
// Prevent negative numbers by adding e
|
||||||
|
const arrowLength = Math.log(speed + 3) * vecScale / this.sim.display.scale;
|
||||||
|
const endVx = x + arrowLength * Math.cos(arrowDirection);
|
||||||
|
const endVy = y + arrowLength * Math.sin(arrowDirection);
|
||||||
const style = VELOCITY_VECTOR_COLOR === 'object color' ?
|
const style = VELOCITY_VECTOR_COLOR === 'object color' ?
|
||||||
`rgb(${r}, ${g}, ${b})` : VELOCITY_VECTOR_COLOR;
|
`rgb(${r}, ${g}, ${b})` : VELOCITY_VECTOR_COLOR;
|
||||||
sim.display.drawArrow(x, y, endVx, endVy, {
|
sim.display.drawArrow(x, y, endVx, endVy, {
|
||||||
@ -181,11 +193,13 @@ export class MassObject {
|
|||||||
|
|
||||||
// Draw arrow for acceleration
|
// Draw arrow for acceleration
|
||||||
if (sim.getOption('display.acceleration')) {
|
if (sim.getOption('display.acceleration')) {
|
||||||
|
const vecScale = this.sim.getOption('param.accelerationScale');
|
||||||
const accelerationMagnitude = Math.sqrt(acceleration.x ** 2 + acceleration.y ** 2);
|
const accelerationMagnitude = Math.sqrt(acceleration.x ** 2 + acceleration.y ** 2);
|
||||||
const endAx = x + ACCELERATION_VECTOR_SCALE * acceleration.x /
|
const arrowDirection = Math.atan2(acceleration.y, acceleration.x);
|
||||||
accelerationMagnitude * Math.log(accelerationMagnitude);
|
// Prevent negative numbers by adding e
|
||||||
const endAy = y + ACCELERATION_VECTOR_SCALE * acceleration.y /
|
const arrowLength = Math.log(accelerationMagnitude + 3) * vecScale / this.sim.display.scale;
|
||||||
accelerationMagnitude * Math.log(accelerationMagnitude);
|
const endAx = x + arrowLength * Math.cos(arrowDirection);
|
||||||
|
const endAy = y + arrowLength * Math.sin(arrowDirection);
|
||||||
const style = ACCELERATION_VECTOR_COLOR === 'object color' ?
|
const style = ACCELERATION_VECTOR_COLOR === 'object color' ?
|
||||||
`rgb(${r}, ${g}, ${b})` : ACCELERATION_VECTOR_COLOR;
|
`rgb(${r}, ${g}, ${b})` : ACCELERATION_VECTOR_COLOR;
|
||||||
sim.display.drawArrow(x, y, endAx, endAy, {
|
sim.display.drawArrow(x, y, endAx, endAy, {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ export class Options {
|
|||||||
sim = undefined;
|
sim = undefined;
|
||||||
options = undefined;
|
options = undefined;
|
||||||
values = {};
|
values = {};
|
||||||
|
undefinedObj = {_undefined: true};
|
||||||
|
|
||||||
getStorageKey(path) {
|
getStorageKey(path) {
|
||||||
return `${path}:options`;
|
return `${path}:options`;
|
||||||
@ -28,16 +29,24 @@ export class Options {
|
|||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
value = defaultValue;
|
value = defaultValue;
|
||||||
}
|
}
|
||||||
|
console.log('initializing option', {path, defaultValue, value});
|
||||||
this.values[path] = value;
|
this.values[path] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toStored(value) {
|
toStored(value) {
|
||||||
|
if (value === undefined) {
|
||||||
|
return JSON.stringify(this.undefinedObj);
|
||||||
|
}
|
||||||
return JSON.stringify(value);
|
return JSON.stringify(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// value: string
|
||||||
fromStored(value) {
|
fromStored(value) {
|
||||||
|
if (value === JSON.stringify(this.undefinedObj)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
return JSON.parse(value);
|
return JSON.parse(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export class Overlay {
|
|||||||
infoBox.classList.add(OVERLAY_INFO_BOX_CLASSNAME);
|
infoBox.classList.add(OVERLAY_INFO_BOX_CLASSNAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderInfo() {
|
frame() {
|
||||||
this.infoBox.innerHTML = '';
|
this.infoBox.innerHTML = '';
|
||||||
const table = document.createElement('table');
|
const table = document.createElement('table');
|
||||||
for (let [k, v] of Object.entries(this.sim.info)) {
|
for (let [k, v] of Object.entries(this.sim.info)) {
|
||||||
|
|||||||
57
pointer.js
57
pointer.js
@ -13,8 +13,8 @@ export class Pointer {
|
|||||||
sim = undefined;
|
sim = undefined;
|
||||||
|
|
||||||
pointerHistory = [];
|
pointerHistory = [];
|
||||||
panTouchStart = undefined; // {x: undefined, y: undefined, t: undefined};
|
touchStart = undefined; // {x: undefined, y: undefined, t: undefined};
|
||||||
panTouchLatest = undefined; // {x: undefined, y: undefined, t: undefined};
|
touchLatest = undefined; // {x: undefined, y: undefined, t: undefined};
|
||||||
suppressClick = false;
|
suppressClick = false;
|
||||||
|
|
||||||
constructor(sim) {
|
constructor(sim) {
|
||||||
@ -61,7 +61,7 @@ export class Pointer {
|
|||||||
getPointerVelocity(points = POINTER_HISTORY_SIZE) {
|
getPointerVelocity(points = POINTER_HISTORY_SIZE) {
|
||||||
// Average over pointer history
|
// Average over pointer history
|
||||||
if (this.pointerHistory.length < 2) {
|
if (this.pointerHistory.length < 2) {
|
||||||
return this.latestPointerVelocity ?? {x: 0, y: 0, dt: 1};
|
return this.latestVelocity ?? {x: 0, y: 0, dt: 1};
|
||||||
}
|
}
|
||||||
points = Math.min(points, POINTER_HISTORY_SIZE, this.pointerHistory.length);
|
points = Math.min(points, POINTER_HISTORY_SIZE, this.pointerHistory.length);
|
||||||
const start = this.pointerHistory[this.pointerHistory.length - points];
|
const start = this.pointerHistory[this.pointerHistory.length - points];
|
||||||
@ -79,7 +79,7 @@ export class Pointer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updatePointer({x, y}) {
|
updatePointer({x, y}) {
|
||||||
const t = document.timeline.currentTime;
|
const t = this.sim.rawTime;
|
||||||
while (this.pointerHistory.length >= POINTER_HISTORY_SIZE) {
|
while (this.pointerHistory.length >= POINTER_HISTORY_SIZE) {
|
||||||
this.pointerHistory.shift();
|
this.pointerHistory.shift();
|
||||||
}
|
}
|
||||||
@ -87,7 +87,7 @@ export class Pointer {
|
|||||||
this.pointerHistory.push({t, x, y, v});
|
this.pointerHistory.push({t, x, y, v});
|
||||||
}
|
}
|
||||||
|
|
||||||
get latestPointerVelocity() {
|
get latestVelocity() {
|
||||||
const latestPointer = this.pointerHistory[this.pointerHistory.length - 1];
|
const latestPointer = this.pointerHistory[this.pointerHistory.length - 1];
|
||||||
return latestPointer?.v;
|
return latestPointer?.v;
|
||||||
}
|
}
|
||||||
@ -98,16 +98,21 @@ export class Pointer {
|
|||||||
|
|
||||||
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
|
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
|
||||||
const {x, y} = this.sim.screenToSim(clientX, clientY)
|
const {x, y} = this.sim.screenToSim(clientX, clientY)
|
||||||
this.sim.objects.handlePointerDown({x, y});
|
this.sim.system.handlePointerDown({x, y});
|
||||||
|
|
||||||
} else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) {
|
} else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) {
|
||||||
this.panTouchStart = {
|
this.touchStart = {
|
||||||
x: clientX,
|
x: clientX,
|
||||||
y: clientY,
|
y: clientY,
|
||||||
t: document.timeline.currentTime,
|
t: this.sim.rawTime,
|
||||||
viewOrigin: {...this.sim.display.viewOrigin},
|
viewOrigin: {...this.sim.display.viewOrigin},
|
||||||
};
|
};
|
||||||
this.panTouchLatest = {...this.panTouchStart};
|
this.touchLatest = {
|
||||||
|
...this.touchStart,
|
||||||
|
dx: 0,
|
||||||
|
dy: 0,
|
||||||
|
dt: 0,
|
||||||
|
};
|
||||||
|
|
||||||
} else if (this.sim.isCurrentMode(MODE_OBJECT_SELECT)) {
|
} else if (this.sim.isCurrentMode(MODE_OBJECT_SELECT)) {
|
||||||
// TODO: Start a selection box
|
// TODO: Start a selection box
|
||||||
@ -117,16 +122,15 @@ export class Pointer {
|
|||||||
handlePointerUp({x: clientX, y: clientY}) {
|
handlePointerUp({x: clientX, y: clientY}) {
|
||||||
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
|
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
|
||||||
const {x, y} = this.sim.screenToSim(clientX, clientY);
|
const {x, y} = this.sim.screenToSim(clientX, clientY);
|
||||||
this.sim.objects.handlePointerUp({x, y});
|
this.sim.system.handlePointerUp({x, y});
|
||||||
|
|
||||||
} else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) {
|
} else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) {
|
||||||
// Set panning velocity
|
// Set panning velocity
|
||||||
if (this.panTouchStart && this.panTouchLatest) {
|
if (this.touchStart && this.touchLatest) {
|
||||||
const dt = (this.panTouchLatest.t - this.panTouchStart.t) / 1000;
|
if (!this.touchLatest.dt) {
|
||||||
if (!dt) {
|
|
||||||
this.sim.panning = undefined;
|
this.sim.panning = undefined;
|
||||||
} else {
|
} else {
|
||||||
const v = {...this.latestPointerVelocity};
|
const v = {...this.latestVelocity};
|
||||||
// Convert pointer velocity to simulation scale
|
// Convert pointer velocity to simulation scale
|
||||||
v.x /= this.sim.display.scale;
|
v.x /= this.sim.display.scale;
|
||||||
v.y /= this.sim.display.scale;
|
v.y /= this.sim.display.scale;
|
||||||
@ -136,7 +140,7 @@ export class Pointer {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.panTouchStart = undefined;
|
this.touchStart = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,23 +151,32 @@ export class Pointer {
|
|||||||
this.updatePointer({x: clientX, y: clientY});
|
this.updatePointer({x: clientX, y: clientY});
|
||||||
|
|
||||||
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
|
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
|
||||||
// Convert pointer velocity to simulation scale
|
|
||||||
const vx = this.latestPointerVelocity.x / this.sim.display.scale;
|
|
||||||
const vy = this.latestPointerVelocity.y / this.sim.display.scale;
|
|
||||||
const {x, y} = this.sim.screenToSim(clientX, clientY);
|
const {x, y} = this.sim.screenToSim(clientX, clientY);
|
||||||
this.sim.objects.handlePointerMove({x, y, vx, vy});
|
this.sim.system.handlePointerMove({x, y});
|
||||||
|
|
||||||
} else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) {
|
} else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) {
|
||||||
if (this.panTouchStart) {
|
if (this.touchStart) {
|
||||||
// Event loop should be able to read
|
// Event loop should be able to read
|
||||||
this.panTouchLatest = {
|
this.touchLatest = {
|
||||||
x: clientX,
|
x: clientX,
|
||||||
y: clientY,
|
y: clientY,
|
||||||
t: this.sim.rawTime,
|
t: this.sim.rawTime,
|
||||||
|
dx: clientX - this.touchStart.x,
|
||||||
|
dy: clientY - this.touchStart.y,
|
||||||
|
dt: this.sim.rawTime - this.touchStart.t,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frame() {
|
||||||
|
// Add another entry for the current pointer position
|
||||||
|
const { pointerHistory } = this.sim.pointer ?? {};
|
||||||
|
console.log('pointer history length', pointerHistory?.length);
|
||||||
|
if (pointerHistory?.length) {
|
||||||
|
const currentPointer = pointerHistory[pointerHistory.length - 1];
|
||||||
|
this.sim.pointer.updatePointer(currentPointer);
|
||||||
|
console.log('updating in case of idle pointer', currentPointer.x, currentPointer.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,10 +4,12 @@ export const simOptions = {
|
|||||||
selection: ['Pause While Selecting', 'boolean', true],
|
selection: ['Pause While Selecting', 'boolean', true],
|
||||||
},
|
},
|
||||||
display: {
|
display: {
|
||||||
velocity: ['Velocity Vector', 'boolean', true],
|
|
||||||
acceleration: ['Accel Vector', 'boolean', true],
|
|
||||||
traces: ['Path Trace', 'boolean', true],
|
traces: ['Path Trace', 'boolean', true],
|
||||||
dashedTraces: ['Dashed', 'boolean', false, {tall: true}],
|
dashedTraces: ['Dashed', 'boolean', false, {tall: true}],
|
||||||
|
velocity: ['Velocity Vector', 'boolean', true],
|
||||||
|
acceleration: ['Accel Vector', 'boolean', true],
|
||||||
|
velocityScale: ['Velocity<br>Vec Scale', 'number', 20],
|
||||||
|
accelerationScale: ['Accel<br>Vec Scale', 'number', 20],
|
||||||
},
|
},
|
||||||
collision: {
|
collision: {
|
||||||
merge: ['Merge Masses<br>on Collision', 'boolean', true, {wide: true}],
|
merge: ['Merge Masses<br>on Collision', 'boolean', true, {wide: true}],
|
||||||
@ -20,8 +22,9 @@ export const simOptions = {
|
|||||||
},
|
},
|
||||||
debug: {
|
debug: {
|
||||||
objectsInfo: ['Objects Info', 'boolean', false],
|
objectsInfo: ['Objects Info', 'boolean', false],
|
||||||
|
aliveObjects: ['Alive Only', 'boolean', false],
|
||||||
cursorInfo: ['Cursor Info', 'boolean', false],
|
cursorInfo: ['Cursor Info', 'boolean', false],
|
||||||
frameRate: ['Frame Rate', 'boolean', false, {wide: true}],
|
frameRate: ['Frame Rate', 'boolean', false],
|
||||||
currentMode: ['Current Mode', 'boolean', false],
|
currentMode: ['Current Mode', 'boolean', false],
|
||||||
panningInfo: ['Panning Info', 'boolean', false],
|
panningInfo: ['Panning Info', 'boolean', false],
|
||||||
},
|
},
|
||||||
|
|||||||
36
simulator.js
36
simulator.js
@ -5,7 +5,7 @@ import {
|
|||||||
SCALE_POWER_MIN,
|
SCALE_POWER_MIN,
|
||||||
} from './config.js';
|
} from './config.js';
|
||||||
import { Display } from './display.js';
|
import { Display } from './display.js';
|
||||||
import { Objects } from './objects.js';
|
import { System } from './system.js';
|
||||||
import { Options } from './options.js';
|
import { Options } from './options.js';
|
||||||
import { simOptions } from './sim-options.js';
|
import { simOptions } from './sim-options.js';
|
||||||
import { initializeTools } from './sim-tools.js';
|
import { initializeTools } from './sim-tools.js';
|
||||||
@ -14,12 +14,13 @@ export class Sim {
|
|||||||
info = {};
|
info = {};
|
||||||
rawTime = undefined;
|
rawTime = undefined;
|
||||||
time = undefined;
|
time = undefined;
|
||||||
|
timeScale = undefined;
|
||||||
nextZoom = undefined;
|
nextZoom = undefined;
|
||||||
playing = true;
|
playing = true;
|
||||||
recentFrames = [];
|
recentFrames = [];
|
||||||
frameRate = 0;
|
frameRate = 0;
|
||||||
|
|
||||||
objects = undefined;
|
system = undefined;
|
||||||
display = undefined;
|
display = undefined;
|
||||||
overlay = undefined;
|
overlay = undefined;
|
||||||
pointer = undefined;
|
pointer = undefined;
|
||||||
@ -31,6 +32,7 @@ export class Sim {
|
|||||||
getCurrentMode = () => undefined;
|
getCurrentMode = () => undefined;
|
||||||
setCurrentMode = () => undefined;
|
setCurrentMode = () => undefined;
|
||||||
getOption = () => undefined;
|
getOption = () => undefined;
|
||||||
|
setOption = () => undefined;
|
||||||
onModeEnter = () => undefined;
|
onModeEnter = () => undefined;
|
||||||
onModeLeave = () => undefined;
|
onModeLeave = () => undefined;
|
||||||
|
|
||||||
@ -41,8 +43,8 @@ export class Sim {
|
|||||||
const oldest = rfs[0];
|
const oldest = rfs[0];
|
||||||
const newest = rfs[rfs.length - 1];
|
const newest = rfs[rfs.length - 1];
|
||||||
const count = rfs.length;
|
const count = rfs.length;
|
||||||
const duration = (newest - oldest) / 1000; // ms to s
|
const duration = (newest - oldest);
|
||||||
this.frameRate = count / duration;
|
this.frameRate = 1000 * count / duration; // Converting from ms to s
|
||||||
if (duration >= FRAMERATE_SAMPLE_DURATION) {
|
if (duration >= FRAMERATE_SAMPLE_DURATION) {
|
||||||
rfs.shift();
|
rfs.shift();
|
||||||
}
|
}
|
||||||
@ -55,7 +57,7 @@ export class Sim {
|
|||||||
|
|
||||||
this.options = new Options(this, simOptions);
|
this.options = new Options(this, simOptions);
|
||||||
this.display = new Display(this);
|
this.display = new Display(this);
|
||||||
this.objects = new Objects(this);
|
this.system = new System(this);
|
||||||
|
|
||||||
initializeTools(this);
|
initializeTools(this);
|
||||||
|
|
||||||
@ -90,10 +92,7 @@ export class Sim {
|
|||||||
|
|
||||||
if (this.playing && velocity) {
|
if (this.playing && velocity) {
|
||||||
this.panning = {
|
this.panning = {
|
||||||
velocity: {
|
velocity: { ...velocity }
|
||||||
x: -velocity.x,
|
|
||||||
y: -velocity.y,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,11 +128,9 @@ export class Sim {
|
|||||||
// Main loop
|
// Main loop
|
||||||
loop(currentTime) {
|
loop(currentTime) {
|
||||||
this.markFrame(currentTime);
|
this.markFrame(currentTime);
|
||||||
const timeScale = this.getOption('param.timeScale');
|
this.timeScale = this.getOption('param.timeScale');
|
||||||
|
|
||||||
// elapsedTime in milliseconds
|
const elapsedTime = (currentTime - this.rawTime) * this.timeScale;
|
||||||
// rawTime in milliseconds
|
|
||||||
const elapsedTime = (currentTime - this.rawTime) / timeScale;
|
|
||||||
this.rawTime = currentTime;
|
this.rawTime = currentTime;
|
||||||
|
|
||||||
if (this.playing) {
|
if (this.playing) {
|
||||||
@ -156,19 +153,18 @@ export class Sim {
|
|||||||
if (this.getOption('debug.panningInfo')) {
|
if (this.getOption('debug.panningInfo')) {
|
||||||
const {x, y} = this.panning?.velocity ?? {};
|
const {x, y} = this.panning?.velocity ?? {};
|
||||||
this.info['Panning Velocity'] = [`${x?.toPrecision(6)}, `, y?.toPrecision(6)];
|
this.info['Panning Velocity'] = [`${x?.toPrecision(6)}, `, y?.toPrecision(6)];
|
||||||
const { centerOfMass } = this.objects.computeSystemCenter();
|
const { centerOfMass } = this.system.computeSystemCenter();
|
||||||
this.info['Center of Mass'] = [`${centerOfMass.x.toPrecision(6)}, `, centerOfMass.y.toPrecision(6)];
|
this.info['Center of Mass'] = [`${centerOfMass.x.toPrecision(6)}, `, centerOfMass.y.toPrecision(6)];
|
||||||
this.info['Net Angular Momentum'] = this.objects.computeSystemAngularMomentum().toPrecision(6);
|
this.info['Net Angular Momentum'] = this.system.computeSystemAngularMomentum().toPrecision(6);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.objects.computeFrame(elapsedTime);
|
this.display.frame(elapsedTime);
|
||||||
this.overlay.renderInfo();
|
this.system.frame(elapsedTime);
|
||||||
// this.display.computePanning(elapsedTime);
|
this.overlay.frame();
|
||||||
this.display.fillCanvas();
|
|
||||||
this.display.drawObjects();
|
|
||||||
for (const group in this.toolbarGroups) {
|
for (const group in this.toolbarGroups) {
|
||||||
this.toolbarGroups[group].frame();
|
this.toolbarGroups[group].frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAnimationFrame(t => this.loop(t));
|
requestAnimationFrame(t => this.loop(t));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { MassObject } from './object.js';
|
import { MassObject } from './object.js';
|
||||||
import { ZOOM_TO_FIT_PADDING } from './config.js';
|
import { ZOOM_TO_FIT_PADDING } from './config.js';
|
||||||
|
|
||||||
export class Objects {
|
export class System {
|
||||||
objects = [];
|
objects = [];
|
||||||
creatingObject = undefined;
|
creatingObject = undefined;
|
||||||
selectedObject = undefined;
|
selectedObject = undefined;
|
||||||
|
selectObjectStart = undefined;
|
||||||
paused = false;
|
paused = false;
|
||||||
panVelocityPaused = undefined;
|
panVelocityPaused = undefined;
|
||||||
|
|
||||||
@ -35,8 +36,9 @@ export class Objects {
|
|||||||
// Create an object with mass that grows as pointer is held down
|
// Create an object with mass that grows as pointer is held down
|
||||||
createObject(x, y) {
|
createObject(x, y) {
|
||||||
const idx = this.objects.length;
|
const idx = this.objects.length;
|
||||||
const obj = new MassObject(x, y, idx);
|
const obj = new MassObject(this.sim, x, y);
|
||||||
this.creatingObject = idx;
|
this.creatingObject = idx;
|
||||||
|
this.selectedObjectStart = {x, y, pointer: {x, y}};
|
||||||
this.objects.push(obj);
|
this.objects.push(obj);
|
||||||
// Pause the simulation during mass creation; this avoids some complex local dynamics
|
// Pause the simulation during mass creation; this avoids some complex local dynamics
|
||||||
if (this.sim.getOption('pauseDuring.creation')) {
|
if (this.sim.getOption('pauseDuring.creation')) {
|
||||||
@ -59,8 +61,10 @@ export class Objects {
|
|||||||
return this.objects[i];
|
return this.objects[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
selectObject(i) {
|
selectObject(i, pointer) {
|
||||||
this.selectedObject = i;
|
this.selectedObject = i;
|
||||||
|
const {x, y} = this.object(i).position;
|
||||||
|
this.selectedObjectStart = {x, y, pointer};
|
||||||
if (this.sim.getOption('pauseDuring.selection')) {
|
if (this.sim.getOption('pauseDuring.selection')) {
|
||||||
this.pause();
|
this.pause();
|
||||||
}
|
}
|
||||||
@ -68,6 +72,7 @@ export class Objects {
|
|||||||
|
|
||||||
deselect() {
|
deselect() {
|
||||||
this.selectedObject = undefined;
|
this.selectedObject = undefined;
|
||||||
|
this.selectedObjectStart = undefined;
|
||||||
this.resume();
|
this.resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,6 +112,7 @@ export class Objects {
|
|||||||
|
|
||||||
objectAtLocation(x, y) {
|
objectAtLocation(x, y) {
|
||||||
let idx = undefined;
|
let idx = undefined;
|
||||||
|
this.selectedObjectStart = undefined;
|
||||||
this.forEachObject((obj, i) => {
|
this.forEachObject((obj, i) => {
|
||||||
// If distance to object is less than object's radius, we are touching the object
|
// If distance to object is less than object's radius, we are touching the object
|
||||||
const dist = Math.pow((obj.position.x - x)**2 + (obj.position.y - y)**2, 1/2);
|
const dist = Math.pow((obj.position.x - x)**2 + (obj.position.y - y)**2, 1/2);
|
||||||
@ -123,7 +129,7 @@ export class Objects {
|
|||||||
const touchingObject = this.objectAtLocation(x, y);
|
const touchingObject = this.objectAtLocation(x, y);
|
||||||
|
|
||||||
if (touchingObject !== undefined) {
|
if (touchingObject !== undefined) {
|
||||||
this.selectObject(touchingObject);
|
this.selectObject(touchingObject, {x, y});
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, create a new object
|
// Otherwise, create a new object
|
||||||
this.createObject(x, y);
|
this.createObject(x, y);
|
||||||
@ -131,24 +137,31 @@ export class Objects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handlePointerUp() {
|
handlePointerUp() {
|
||||||
|
const obj = this.getSelectedOrCreating();
|
||||||
|
if (obj === undefined) return;
|
||||||
this.doneCreatingObject();
|
this.doneCreatingObject();
|
||||||
this.deselect();
|
this.deselect();
|
||||||
|
// Convert pointer velocity to simulation scale
|
||||||
|
// Including time scale - if time is slow, our motion is relatively faster
|
||||||
|
const pointer = {...this.sim.pointer.latestVelocity};
|
||||||
|
obj.velocity.x = pointer.x / this.sim.display.scale * this.sim.timeScale;
|
||||||
|
obj.velocity.y = pointer.y / this.sim.display.scale * this.sim.timeScale;
|
||||||
|
if (this.sim.panning?.velocity) {
|
||||||
|
obj.velocity.x += this.sim.panning.velocity.x;
|
||||||
|
obj.velocity.y += this.sim.panning.velocity.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePointerMove({x, y, vx, vy}) {
|
handlePointerMove({x, y}) {
|
||||||
// If the cursor moves while creating an object, or while an object is selected,
|
// If the cursor moves while creating an object, or while an object is selected,
|
||||||
// update the position and velocity of the object
|
|
||||||
// update the position using the pointer motion but the velocity using the pointer velocity
|
// update the position using the pointer motion but the velocity using the pointer velocity
|
||||||
const obj = this.getSelectedOrCreating();
|
const obj = this.getSelectedOrCreating();
|
||||||
if (obj === undefined) return;
|
if (obj === undefined) return;
|
||||||
if (this.sim.panning?.velocity) {
|
const start = this.selectedObjectStart;
|
||||||
vx += this.sim.panning.velocity.x;
|
obj.position.x = start.x + (x - start.pointer.x);
|
||||||
vy += this.sim.panning.velocity.y;
|
obj.position.y = start.y + (y - start.pointer.y);
|
||||||
}
|
obj.velocity.x = 0;
|
||||||
obj.position.x = x;
|
obj.velocity.y = 0;
|
||||||
obj.position.y = y;
|
|
||||||
obj.velocity.x = vx;
|
|
||||||
obj.velocity.y = vy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// cb: (obj, idx) => {}
|
// cb: (obj, idx) => {}
|
||||||
@ -163,6 +176,10 @@ export class Objects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawObjects() {
|
||||||
|
this.forEachObject(obj => obj.drawObject(this.sim), {alive: null});
|
||||||
|
}
|
||||||
|
|
||||||
// cb: (acc, obj, idx) => {}
|
// cb: (acc, obj, idx) => {}
|
||||||
reduce(cb, initial, opts) {
|
reduce(cb, initial, opts) {
|
||||||
let acc = initial;
|
let acc = initial;
|
||||||
@ -201,9 +218,7 @@ export class Objects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// elapsedTime is given in milliseconds
|
// elapsedTime is given in milliseconds
|
||||||
computeFrame(elapsedTime) {
|
frame(elapsedTime) {
|
||||||
// convert elapsed time to seconds
|
|
||||||
elapsedTime /= 1000;
|
|
||||||
// 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
|
||||||
|
|
||||||
@ -211,8 +226,7 @@ export class Objects {
|
|||||||
|
|
||||||
if (this.creatingObject !== undefined) {
|
if (this.creatingObject !== undefined) {
|
||||||
const obj = this.objects[this.creatingObject];
|
const obj = this.objects[this.creatingObject];
|
||||||
// Putting in a somewhat arbitrary scaling factor here
|
let massCreationRate = this.sim.getOption('param.massCreationRate');
|
||||||
let massCreationRate = this.sim.getOption('param.massCreationRate') / 1000;
|
|
||||||
// Mass creation rate acceleration
|
// Mass creation rate acceleration
|
||||||
if (this.sim.getOption('param.massAcceleration')) {
|
if (this.sim.getOption('param.massAcceleration')) {
|
||||||
massCreationRate *= obj.age;
|
massCreationRate *= obj.age;
|
||||||
@ -224,8 +238,6 @@ export class Objects {
|
|||||||
this.computeForces();
|
this.computeForces();
|
||||||
|
|
||||||
if (this.sim.playing) {
|
if (this.sim.playing) {
|
||||||
// TODO: If creating/selected object, clamp its position to the cursor
|
|
||||||
|
|
||||||
// Predict positions (Velocity verlet method)
|
// Predict positions (Velocity verlet method)
|
||||||
this.forEachObject(obj => {
|
this.forEachObject(obj => {
|
||||||
obj.currentAcceleration = {...obj.acceleration};
|
obj.currentAcceleration = {...obj.acceleration};
|
||||||
@ -307,26 +319,39 @@ export class Objects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Display objects info
|
// Display objects info
|
||||||
|
// First clear info from previous frame
|
||||||
|
this.forEachObject((_obj, i) => {
|
||||||
|
delete this.sim.info[`Object ${i}`];
|
||||||
|
}, { alive: null });
|
||||||
if (this.sim.getOption('debug.objectsInfo')) {
|
if (this.sim.getOption('debug.objectsInfo')) {
|
||||||
|
const aliveOnly = this.sim.getOption('debug.aliveObjects');
|
||||||
this.forEachObject((obj, i) => {
|
this.forEachObject((obj, i) => {
|
||||||
const speed = Math.pow(obj.velocity.x ** 2 + obj.velocity.y ** 2, 1/2);
|
const speed = Math.pow(obj.velocity.x ** 2 + obj.velocity.y ** 2, 1/2);
|
||||||
|
const accel = Math.pow(obj.acceleration.x ** 2 + obj.acceleration.y ** 2, 1/2);
|
||||||
// Invert y so that the angle is counterclockwise from x-axis
|
// Invert y so that the angle is counterclockwise from x-axis
|
||||||
const direction = Math.atan2(-obj.velocity.y, obj.velocity.x) * 180 / Math.PI;
|
const direction = Math.atan2(-obj.velocity.y, obj.velocity.x) * 180 / Math.PI;
|
||||||
|
const accelDir = Math.atan2(-obj.acceleration.y, obj.acceleration.x) * 180 / Math.PI;
|
||||||
|
const {r, g, b} = obj.color;
|
||||||
this.sim.info[`Object ${i}`] = [
|
this.sim.info[`Object ${i}`] = [
|
||||||
`${obj.position.x.toPrecision(6)}, `,
|
`<span style="background-color: rgb(${r},${g},${b});"> </span>`,
|
||||||
`${obj.position.y.toPrecision(6)}, `,
|
`${obj.position.x.toPrecision(4)}, `,
|
||||||
`${obj.mass.toPrecision(6)} kg, `,
|
`${obj.position.y.toPrecision(4)}, `,
|
||||||
|
`${obj.mass.toPrecision(4)} kg, `,
|
||||||
`${speed.toPrecision(2)} m/s, ${direction.toPrecision(2)}°`,
|
`${speed.toPrecision(2)} m/s, ${direction.toPrecision(2)}°`,
|
||||||
|
`${accel.toPrecision(2)} m/s<sup>2</sup>, ${accelDir.toPrecision(2)}°`,
|
||||||
`Alive: ${obj.alive}`,
|
`Alive: ${obj.alive}`,
|
||||||
];
|
];
|
||||||
}, { alive: null });
|
}, { alive: aliveOnly || null });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render the objects
|
||||||
|
this.drawObjects();
|
||||||
}
|
}
|
||||||
|
|
||||||
computeSystemCenter() {
|
computeSystemCenter() {
|
||||||
// Determine center of mass
|
// Determine center of mass
|
||||||
const { totalMass, count, totalMassLocation } =
|
const { totalMass, count, totalMassLocation } =
|
||||||
this.sim.objects.reduce((acc, obj) => ({
|
this.reduce((acc, obj) => ({
|
||||||
count: acc.count + 1,
|
count: acc.count + 1,
|
||||||
totalMass: acc.totalMass + obj.mass,
|
totalMass: acc.totalMass + obj.mass,
|
||||||
totalMassLocation: {
|
totalMassLocation: {
|
||||||
@ -345,7 +370,7 @@ export class Objects {
|
|||||||
} : {x: 0, y: 0};
|
} : {x: 0, y: 0};
|
||||||
|
|
||||||
// Determine average momentum
|
// Determine average momentum
|
||||||
const netMomentum = this.sim.objects.reduce((acc, obj) => ({
|
const netMomentum = this.reduce((acc, obj) => ({
|
||||||
x: acc.x + obj.mass * obj.velocity.x,
|
x: acc.x + obj.mass * obj.velocity.x,
|
||||||
y: acc.y + obj.mass * obj.velocity.y,
|
y: acc.y + obj.mass * obj.velocity.y,
|
||||||
}), { x: 0, y: 0 });
|
}), { x: 0, y: 0 });
|
||||||
@ -43,18 +43,11 @@ export class OptionsTool extends Tool {
|
|||||||
const value = this.sim.getOption(path);
|
const value = this.sim.getOption(path);
|
||||||
button.style.opacity = value ? '100%' : '50%';
|
button.style.opacity = value ? '100%' : '50%';
|
||||||
this.sim.onOptionSet(path, value => {
|
this.sim.onOptionSet(path, value => {
|
||||||
console.log('option set cb', path, value);
|
|
||||||
button.style.opacity = value ? '100%' : '50%';
|
button.style.opacity = value ? '100%' : '50%';
|
||||||
console.log('button opacity', button.style.opacity);
|
|
||||||
});
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
const value = this.sim.options.getOption(path, true);
|
|
||||||
console.log('click, option value', value);
|
|
||||||
this.sim.setOption(path, !value);
|
|
||||||
});
|
});
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
const value = this.sim.getOption(path);
|
const value = this.sim.getOption(path);
|
||||||
this.setOption(path, !value);
|
this.sim.setOption(path, !value);
|
||||||
});
|
});
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -81,14 +81,14 @@ export class UtilityTool extends Tool {
|
|||||||
|
|
||||||
zeroVelocity.addEventListener('click', () => {
|
zeroVelocity.addEventListener('click', () => {
|
||||||
// Determine center of mass and average momentum
|
// Determine center of mass and average momentum
|
||||||
const { totalMass, netMomentum } = this.sim.objects.computeSystemCenter();
|
const { totalMass, netMomentum } = this.sim.system.computeSystemCenter();
|
||||||
const netVelocity = {
|
const netVelocity = {
|
||||||
x: netMomentum.x / totalMass,
|
x: netMomentum.x / totalMass,
|
||||||
y: netMomentum.y / totalMass,
|
y: netMomentum.y / totalMass,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply offset to all object velocities
|
// Apply offset to all object velocities
|
||||||
this.sim.objects.forEachObject(obj => {
|
this.sim.system.forEachObject(obj => {
|
||||||
obj.velocity.x -= netVelocity.x;
|
obj.velocity.x -= netVelocity.x;
|
||||||
obj.velocity.y -= netVelocity.y;
|
obj.velocity.y -= netVelocity.y;
|
||||||
});
|
});
|
||||||
@ -99,14 +99,14 @@ export class UtilityTool extends Tool {
|
|||||||
|
|
||||||
clearTraces.addEventListener('click', () => {
|
clearTraces.addEventListener('click', () => {
|
||||||
// Obliterate object histories
|
// Obliterate object histories
|
||||||
this.sim.objects.forEachObject(obj => {
|
this.sim.system.forEachObject(obj => {
|
||||||
obj.history = [];
|
obj.history = [];
|
||||||
}, {alive: null});
|
}, {alive: null});
|
||||||
});
|
});
|
||||||
|
|
||||||
zoomAll.addEventListener('click', () => {
|
zoomAll.addEventListener('click', () => {
|
||||||
// Determine bounding box
|
// Determine bounding box
|
||||||
const box = this.sim.objects.boundingBox;
|
const box = this.sim.system.boundingBox;
|
||||||
const x = (box.start.x + box.end.x) / 2;
|
const x = (box.start.x + box.end.x) / 2;
|
||||||
const y = (box.start.y + box.end.y) / 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 widthRatio = Math.abs(box.start.x - box.end.x) / this.sim.display.width;
|
||||||
@ -118,7 +118,7 @@ export class UtilityTool extends Tool {
|
|||||||
const netMomentum = {x: 0, y: 0};
|
const netMomentum = {x: 0, y: 0};
|
||||||
let totalMass = 0;
|
let totalMass = 0;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
this.sim.objects.forEachObject(obj => {
|
this.sim.system.forEachObject(obj => {
|
||||||
count++;
|
count++;
|
||||||
netMomentum.x += obj.mass * obj.velocity.x;
|
netMomentum.x += obj.mass * obj.velocity.x;
|
||||||
netMomentum.y += obj.mass * obj.velocity.y;
|
netMomentum.y += obj.mass * obj.velocity.y;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user