diff --git a/Readme.md b/Readme.md index 21efbdf..6bd84e8 100644 --- a/Readme.md +++ b/Readme.md @@ -8,6 +8,7 @@ Uses `npm` for `eslint`. TODO ---- +- [ ] Parameter Slider - [ ] Selection Box - [ ] Object List - [ ] Object Detail @@ -17,11 +18,17 @@ TODO - [ ] Zoom Easing - [ ] 2-touch Pan & Zoom - [ ] Multi-touch Mass Create +- [ ] Camera Rotation + - [ ] 2-touch Rotation + - [ ] Spinning Frame - [ ] Undo feature: - [ ] Undo "Clear Traces" Action - [ ] Undo "Reset +- [ ] Time Control: Reverse Time - [ ] Save to LocalStorage - [ ] Lossy Rescaling To Widen Zoom (Handling overflow/underflow) - [ ] Track farthest reaches, min/max in each dimension (x, y) - [ ] Enabling Zoom to Fit Traces -- [ ] Tool: Zero Angular Momentum +- [x] Compute Net Angular Momentum +- [ ] Display Net Angular Momentum + diff --git a/config.js b/config.js index 63c5a4d..340a08e 100644 --- a/config.js +++ b/config.js @@ -1,9 +1,10 @@ // DISPLAY export const DISPLAY_OBJECTS_INFO = false; -export const DISPLAY_CURSOR_INFO = false; +export const DISPLAY_CURSOR_INFO = true; export const DISPLAY_CANVAS_SIZE = false; export const DISPLAY_CURRENT_SCALE = false; export const DISPLAY_CURRENT_MODE = false; +export const DISPLAY_PANNING_INFO = true; // VELOCITY export const VELOCITY_VECTOR_SCALE = 8E0; @@ -43,6 +44,7 @@ export const TOOL_INFO_CLASSNAME = 'lhg-tool-info'; export const TOOLBAR_CLASSNAME = 'lhg-toolbar'; export const TOOLBAR_HEADER_CLASSNAME = 'lhg-toolbar-header'; export const WIDE_CLASSNAME = 'lhg-wide'; +export const OVERLAY_INFO_BOX_CLASSNAME = 'lhg-overlay-info-box'; // EVENT NAMES export const EVENT_MODE_LEAVE = 'lhg-mode-leave'; diff --git a/objects.js b/objects.js index 8ad08cd..f4fed35 100644 --- a/objects.js +++ b/objects.js @@ -313,4 +313,46 @@ export class Objects { }); } } + + computeSystemCenter() { + // Determine center of mass + const { totalMass, count, totalMassLocation } = + this.sim.objects.reduce((acc, obj) => ({ + count: acc.count + 1, + totalMass: acc.totalMass + obj.mass, + totalMassLocation: { + x: acc.totalMassLocation.x + obj.position.x * obj.mass, + y: acc.totalMassLocation.y + obj.position.y * obj.mass, + }, + }), { + totalMassLocation: {x: 0, y: 0}, + totalMass: 0, + count: 0, + }); + + if (!count) return; + + const centerOfMass = { + x: totalMassLocation.x / totalMass, + y: totalMassLocation.y / totalMass, + }; + + return { totalMass, count, totalMassLocation, centerOfMass }; + } + + computeSystemAngularMomentum(centerOfMass) { + return this.reduce((acc, obj) => { + // Angular momentum for each object is m * s / d + // where d is the distance of the object from the global center of mass + // and s is the magnitude of the cross product of v and r + const r = { + x: obj.position.x - centerOfMass.x, + y: obj.position.y - centerOfMass.y, + }; + const v = obj.velocity; + const s = v.x * r.y - v.y * r.x; + const d = Math.sqrt(r.x ** 2 + r.y ** 2); + return acc + obj.mass * s / d; + }, 0); + } } diff --git a/overlay.js b/overlay.js index bd48cb0..c990623 100644 --- a/overlay.js +++ b/overlay.js @@ -1,3 +1,5 @@ +import { OVERLAY_INFO_BOX_CLASSNAME } from './config.js'; + export class Overlay { sim = undefined; constructor(sim) { @@ -7,12 +9,7 @@ export class Overlay { const infoBox = document.createElement('div'); this.sim.div.appendChild(infoBox); this.infoBox = infoBox; - infoBox.style.position = 'relative'; - infoBox.style.display = 'inline-block'; - infoBox.style.top = 0; - infoBox.style.left = '14em'; - infoBox.width = 'fit-content'; - infoBox.style.zIndex = 1; + infoBox.classList.add(OVERLAY_INFO_BOX_CLASSNAME); } renderInfo() { diff --git a/pointer.js b/pointer.js index 48a1652..2c77de4 100644 --- a/pointer.js +++ b/pointer.js @@ -26,7 +26,7 @@ export class Pointer { el.addEventListener('pointermove', e => { if (DISPLAY_CURSOR_INFO) { - this.sim.info['pointermove'] = [`${e.clientX}, `, `${e.clientY}`]; + this.sim.info['pointermove'] = [`${e.clientX.toPrecision(6)}, `, `${e.clientY.toPrecision(6)}`]; } this.handlePointerMove({x: e.clientX, y: e.clientY}); }); diff --git a/simulator.js b/simulator.js index 9ad54ea..63230b3 100644 --- a/simulator.js +++ b/simulator.js @@ -1,6 +1,7 @@ import { DISPLAY_CURRENT_MODE, DISPLAY_CURRENT_SCALE, + DISPLAY_PANNING_INFO, EVENT_ZOOM, SCALE_POWER_MAX, SCALE_POWER_MIN, @@ -151,7 +152,9 @@ export class Sim { const timeScale = this.getOption('param.timeScale'); const elapsedTime = (currentTime - this.rawTime) * timeScale; this.rawTime = currentTime; - this.time += elapsedTime; + if (this.playing) { + this.time += elapsedTime; + } if (DISPLAY_CURRENT_MODE) { this.info['Mode'] = this.getCurrentMode(); @@ -166,6 +169,11 @@ export class Sim { this.info['Scale'] = this.getScaleDisplay(); } + if (DISPLAY_PANNING_INFO) { + const {x, y} = this.panning?.velocity ?? {}; + this.info['Panning'] = [`${x?.toPrecision(6)}, `, y?.toPrecision(6)]; + } + this.display.computePanning(elapsedTime); this.objects.computeFrame(elapsedTime); this.overlay.renderInfo(); diff --git a/style.css b/style.css index 245089b..bcd5887 100644 --- a/style.css +++ b/style.css @@ -31,8 +31,8 @@ div.lhg-toolbar { position: relative; z-index: 2; width: fit-content; - margin: 0.5EM; - border-radius: 0.5EM; + margin: 0.5em; + border-radius: 0.5em; border-width: 1px; border-color: #282; border-style: solid; @@ -42,9 +42,9 @@ div.lhg-tool { position: relative; top: 0; left: 0; - width: 12EM; - /* padding: 0.5EM; */ - margin: 0.5EM; + width: 12em; + /* padding: 0.5em; */ + margin: 0.5em; text-align: middle; } @@ -59,14 +59,14 @@ div.lhg-tool button, div.lhg-tool input { width: 6em; background-color: #333; color: #5f5; - border-radius: 0.5EM; + border-radius: 0.5em; border-color: #000; border-width: 2px; border-style: solid; - padding-top: 0.5EM; - padding-bottom: 0.5EM; - padding-left: 0EM; - padding-right: 0EM; + padding-top: 0.5em; + padding-bottom: 0.5em; + padding-left: 0em; + padding-right: 0em; text-align: center; margin-left: 0; margin-right: 0; @@ -74,7 +74,7 @@ div.lhg-tool button, div.lhg-tool input { } div.lhg-tool input { - width: 6EM; + width: 6em; } div.lhg-tool button:hover { @@ -107,3 +107,10 @@ div.lhg-tool .lhg-wide { width: 12em; } +div.lhg-overlay-info-box { + position: absolute; + top: 0px; + left: 14em; + width: fit-content; + z-index: 1; +} diff --git a/tool/zoom.js b/tool/zoom.js index 5177384..03c9fd0 100644 --- a/tool/zoom.js +++ b/tool/zoom.js @@ -92,50 +92,17 @@ export class Zoom extends Tool { zeroVelocity.addEventListener('click', () => { // TODO: Zero net angular momentum // Determine center of mass - const { totalMass, count, totalMassLocation } = - this.sim.objects.reduce((acc, obj) => ({ - count: acc.count + 1, - totalMass: acc.totalMass + obj.mass, - totalMassLocation: { - x: acc.totalMassLocation.x + obj.position.x * obj.mass, - y: acc.totalMassLocation.y + obj.position.y * obj.mass, - }, - }), { - totalMassLocation: {x: 0, y: 0}, - totalMass: 0, - count: 0, - }); - - if (!count) return; - - const centerOfMass = { - x: totalMassLocation.x / totalMass, - y: totalMassLocation.y / totalMass, - }; - - console.log({totalMass, count, totalMassLocation, centerOfMass}); + const { totalMass, centerOfMass } = + this.sim.objects.computeSystemCenter(); // Determine total angular momentum - const netAngularMomentum = this.sim.objects.reduce((acc, obj, idx) => { - // Angular momentum for each object is m * s / d - // where d is the distance of the object from the global center of mass - // and s is the magnitude of the cross product of v and r - const r = { - x: obj.position.x - centerOfMass.x, - y: obj.position.y - centerOfMass.y, - }; - const v = obj.velocity; - const s = v.x * r.y - v.y * r.x; - const d = Math.sqrt(r.x ** 2 + r.y ** 2); - console.log(`obj ${idx} s`, s, 'd', d); - return acc + obj.mass * s / d; - }, 0); - + const netAngularMomentum = this.sim.objects + .computeSystemAngularMomentum(centerOfMass); console.log('net angular momentum', netAngularMomentum); const netAngularVelocity = netAngularMomentum / totalMass; console.log('net angular velocity', netAngularVelocity); - // TODO: Apply rotation... + // TODO: Camera rotation // Determine average momentum const netMomentum = this.sim.objects.reduce((acc, obj) => ({