diff --git a/Readme.md b/Readme.md index 1ff4a50..83466e1 100644 --- a/Readme.md +++ b/Readme.md @@ -8,8 +8,7 @@ Uses `npm` for `eslint`. Screenshots ----------- -![A small body orbiting a pair of larger ones](./gravity-simulator-2.png "Gravity Simulator Screenshot 2") -![A body orbiting a pair, all three of similar size](./gravity-simulator-3.png "Gravity Simulator Screenshot 3") +![A binary pair orbiting a larger partner](./gravity-simulator-4.png "Gravity Simulator Screenshot 4") TODO ---- @@ -31,10 +30,7 @@ TODO - [ ] Undo "Clear Traces" Action - [ ] Undo "Reset - [ ] Time Control: Reverse Time -- [x] Save to LocalStorage - [ ] Lossy Rescaling To Widen Zoom (Handling overflow/underflow) - [ ] Track farthest reaches, min/max in each dimension (x, y) -- [x] Compute Net Angular Momentum -- [ ] Display Net Angular Momentum - [ ] Calculate Work as FxD as measure of energy flux - [ ] Option to automatically slow time when energy flux is greater diff --git a/gravity-simulator-4.png b/gravity-simulator-4.png new file mode 100644 index 0000000..5aa1d53 Binary files /dev/null and b/gravity-simulator-4.png differ diff --git a/object.js b/object.js index b958804..ef49ff6 100644 --- a/object.js +++ b/object.js @@ -180,22 +180,19 @@ export class MassObject { let velocity = {x: vx, y: vy}; if (isSelected) { const pointerV = this.sim.pointer.latestVelocity; - const scale = this.sim.display.scale; // const panning = this.sim.panning?.velocity ?? {x: 0, y: 0}; // velocity.x = vx + (pointerV.x + panning.x) * scale; // velocity.y = vy + (pointerV.y + panning.y) * scale; - velocity.x = vx + pointerV.x * scale; - velocity.y = vy + pointerV.y * scale; + velocity.x = vx + pointerV.x / this.sim.timeScale; + velocity.y = vy + pointerV.y / this.sim.timeScale; } - const speed = Math.sqrt(velocity.x ** 2, velocity.y ** 2); + const speed = Math.sqrt(velocity.x ** 2, velocity.y ** 2) / this.sim.display.scale; const arrowDirection = Math.atan2(velocity.y, velocity.x); // Prevent negative numbers by adding 1 - const arrowLength = Math.log(speed + 1) * vecScale; + // TODO: Make logarithmic vector length scale optional + const arrowLength = Math.log10(speed + 1) * vecScale + radius; const endVx = x + arrowLength * Math.cos(arrowDirection); const endVy = y + arrowLength * Math.sin(arrowDirection); - if (sim.getOption('debug.panningInfo')) { - console.log('velocity', {vecScale, isSelected, velocity, speed, arrowDirection, arrowLength, endVx, endVy}); - } const style = VELOCITY_VECTOR_COLOR === 'object color' ? `rgb(${r}, ${g}, ${b})` : VELOCITY_VECTOR_COLOR; sim.display.drawArrow(x, y, endVx, endVy, { @@ -209,11 +206,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 vecScale = this.sim.getOption('display.accelerationScale'); + const accelerationMagnitude = Math.sqrt(acceleration.x ** 2 + acceleration.y ** 2) / + this.sim.display.scale; const arrowDirection = Math.atan2(acceleration.y, acceleration.x); - // Prevent negative numbers by adding e - const arrowLength = Math.log(accelerationMagnitude + 1) * vecScale / this.sim.display.scale; + // Prevent negative numbers by adding 1 + const arrowLength = Math.log10(accelerationMagnitude + 1) * vecScale + radius; + //const arrowLength = accelerationMagnitude * vecScale; const endAx = x + arrowLength * Math.cos(arrowDirection); const endAy = y + arrowLength * Math.sin(arrowDirection); const style = ACCELERATION_VECTOR_COLOR === 'object color' ? diff --git a/sim-options.js b/sim-options.js index dd9dd07..446a312 100644 --- a/sim-options.js +++ b/sim-options.js @@ -4,12 +4,12 @@ export const simOptions = { selection: ['Pause While Selecting', 'boolean', true], }, display: { - traces: ['Path Trace', 'boolean', true], + traces: ['Path Traces', '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], + velocity: ['Velocity Vectors', 'boolean', true], + acceleration: ['Accel Vectors', 'boolean', true], + velocityScale: ['Velocity
Vec Scale', 'number', 80], + accelerationScale: ['Accel
Vec Scale', 'number', 800], }, collision: { merge: ['Merge Masses
on Collision', 'boolean', true, {wide: true}], @@ -18,7 +18,6 @@ export const simOptions = { gravity: ['Gravity', 'number', 1], timeScale: ['Time Scale', 'number', 0.1], massCreationRate: ['Mass Creation Rate', 'number', 1], - massAcceleration: ['Mass Rate Accel', 'boolean', false, {wide: true}], }, debug: { objectsInfo: ['Objects Info', 'boolean', false], diff --git a/system.js b/system.js index cd57812..c88638e 100644 --- a/system.js +++ b/system.js @@ -157,8 +157,8 @@ export class System { // 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; - obj.velocity.y = pointer.y / this.sim.display.scale; + 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; @@ -239,12 +239,7 @@ export class System { if (this.creatingObject !== undefined) { const obj = this.objects[this.creatingObject]; - let massCreationRate = this.sim.getOption('param.massCreationRate'); - // Mass creation rate acceleration - if (this.sim.getOption('param.massAcceleration')) { - // TODO: Separate parameter for mass creation acceleration rate - massCreationRate *= obj.rawAge; - } + let massCreationRate = this.sim.getOption('param.massCreationRate') / this.sim.display.scale; // Keep consistent time scale obj.mass += massCreationRate * elapsedTime / this.sim.timeScale; } diff --git a/tool/utility.js b/tool/utility.js index 46dccd6..ae5a5d3 100644 --- a/tool/utility.js +++ b/tool/utility.js @@ -39,7 +39,6 @@ export class UtilityTool extends Tool { constructor(container) { super(container); - const zeroVelocity = document.createElement('button'); const clearTraces = document.createElement('button'); const currentTime = document.createElement('button'); const clearDebug = document.createElement('button'); @@ -47,39 +46,18 @@ export class UtilityTool extends Tool { this.currentTimeEl = currentTime; this.div.appendChild(currentTime); - this.div.appendChild(zeroVelocity); this.div.appendChild(clearTraces); this.div.appendChild(clearDebug); - zeroVelocity.classList.add(WIDE_CLASSNAME); clearTraces.classList.add(WIDE_CLASSNAME); currentTime.classList.add(TOOL_INFO_CLASSNAME); currentTime.classList.add(WIDE_CLASSNAME); clearDebug.classList.add(WIDE_CLASSNAME); - zeroVelocity.innerHTML = 'Zero Momentum'; clearTraces.innerHTML = 'Clear Traces'; currentTime.innerHTML = this.timeText; clearDebug.innerHTML = 'Clear Debug'; - zeroVelocity.addEventListener('click', () => { - // Determine center of mass and average momentum - 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.system.forEachObject(obj => { - obj.velocity.x -= netVelocity.x; - obj.velocity.y -= netVelocity.y; - }); - - // Cancel panning - this.sim.panning = undefined; - }); - clearTraces.addEventListener('click', () => { // Obliterate object histories this.sim.system.forEachObject(obj => { diff --git a/tool/zoom.js b/tool/zoom.js index c4b441b..0175640 100644 --- a/tool/zoom.js +++ b/tool/zoom.js @@ -3,7 +3,6 @@ import { ZOOM_IN_FACTOR, ZOOM_OUT_FACTOR, WIDE_CLASSNAME, - TALL_CLASSNAME, TOOL_INFO_CLASSNAME, } from '../config.js'; @@ -19,21 +18,22 @@ export class Zoom extends Tool { const zoomOut = document.createElement('button'); const zoomIn = document.createElement('button'); const zoomAll = document.createElement('button'); + const zeroVelocity = document.createElement('button'); this.div.appendChild(currentScale); this.div.appendChild(zoomOut); this.div.appendChild(zoomIn); this.div.appendChild(zoomAll); + this.div.appendChild(zeroVelocity); currentScale.classList.add(WIDE_CLASSNAME); currentScale.classList.add(TOOL_INFO_CLASSNAME); - zoomAll.classList.add(WIDE_CLASSNAME); - zoomAll.classList.add(TALL_CLASSNAME); currentScale.innerHTML = this.displayScaleText; zoomOut.innerHTML = 'Zoom
Out'; zoomIn.innerHTML = 'Zoom
In'; zoomAll.innerHTML = 'Zoom to Fit'; + zeroVelocity.innerHTML = 'Zero Momentum'; this.sim.onZoom(() => { currentScale.innerHTML = this.displayScaleText; @@ -60,7 +60,7 @@ export class Zoom extends Tool { const y = (box.start.y + box.end.y) / 2; const widthRatio = Math.abs(box.start.x - box.end.x) / this.sim.display.width; const heightRatio = Math.abs(box.start.y - box.end.y) / this.sim.display.height; - const ratio = Math.max(widthRatio, heightRatio) * 2.5; + const ratio = Math.max(widthRatio, heightRatio) * 4; const factor = Math.ceil(Math.log2(1 / ratio)); // Determine average momentum and set panning velocity to match @@ -71,5 +71,24 @@ export class Zoom extends Tool { }; this.sim.scheduleZoom({x, y}, factor, netVelocity) }); + + zeroVelocity.addEventListener('click', () => { + // Determine center of mass and average momentum + 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.system.forEachObject(obj => { + obj.velocity.x -= netVelocity.x; + obj.velocity.y -= netVelocity.y; + }); + + // Cancel panning + this.sim.panning = undefined; + }); + } }