refactoring

This commit is contained in:
Lentil Hoffman 2025-12-29 16:06:08 -06:00
parent dcc630a2e6
commit 9070617bf4
Signed by: lentil
GPG Key ID: 0F5B99F3F4D0C087
8 changed files with 89 additions and 59 deletions

View File

@ -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

View File

@ -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';

View File

@ -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);
}
}

View File

@ -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() {

View File

@ -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});
});

View File

@ -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();

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,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;
}

View File

@ -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) => ({