From f5512c1be3573f5315c7bfae381f84048393def1 Mon Sep 17 00:00:00 2001 From: Ladd Date: Wed, 31 Dec 2025 15:56:01 -0600 Subject: [PATCH] work in progress --- config.js | 3 +- display.js | 25 +++++-------- object.js | 40 ++++++++++++++------- options.js | 9 +++++ overlay.js | 2 +- pointer.js | 57 +++++++++++++++++------------ sim-options.js | 9 +++-- simulator.js | 36 +++++++++---------- objects.js => system.js | 79 +++++++++++++++++++++++++++-------------- tool/options.js | 9 +---- tool/utility.js | 10 +++--- 11 files changed, 162 insertions(+), 117 deletions(-) rename objects.js => system.js (83%) diff --git a/config.js b/config.js index dcfb7df..60a9a12 100644 --- a/config.js +++ b/config.js @@ -1,11 +1,9 @@ // 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_WIDTH = 1.5; export const VELOCITY_VECTOR_ARROWHEAD = true; // 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_WIDTH = 1.5; export const ACCELERATION_VECTOR_ARROWHEAD = true; @@ -54,3 +52,4 @@ export const MODE_OBJECT_SELECT = 'select'; // LOCAL STORAGE PREFIXES/SUFFIXES export const TOOLBAR_EXPANDED_SUFFIX = 'lhg-toolbar-expanded'; + diff --git a/display.js b/display.js index e193d37..0a58386 100644 --- a/display.js +++ b/display.js @@ -67,10 +67,6 @@ export class Display { 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}) { const ctx = this.ctx; ctx.strokeStyle = style; @@ -139,17 +135,12 @@ export class Display { ctx.resetTransform(); } - computePanning(elapsedTime) { - // Add another entry for the current pointer position + frame(elapsedTime) { const { - pointerHistory, - panTouchStart: start, - panTouchLatest: latest, + touchStart: start, + touchLatest: latest, } = this.sim.pointer ?? {}; - if (pointerHistory?.length) { - const currentPointer = pointerHistory[pointerHistory.length - 1]; - this.sim.pointer.updatePointer(currentPointer); - } + if (start && latest) { // Direct translate 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 const { velocity } = this.sim.panning; // TODO: something with time scale? Panning is too fast. - this.viewOrigin.x -= velocity.x * elapsedTime / 1000; // millisecond conversion? - this.viewOrigin.y -= velocity.y * elapsedTime / 1000; + this.viewOrigin.x += velocity.x * elapsedTime; + this.viewOrigin.y += velocity.y * elapsedTime; } - } + // Clear canvas in preparation for other modules to render this frame + this.fillCanvas(); + } } diff --git a/object.js b/object.js index 301c8b7..6571989 100644 --- a/object.js +++ b/object.js @@ -1,7 +1,6 @@ import { ACCELERATION_VECTOR_ARROWHEAD, ACCELERATION_VECTOR_COLOR, - ACCELERATION_VECTOR_SCALE, ACCELERATION_VECTOR_WIDTH, OFFSCREEN_OBJECT_ARROWHEAD_LENGTH, OFFSCREEN_OBJECT_LINE_SCALE, @@ -12,11 +11,12 @@ import { PATH_TRACES_WIDTH, VELOCITY_VECTOR_ARROWHEAD, VELOCITY_VECTOR_COLOR, - VELOCITY_VECTOR_SCALE, VELOCITY_VECTOR_WIDTH, } from './config.js'; export class MassObject { + sim = undefined; + id = undefined; mass = 0; density = 1; position = {x: undefined, y: undefined}; @@ -28,7 +28,9 @@ export class MassObject { history = []; alive = true; - constructor(x, y) { + constructor(sim, x, y) { + this.sim = sim; + this.id = crypto.randomUUID(); this.position.x = x; this.position.y = y; this.color.r = Math.random() * 256; @@ -86,7 +88,7 @@ export class MassObject { const opacity = dashedTraces ? PATH_TRACES_DASHED_OPACITY : PATH_TRACES_OPACITY; ctx.strokeStyle = PATH_TRACES_COLOR === 'object 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(); let dash = false; for (let i = 0; i < this.history.length; i++) { @@ -142,8 +144,8 @@ export class MassObject { const arrowDirection = Math.atan2(py - cy, px - cx); // Length of arrow based on distance (logarithmic scale) - const distance = Math.sqrt((x - px) ** 2, (y - py) ** 2) * this.scale; - const arrowLength = Math.log(distance) * OFFSCREEN_OBJECT_LINE_SCALE / this.scale; + const distance = Math.sqrt((x - px) ** 2, (y - py) ** 2); + const arrowLength = Math.log(distance) * OFFSCREEN_OBJECT_LINE_SCALE / this.sim.display.scale; const startAx = px - arrowLength * Math.cos(arrowDirection); const startAy = py - arrowLength * Math.sin(arrowDirection); sim.display.drawArrow(startAx, startAy, px, py, { @@ -165,9 +167,19 @@ export class MassObject { // Draw arrow for the velocity if (sim.getOption('display.velocity')) { - const speed = Math.sqrt(vx ** 2 + vy ** 2); - const endVx = x + VELOCITY_VECTOR_SCALE * vx / speed * Math.log(speed); - const endVy = y + VELOCITY_VECTOR_SCALE * vy / speed * Math.log(speed); + // If this object is being dragged by the user, + // show the pointer velocity instead of object velocity + 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' ? `rgb(${r}, ${g}, ${b})` : VELOCITY_VECTOR_COLOR; sim.display.drawArrow(x, y, endVx, endVy, { @@ -181,11 +193,13 @@ export class MassObject { // Draw arrow for acceleration if (sim.getOption('display.acceleration')) { + const vecScale = this.sim.getOption('param.accelerationScale'); const accelerationMagnitude = Math.sqrt(acceleration.x ** 2 + acceleration.y ** 2); - const endAx = x + ACCELERATION_VECTOR_SCALE * acceleration.x / - accelerationMagnitude * Math.log(accelerationMagnitude); - const endAy = y + ACCELERATION_VECTOR_SCALE * acceleration.y / - accelerationMagnitude * Math.log(accelerationMagnitude); + const arrowDirection = Math.atan2(acceleration.y, acceleration.x); + // Prevent negative numbers by adding e + const arrowLength = Math.log(accelerationMagnitude + 3) * vecScale / this.sim.display.scale; + const endAx = x + arrowLength * Math.cos(arrowDirection); + const endAy = y + arrowLength * Math.sin(arrowDirection); const style = ACCELERATION_VECTOR_COLOR === 'object color' ? `rgb(${r}, ${g}, ${b})` : ACCELERATION_VECTOR_COLOR; sim.display.drawArrow(x, y, endAx, endAy, { diff --git a/options.js b/options.js index f21117d..aa2263d 100644 --- a/options.js +++ b/options.js @@ -6,6 +6,7 @@ export class Options { sim = undefined; options = undefined; values = {}; + undefinedObj = {_undefined: true}; getStorageKey(path) { return `${path}:options`; @@ -28,16 +29,24 @@ export class Options { if (value === undefined) { value = defaultValue; } + console.log('initializing option', {path, defaultValue, value}); this.values[path] = value; } } } toStored(value) { + if (value === undefined) { + return JSON.stringify(this.undefinedObj); + } return JSON.stringify(value); } + // value: string fromStored(value) { + if (value === JSON.stringify(this.undefinedObj)) { + return undefined; + } return JSON.parse(value); } diff --git a/overlay.js b/overlay.js index c990623..6aed4e7 100644 --- a/overlay.js +++ b/overlay.js @@ -12,7 +12,7 @@ export class Overlay { infoBox.classList.add(OVERLAY_INFO_BOX_CLASSNAME); } - renderInfo() { + frame() { this.infoBox.innerHTML = ''; const table = document.createElement('table'); for (let [k, v] of Object.entries(this.sim.info)) { diff --git a/pointer.js b/pointer.js index d9c10bc..77b23a7 100644 --- a/pointer.js +++ b/pointer.js @@ -13,8 +13,8 @@ export class Pointer { sim = undefined; pointerHistory = []; - panTouchStart = undefined; // {x: undefined, y: undefined, t: undefined}; - panTouchLatest = undefined; // {x: undefined, y: undefined, t: undefined}; + touchStart = undefined; // {x: undefined, y: undefined, t: undefined}; + touchLatest = undefined; // {x: undefined, y: undefined, t: undefined}; suppressClick = false; constructor(sim) { @@ -61,7 +61,7 @@ export class Pointer { getPointerVelocity(points = POINTER_HISTORY_SIZE) { // Average over pointer history 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); const start = this.pointerHistory[this.pointerHistory.length - points]; @@ -79,7 +79,7 @@ export class Pointer { } updatePointer({x, y}) { - const t = document.timeline.currentTime; + const t = this.sim.rawTime; while (this.pointerHistory.length >= POINTER_HISTORY_SIZE) { this.pointerHistory.shift(); } @@ -87,7 +87,7 @@ export class Pointer { this.pointerHistory.push({t, x, y, v}); } - get latestPointerVelocity() { + get latestVelocity() { const latestPointer = this.pointerHistory[this.pointerHistory.length - 1]; return latestPointer?.v; } @@ -98,16 +98,21 @@ export class Pointer { if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) { 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)) { - this.panTouchStart = { + this.touchStart = { x: clientX, y: clientY, - t: document.timeline.currentTime, + t: this.sim.rawTime, 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)) { // TODO: Start a selection box @@ -117,16 +122,15 @@ export class Pointer { handlePointerUp({x: clientX, y: clientY}) { if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) { 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)) { // Set panning velocity - if (this.panTouchStart && this.panTouchLatest) { - const dt = (this.panTouchLatest.t - this.panTouchStart.t) / 1000; - if (!dt) { + if (this.touchStart && this.touchLatest) { + if (!this.touchLatest.dt) { this.sim.panning = undefined; } else { - const v = {...this.latestPointerVelocity}; + const v = {...this.latestVelocity}; // Convert pointer velocity to simulation scale v.x /= 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}); 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); - this.sim.objects.handlePointerMove({x, y, vx, vy}); + this.sim.system.handlePointerMove({x, y}); } else if (this.sim.isCurrentMode(MODE_PAN_VIEW)) { - if (this.panTouchStart) { + if (this.touchStart) { // Event loop should be able to read - this.panTouchLatest = { + this.touchLatest = { x: clientX, y: clientY, 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); + } + } } diff --git a/sim-options.js b/sim-options.js index 64b3c49..9640d7f 100644 --- a/sim-options.js +++ b/sim-options.js @@ -4,10 +4,12 @@ export const simOptions = { selection: ['Pause While Selecting', 'boolean', true], }, display: { - velocity: ['Velocity Vector', 'boolean', true], - acceleration: ['Accel Vector', 'boolean', true], traces: ['Path Trace', 'boolean', true], dashedTraces: ['Dashed', 'boolean', false, {tall: true}], + velocity: ['Velocity Vector', 'boolean', true], + acceleration: ['Accel Vector', 'boolean', true], + velocityScale: ['Velocity
Vec Scale', 'number', 20], + accelerationScale: ['Accel
Vec Scale', 'number', 20], }, collision: { merge: ['Merge Masses
on Collision', 'boolean', true, {wide: true}], @@ -20,8 +22,9 @@ export const simOptions = { }, debug: { objectsInfo: ['Objects Info', 'boolean', false], + aliveObjects: ['Alive Only', 'boolean', false], cursorInfo: ['Cursor Info', 'boolean', false], - frameRate: ['Frame Rate', 'boolean', false, {wide: true}], + frameRate: ['Frame Rate', 'boolean', false], currentMode: ['Current Mode', 'boolean', false], panningInfo: ['Panning Info', 'boolean', false], }, diff --git a/simulator.js b/simulator.js index 4c8d926..f480e2e 100644 --- a/simulator.js +++ b/simulator.js @@ -5,7 +5,7 @@ import { SCALE_POWER_MIN, } from './config.js'; import { Display } from './display.js'; -import { Objects } from './objects.js'; +import { System } from './system.js'; import { Options } from './options.js'; import { simOptions } from './sim-options.js'; import { initializeTools } from './sim-tools.js'; @@ -14,12 +14,13 @@ export class Sim { info = {}; rawTime = undefined; time = undefined; + timeScale = undefined; nextZoom = undefined; playing = true; recentFrames = []; frameRate = 0; - objects = undefined; + system = undefined; display = undefined; overlay = undefined; pointer = undefined; @@ -31,6 +32,7 @@ export class Sim { getCurrentMode = () => undefined; setCurrentMode = () => undefined; getOption = () => undefined; + setOption = () => undefined; onModeEnter = () => undefined; onModeLeave = () => undefined; @@ -41,8 +43,8 @@ export class Sim { const oldest = rfs[0]; const newest = rfs[rfs.length - 1]; const count = rfs.length; - const duration = (newest - oldest) / 1000; // ms to s - this.frameRate = count / duration; + const duration = (newest - oldest); + this.frameRate = 1000 * count / duration; // Converting from ms to s if (duration >= FRAMERATE_SAMPLE_DURATION) { rfs.shift(); } @@ -55,7 +57,7 @@ export class Sim { this.options = new Options(this, simOptions); this.display = new Display(this); - this.objects = new Objects(this); + this.system = new System(this); initializeTools(this); @@ -90,10 +92,7 @@ export class Sim { if (this.playing && velocity) { this.panning = { - velocity: { - x: -velocity.x, - y: -velocity.y, - } + velocity: { ...velocity } }; } @@ -129,11 +128,9 @@ export class Sim { // Main loop loop(currentTime) { this.markFrame(currentTime); - const timeScale = this.getOption('param.timeScale'); + this.timeScale = this.getOption('param.timeScale'); - // elapsedTime in milliseconds - // rawTime in milliseconds - const elapsedTime = (currentTime - this.rawTime) / timeScale; + const elapsedTime = (currentTime - this.rawTime) * this.timeScale; this.rawTime = currentTime; if (this.playing) { @@ -156,19 +153,18 @@ export class Sim { if (this.getOption('debug.panningInfo')) { const {x, y} = this.panning?.velocity ?? {}; 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['Net Angular Momentum'] = this.objects.computeSystemAngularMomentum().toPrecision(6); + this.info['Net Angular Momentum'] = this.system.computeSystemAngularMomentum().toPrecision(6); } - this.objects.computeFrame(elapsedTime); - this.overlay.renderInfo(); - // this.display.computePanning(elapsedTime); - this.display.fillCanvas(); - this.display.drawObjects(); + this.display.frame(elapsedTime); + this.system.frame(elapsedTime); + this.overlay.frame(); for (const group in this.toolbarGroups) { this.toolbarGroups[group].frame(); } + requestAnimationFrame(t => this.loop(t)); } } diff --git a/objects.js b/system.js similarity index 83% rename from objects.js rename to system.js index 42ec672..aae419a 100644 --- a/objects.js +++ b/system.js @@ -1,10 +1,11 @@ import { MassObject } from './object.js'; import { ZOOM_TO_FIT_PADDING } from './config.js'; -export class Objects { +export class System { objects = []; creatingObject = undefined; selectedObject = undefined; + selectObjectStart = undefined; paused = false; panVelocityPaused = undefined; @@ -35,8 +36,9 @@ export class Objects { // Create an object with mass that grows as pointer is held down createObject(x, y) { const idx = this.objects.length; - const obj = new MassObject(x, y, idx); + const obj = new MassObject(this.sim, x, y); this.creatingObject = idx; + this.selectedObjectStart = {x, y, pointer: {x, y}}; this.objects.push(obj); // Pause the simulation during mass creation; this avoids some complex local dynamics if (this.sim.getOption('pauseDuring.creation')) { @@ -59,8 +61,10 @@ export class Objects { return this.objects[i]; } - selectObject(i) { + selectObject(i, pointer) { this.selectedObject = i; + const {x, y} = this.object(i).position; + this.selectedObjectStart = {x, y, pointer}; if (this.sim.getOption('pauseDuring.selection')) { this.pause(); } @@ -68,6 +72,7 @@ export class Objects { deselect() { this.selectedObject = undefined; + this.selectedObjectStart = undefined; this.resume(); } @@ -107,6 +112,7 @@ export class Objects { objectAtLocation(x, y) { let idx = undefined; + this.selectedObjectStart = undefined; this.forEachObject((obj, i) => { // 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); @@ -123,7 +129,7 @@ export class Objects { const touchingObject = this.objectAtLocation(x, y); if (touchingObject !== undefined) { - this.selectObject(touchingObject); + this.selectObject(touchingObject, {x, y}); } else { // Otherwise, create a new object this.createObject(x, y); @@ -131,24 +137,31 @@ export class Objects { } handlePointerUp() { + const obj = this.getSelectedOrCreating(); + if (obj === undefined) return; this.doneCreatingObject(); 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, - // update the position and velocity of the object // update the position using the pointer motion but the velocity using the pointer velocity const obj = this.getSelectedOrCreating(); if (obj === undefined) return; - if (this.sim.panning?.velocity) { - vx += this.sim.panning.velocity.x; - vy += this.sim.panning.velocity.y; - } - obj.position.x = x; - obj.position.y = y; - obj.velocity.x = vx; - obj.velocity.y = vy; + const start = this.selectedObjectStart; + obj.position.x = start.x + (x - start.pointer.x); + obj.position.y = start.y + (y - start.pointer.y); + obj.velocity.x = 0; + obj.velocity.y = 0; } // cb: (obj, idx) => {} @@ -163,6 +176,10 @@ export class Objects { } } + drawObjects() { + this.forEachObject(obj => obj.drawObject(this.sim), {alive: null}); + } + // cb: (acc, obj, idx) => {} reduce(cb, initial, opts) { let acc = initial; @@ -201,9 +218,7 @@ export class Objects { } // elapsedTime is given in milliseconds - computeFrame(elapsedTime) { - // convert elapsed time to seconds - elapsedTime /= 1000; + frame(elapsedTime) { // If we're creating an object, increment its mass // with the mass creation rate accelerating over time @@ -211,8 +226,7 @@ export class Objects { if (this.creatingObject !== undefined) { const obj = this.objects[this.creatingObject]; - // Putting in a somewhat arbitrary scaling factor here - let massCreationRate = this.sim.getOption('param.massCreationRate') / 1000; + let massCreationRate = this.sim.getOption('param.massCreationRate'); // Mass creation rate acceleration if (this.sim.getOption('param.massAcceleration')) { massCreationRate *= obj.age; @@ -224,8 +238,6 @@ export class Objects { this.computeForces(); if (this.sim.playing) { - // TODO: If creating/selected object, clamp its position to the cursor - // Predict positions (Velocity verlet method) this.forEachObject(obj => { obj.currentAcceleration = {...obj.acceleration}; @@ -307,26 +319,39 @@ export class Objects { } // 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')) { + const aliveOnly = this.sim.getOption('debug.aliveObjects'); this.forEachObject((obj, i) => { 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 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}`] = [ - `${obj.position.x.toPrecision(6)}, `, - `${obj.position.y.toPrecision(6)}, `, - `${obj.mass.toPrecision(6)} kg, `, + `  `, + `${obj.position.x.toPrecision(4)}, `, + `${obj.position.y.toPrecision(4)}, `, + `${obj.mass.toPrecision(4)} kg, `, `${speed.toPrecision(2)} m/s, ${direction.toPrecision(2)}°`, + `${accel.toPrecision(2)} m/s2, ${accelDir.toPrecision(2)}°`, `Alive: ${obj.alive}`, ]; - }, { alive: null }); + }, { alive: aliveOnly || null }); } + + // Render the objects + this.drawObjects(); } computeSystemCenter() { // Determine center of mass const { totalMass, count, totalMassLocation } = - this.sim.objects.reduce((acc, obj) => ({ + this.reduce((acc, obj) => ({ count: acc.count + 1, totalMass: acc.totalMass + obj.mass, totalMassLocation: { @@ -345,7 +370,7 @@ export class Objects { } : {x: 0, y: 0}; // 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, y: acc.y + obj.mass * obj.velocity.y, }), { x: 0, y: 0 }); diff --git a/tool/options.js b/tool/options.js index 7a18e7a..45abe92 100644 --- a/tool/options.js +++ b/tool/options.js @@ -43,18 +43,11 @@ export class OptionsTool extends Tool { const value = this.sim.getOption(path); button.style.opacity = value ? '100%' : '50%'; this.sim.onOptionSet(path, value => { - console.log('option set cb', path, value); 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', () => { const value = this.sim.getOption(path); - this.setOption(path, !value); + this.sim.setOption(path, !value); }); return button; } diff --git a/tool/utility.js b/tool/utility.js index 2e92b2c..a5c3f70 100644 --- a/tool/utility.js +++ b/tool/utility.js @@ -81,14 +81,14 @@ export class UtilityTool extends Tool { zeroVelocity.addEventListener('click', () => { // Determine center of mass and average momentum - const { totalMass, netMomentum } = this.sim.objects.computeSystemCenter(); + const { totalMass, netMomentum } = this.sim.system.computeSystemCenter(); const netVelocity = { x: netMomentum.x / totalMass, y: netMomentum.y / totalMass, }; // Apply offset to all object velocities - this.sim.objects.forEachObject(obj => { + this.sim.system.forEachObject(obj => { obj.velocity.x -= netVelocity.x; obj.velocity.y -= netVelocity.y; }); @@ -99,14 +99,14 @@ export class UtilityTool extends Tool { clearTraces.addEventListener('click', () => { // Obliterate object histories - this.sim.objects.forEachObject(obj => { + this.sim.system.forEachObject(obj => { obj.history = []; }, {alive: null}); }); zoomAll.addEventListener('click', () => { // 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 y = (box.start.y + box.end.y) / 2; 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}; let totalMass = 0; let count = 0; - this.sim.objects.forEachObject(obj => { + this.sim.system.forEachObject(obj => { count++; netMomentum.x += obj.mass * obj.velocity.x; netMomentum.y += obj.mass * obj.velocity.y;