From e434726be9b140b882a90ee556b2903d41415a4b Mon Sep 17 00:00:00 2001 From: Lentil Hoffman Date: Mon, 29 Dec 2025 13:39:23 -0600 Subject: [PATCH] objects reducer, and some angular momentum calculations --- display.js | 2 +- favicon.ico | Bin 0 -> 32038 bytes objects.js | 55 +++++++++++++++++++++++-------------- tool/play-pause.js | 2 +- tool/zoom.js | 67 +++++++++++++++++++++++++++++++++++---------- 5 files changed, 89 insertions(+), 37 deletions(-) create mode 100644 favicon.ico diff --git a/display.js b/display.js index b21d3f7..73d7596 100644 --- a/display.js +++ b/display.js @@ -72,7 +72,7 @@ export class Display { } drawObjects() { - this.sim.objects.forEachObject(obj => obj.drawObject(this.sim), null); + this.sim.objects.forEachObject(obj => obj.drawObject(this.sim), {alive: null}); } drawArrow(startX, startY, endX, endY, {style, width, arrowhead, arrowheadLength, fill, ifShort}) { diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..546de5095269d62e5cb4dcb362eb2273eb20630d GIT binary patch literal 32038 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 literal 0 HcmV?d00001 diff --git a/objects.js b/objects.js index 5763eae..2522b4c 100644 --- a/objects.js +++ b/objects.js @@ -86,25 +86,25 @@ export class Objects { } get boundingBox() { - const box = { + const box = this.reduce((acc, obj) => { + if (acc.start.x === undefined) { + acc.start = {...obj.position}; + acc.end = {...obj.position}; + } else { + if (obj.position.x < acc.start.x) acc.start.x = obj.position.x; + if (obj.position.x > acc.end.x) acc.end.x = obj.position.x; + if (obj.position.y < acc.start.y) acc.start.y = obj.position.y; + if (obj.position.y > acc.end.y) acc.end.y = obj.position.y; + } + }, { start: {x: undefined, y: undefined}, end: {x: undefined, y: undefined}, - }; - this.forEachObject(obj => { - if (box.start.x === undefined) { - box.start = {...obj.position}; - box.end = {...obj.position}; - } else { - if (obj.position.x < box.start.x) box.start.x = obj.position.x; - if (obj.position.x > box.end.x) box.end.x = obj.position.x; - if (obj.position.y < box.start.y) box.start.y = obj.position.y; - if (obj.position.y > box.end.y) box.end.y = obj.position.y; - } }); - box.start.x -= ZOOM_TO_FIT_PADDING; - box.start.y -= ZOOM_TO_FIT_PADDING; - box.end.x += ZOOM_TO_FIT_PADDING; - box.end.y += ZOOM_TO_FIT_PADDING; + + box.start.x = (box.start.x ?? 0) - ZOOM_TO_FIT_PADDING; + box.start.y = (box.start.y ?? 0) - ZOOM_TO_FIT_PADDING; + box.end.x = (box.end.x ?? 0) + ZOOM_TO_FIT_PADDING; + box.end.y = (box.end.y ?? 0) + ZOOM_TO_FIT_PADDING; return box; } @@ -155,9 +155,9 @@ export class Objects { } // cb: (obj, idx) => {} - // TODO: Reducer - forEachObject(cb, alive = true, startWith = 0) { - for (let i = startWith; i < this.objects.length; i++) { + forEachObject(cb, {alive, startWith} = {}) { + if (alive === undefined) alive = true; + for (let i = startWith ?? 0; i < this.objects.length; i++) { const obj = this.objects[i]; if (alive === null || alive == obj.alive) { const ret = cb(obj, i); @@ -166,6 +166,19 @@ export class Objects { } } + // cb: (acc, obj, idx) => {} + reduce(cb, initial, opts) { + let acc = initial; + console.log('reduce, initial', acc); + this.forEachObject((obj, idx) => { + const ret = cb(acc, obj, idx); + if (ret !== undefined) { + acc = ret; + } + }, opts); + return acc; + } + computeForces() { const gravity = this.sim.getOption('param.gravity'); if (this.objects.length < 2) return; @@ -183,7 +196,7 @@ export class Objects { const Fy = F * dy / d; A.forces.push({ x: Fx, y: Fy }); B.forces.push({ x: -Fx, y: -Fy }); - }, true, i + 1); + }, {alive: true, startWith: i + 1}); }); // Also compute acceleration this.forEachObject(obj => { @@ -262,7 +275,7 @@ export class Objects { T.alive = false; T.forces = []; } - }, true, i + 1); + }, {alive: true, startWith: i + 1}); }); } diff --git a/tool/play-pause.js b/tool/play-pause.js index b3860a7..85572a6 100644 --- a/tool/play-pause.js +++ b/tool/play-pause.js @@ -92,7 +92,7 @@ export class PlayPause extends Tool { // Obliterate object histories this.sim.objects.forEachObject(obj => { obj.history = []; - }, null); + }, {alive: null}); }); } } diff --git a/tool/zoom.js b/tool/zoom.js index 2b1dce7..b06b59c 100644 --- a/tool/zoom.js +++ b/tool/zoom.js @@ -90,31 +90,70 @@ 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}); + + // 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); + + console.log('net angular momentum', netAngularMomentum); + const netAngularVelocity = netAngularMomentum / totalMass; + console.log('net angular velocity', netAngularVelocity); + + // TODO: Apply rotation... + // Determine average momentum - const netMomentum = {x: 0, y: 0}; - let totalMass = 0; - let count = 0; - this.sim.objects.forEachObject(obj => { - count++; - netMomentum.x += obj.mass * obj.velocity.x; - netMomentum.y += obj.mass * obj.velocity.y; - totalMass += obj.mass; - }); - if (!count) { - return; - } + const netMomentum = this.sim.objects.reduce((acc, obj) => ({ + x: acc.x + obj.mass * obj.velocity.x, + y: acc.y + obj.mass * obj.velocity.y, + }), { x: 0, y: 0 }); + const netVelocity = { x: netMomentum.x / totalMass, y: netMomentum.y / totalMass, }; + // Apply offset to all object velocities this.sim.objects.forEachObject(obj => { obj.velocity.x -= netVelocity.x; obj.velocity.y -= netVelocity.y; }); - // TODO: Zero net angular momentum - // Cancel panning this.sim.pointer.panning = undefined; });