Compare commits

..

No commits in common. "d869bc5a6fa8d821189956b986bbbfc06a58e095" and "dcc630a2e663a1220670acfa49df161aba919f6d" have entirely different histories.

9 changed files with 59 additions and 94 deletions

View File

@ -5,15 +5,9 @@ Runs in a browser. Just serve `index.html` and associated `css` and `js`.
Uses `npm` for `eslint`.
Screenshots
-----------
![Simulation of a small body orbiting a pair of larger ones](./gravity-simulator-2.png "Gravity Simulator Screenshot 2")
TODO
----
- [ ] Parameter Slider
- [ ] Selection Box
- [ ] Object List
- [ ] Object Detail
@ -23,17 +17,11 @@ 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
- [x] Compute Net Angular Momentum
- [ ] Display Net Angular Momentum
- [ ] Tool: Zero Angular Momentum

View File

@ -1,10 +1,9 @@
// DISPLAY
export const DISPLAY_OBJECTS_INFO = false;
export const DISPLAY_CURSOR_INFO = true;
export const DISPLAY_CURSOR_INFO = false;
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;
@ -44,7 +43,6 @@ 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';

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 KiB

View File

@ -313,46 +313,4 @@ 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);
}
}

View File

@ -1,5 +1,3 @@
import { OVERLAY_INFO_BOX_CLASSNAME } from './config.js';
export class Overlay {
sim = undefined;
constructor(sim) {
@ -9,7 +7,12 @@ export class Overlay {
const infoBox = document.createElement('div');
this.sim.div.appendChild(infoBox);
this.infoBox = infoBox;
infoBox.classList.add(OVERLAY_INFO_BOX_CLASSNAME);
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;
}
renderInfo() {

View File

@ -26,7 +26,7 @@ export class Pointer {
el.addEventListener('pointermove', e => {
if (DISPLAY_CURSOR_INFO) {
this.sim.info['pointermove'] = [`${e.clientX.toPrecision(6)}, `, `${e.clientY.toPrecision(6)}`];
this.sim.info['pointermove'] = [`${e.clientX}, `, `${e.clientY}`];
}
this.handlePointerMove({x: e.clientX, y: e.clientY});
});

View File

@ -1,7 +1,6 @@
import {
DISPLAY_CURRENT_MODE,
DISPLAY_CURRENT_SCALE,
DISPLAY_PANNING_INFO,
EVENT_ZOOM,
SCALE_POWER_MAX,
SCALE_POWER_MIN,
@ -152,9 +151,7 @@ export class Sim {
const timeScale = this.getOption('param.timeScale');
const elapsedTime = (currentTime - this.rawTime) * timeScale;
this.rawTime = currentTime;
if (this.playing) {
this.time += elapsedTime;
}
if (DISPLAY_CURRENT_MODE) {
this.info['Mode'] = this.getCurrentMode();
@ -169,11 +166,6 @@ 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();

View File

@ -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,10 +107,3 @@ div.lhg-tool .lhg-wide {
width: 12em;
}
div.lhg-overlay-info-box {
position: absolute;
top: 0px;
left: 14em;
width: fit-content;
z-index: 1;
}

View File

@ -92,17 +92,50 @@ export class Zoom extends Tool {
zeroVelocity.addEventListener('click', () => {
// TODO: Zero net angular momentum
// Determine center of mass
const { totalMass, centerOfMass } =
this.sim.objects.computeSystemCenter();
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});
// Determine total angular momentum
const netAngularMomentum = this.sim.objects
.computeSystemAngularMomentum(centerOfMass);
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);
console.log('net angular momentum', netAngularMomentum);
const netAngularVelocity = netAngularMomentum / totalMass;
console.log('net angular velocity', netAngularVelocity);
// TODO: Camera rotation
// TODO: Apply rotation...
// Determine average momentum
const netMomentum = this.sim.objects.reduce((acc, obj) => ({