From 54ed2838f71870fad0773526661880a60ab2b4c1 Mon Sep 17 00:00:00 2001 From: Ladd Date: Sun, 4 Jan 2026 13:29:19 -0600 Subject: [PATCH 1/6] Enhancement: options showIf --- helper.js | 38 +++++++++++++++++++++++- sim-options.js | 6 ++-- tool.js | 1 + tool/options.js | 77 +++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 103 insertions(+), 19 deletions(-) diff --git a/helper.js b/helper.js index 33d0443..3517273 100644 --- a/helper.js +++ b/helper.js @@ -1,2 +1,38 @@ -export function makeUtilityButton() { +// `items` is an array of which `item` is a member +// `item` must let us read/write property `hidden` +// `parentEl` is the containing element for `itemEl` +// `itemEl` is the +export function show({items, item, parentEl, itemEl}) { + if (items.length < 2) { + parentEl.appendChild(itemEl); + return; + } + // To determine placement, + // Start with our index in the toolbar tools; + // iterate through toolbar tools before this one, + // and subtract hidden ones from the index. + + let countHidden = 0; + let index = items.indexOf(item); + for (let i = 0; i < index; i++) { + const sibling = items[i]; + if (sibling.hidden) countHidden += 1; + } + index -= countHidden; + + // Now we need to find our place. + // Add to parent using insertBefore. + let idx = 0; + let nextEl = parentEl.firstChild; + while (idx < index) { + nextEl = nextEl.nextSibling; + idx += 1; + } + parentEl.insertBefore(itemEl, nextEl); + item.hidden = false; +} + +export function hide({item, parentEl, itemEl}) { + parentEl.removeChild(itemEl); + item.hidden = true; } diff --git a/sim-options.js b/sim-options.js index 598f2be..f4a6cd5 100644 --- a/sim-options.js +++ b/sim-options.js @@ -8,9 +8,9 @@ export const simOptions = { velocity: ['Velocity Vectors', 'boolean', true], acceleration: ['Accel Vectors', 'boolean', true], traces: ['Path Traces', 'boolean', true], - dashedTraces: ['Dashed', 'boolean', false, {tall: true, hideUnless: 'display.traces'}], - velocityScale: ['Velocity
Vec Scale', 'number', 80, {hideUnless: 'display.velocity'}], - accelerationScale: ['Accel
Vec Scale', 'number', 800, {hideUnless: 'display.acceleration'}], + dashedTraces: ['Dashed', 'boolean', false, {tall: true, showIf: 'display.traces'}], + velocityScale: ['Velocity
Vec Scale', 'number', 80, {showIf: 'display.velocity'}], + accelerationScale: ['Accel
Vec Scale', 'number', 800, {showIf: 'display.acceleration'}], zoomVectors: ['Zoom Vectors', 'boolean', true] }, compensate: { diff --git a/tool.js b/tool.js index 0513e7b..91a7b2d 100644 --- a/tool.js +++ b/tool.js @@ -9,6 +9,7 @@ import { export class Tool { container = undefined; sim = undefined; + hidden = false; constructor() { const div = document.createElement('div'); diff --git a/tool/options.js b/tool/options.js index fe04a1e..fa81043 100644 --- a/tool/options.js +++ b/tool/options.js @@ -1,45 +1,92 @@ // Options picker import { - TOOL_INFO_CLASSNAME, OPTION_GROUP_CLASSNAME, - WIDE_CLASSNAME, TALL_CLASSNAME, + TOOL_INFO_CLASSNAME, + WIDE_CLASSNAME, } from '../config.js'; -import { Tool } from '../tool.js'; +import {Tool} from '../tool.js'; +import {show, hide} from '../helper.js'; export class OptionsTool extends Tool { - sections = undefined; + sectionNames = undefined; + groups = {}; - constructor(sections) { + constructor(sectionNames) { super(); - this.sections = sections; + this.sectionNames = sectionNames; } setContainer(container) { super.setContainer(container); - for (const sectionName of this.sections) { - const option = this.sim.options.getSection(sectionName); - const item = this.visitItem(option); + // Initialize + for (const sectionName of this.sectionNames) { + const group = this.sim.options.getSection(sectionName); + const item = this.visitItem(group); this.div.appendChild(item); } } + // For now, `showIf` must be the name of a boolean property, with optional negation + shouldShow(option) { + if (option.showIf === undefined) return true; + const {name, value} = this.deconstructOption(option.showIf); + return this.sim.getOption(name) === value; + } + + deconstructOption(showIf) { + let name = showIf; + let value = true; + if (name.startsWith('!')) { + value = false; + name = name.slice(1); + } + return {name, value}; + } + visitItem(item, path) { path = [path, item.name].filter(x => !!x).join('.'); switch (item.type) { case 'group': { - const group = document.createElement('div'); - group.classList.add(OPTION_GROUP_CLASSNAME); + const groupEl = document.createElement('div'); + groupEl.classList.add(OPTION_GROUP_CLASSNAME); + const group = {groupEl, items: []}; + this.groups[path] = group; if (item.title) { const heading = document.createElement('h3'); heading.innerHTML = item.title; - group.appendChild(heading); + groupEl.appendChild(heading); + groupEl.items.push({itemEl: heading}); } for (const next of item.items) { - const child = this.visitItem(next, path); - group.appendChild(child); + const optionEl = this.visitItem(next, path); + // const option = {itemEl: optionEl}; + group.items.push(next); + if (this.shouldShow(next)) { + groupEl.appendChild(optionEl); + } + if (next.showIf) { + const {name} = this.deconstructOption(next.showIf); + this.sim.onOptionSet(name, () => { + if (this.shouldShow(next)) { + show({ + items: group.items, + item: next, + parentEl: groupEl, + itemEl: optionEl, + }); + } else { + hide({ + item: next, + parentEl: groupEl, + itemEl: optionEl, + }); + } + }); + } + } - return group; + return groupEl; } case 'boolean': { const button = document.createElement('button'); From 71854d2a953b0eba16fbe59344e7e5aea56442e1 Mon Sep 17 00:00:00 2001 From: Ladd Date: Sun, 4 Jan 2026 15:09:26 -0600 Subject: [PATCH 2/6] added camera vector --- config.js | 3 +++ display.js | 2 +- helper.js | 4 ++++ panning.js | 63 ++++++++++++++++++++----------------------------- pointer.js | 15 ++---------- select.js | 12 +++++++--- sim-options.js | 3 ++- sim-tools.js | 4 ++++ style.css | 8 +++++-- tool/camera.js | 42 +++++++++++++++++++++++++++++++++ tool/options.js | 1 - 11 files changed, 99 insertions(+), 58 deletions(-) create mode 100644 tool/camera.js diff --git a/config.js b/config.js index 8ce6543..c67d7f8 100644 --- a/config.js +++ b/config.js @@ -19,6 +19,9 @@ export const PATH_TRACES_OPACITY = 0.8; export const PATH_TRACES_WIDTH = 1.5; export const PATH_TRACES_DASHED_OPACITY = 1.0; +// PANNING +export const PANNING_ZERO_TOUCH_THRESHOLD = 200; + // SIZES export const POINTER_HISTORY_SIZE = 20; export const OBJECT_HISTORY_SIZE = 1e5; diff --git a/display.js b/display.js index 38ed700..1e48cc2 100644 --- a/display.js +++ b/display.js @@ -140,7 +140,7 @@ export class Display { ctx.resetTransform(); } - drawBox({start, end}) { + drawBox(start, end) { const ctx = this.ctx; ctx.strokeStyle = 'rgb(0, 255, 0)'; ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y); diff --git a/helper.js b/helper.js index 3517273..79cd9b4 100644 --- a/helper.js +++ b/helper.js @@ -2,6 +2,10 @@ // `item` must let us read/write property `hidden` // `parentEl` is the containing element for `itemEl` // `itemEl` is the +// +// The idea is that item remains a member of items, but +// its elementmay be added and removed from the parent element. +// We use the items array to determine the placement of itemEl export function show({items, item, parentEl, itemEl}) { if (items.length < 2) { parentEl.appendChild(itemEl); diff --git a/panning.js b/panning.js index fefd8ba..6bdfafc 100644 --- a/panning.js +++ b/panning.js @@ -1,4 +1,5 @@ -import {add, copy, div, mult, sub, zero} from "./vector.js"; +import {PANNING_ZERO_TOUCH_THRESHOLD} from "./config.js"; +import {add, copy, div, mult, zero} from "./vector.js"; export class Panning { sim = undefined; @@ -11,10 +12,6 @@ export class Panning { this.sim = sim; } - handlePointerDown({x, y}) { - this.initializeTouch({x, y}); - } - initializeTouch({x, y}) { this.touchStart = { x, @@ -30,6 +27,13 @@ export class Panning { }; } + handlePointerDown({x, y}) { + this.initializeTouch({x, y}); + if (this.paused) { + this.paused = false; + } + } + // With fast panning, panning velocity calculation happens every move; // With normal panning, calculation only happens at pointer up. handlePointerMove({x, y}) { @@ -42,49 +46,46 @@ export class Panning { dy: x - this.touchStart.y, dt: this.sim.rawTime - this.touchStart.t, }; - if (this.sim.getOption('compensate.fastPanning')) { - this.updateVelocity(); + + // Convert pointer velocity to simulation scale + let velocity = div(this.sim.pointer.latestVelocity, this.sim.display.scale); + + // Optional time scale compensation + if (this.sim.getOption('compensate.timeScale')) { + velocity = div(velocity, this.sim.timeScale); } + + // Additional scaling factor + velocity = mult(velocity, this.sim.getOption('display.panningSpeed')); + + // TODO: Make it easier to slow down the camera + + // Add pointer velocity to current panning velocity + this.velocity = add(this.velocity, velocity); } } handlePointerUp() { if (this.touchStart && this.touchLatest) { - if (this.touchLatest.dt === 0) { + if (this.touchLatest.dt < PANNING_ZERO_TOUCH_THRESHOLD) { this.velocity = zero; } this.touchStart = undefined; if (this.sim.getOption('compensate.fastPanning')) { this.velocity = zero; - } else { - this.updateVelocity(); } } } frame(elapsedTime) { - const {touchStart: start, touchLatest: latest} = this; const {display} = this.sim; - // Direct translate, unless using fast panning - if (start && latest && !this.sim.getOption('compensate.fastPanning')) { - // start and latest are in screen coordinates, need to convert to sim scale - const delta = div(sub(latest, start), display.scale); - display.viewOrigin = sub(start.viewOrigin, delta); - } - // Apply update to viewOrigin based on panning if (!this.paused) { // elapsedTime is scaled by time scale, is that what we want? // Yes because if panning.velocity == obj.velocity, object should stay in view - const delta = mult(this.velocity, elapsedTime); - display.viewOrigin = add(display.viewOrigin, delta); - } - - // Update what's considered start - if (start && latest) { - this.initializeTouch(this.touchLatest); + display.viewOrigin = add(display.viewOrigin, mult(this.velocity, elapsedTime)); } if (this.sim.getOption('debug.panningInfo')) { @@ -96,18 +97,6 @@ export class Panning { } } - updateVelocity() { - // Convert pointer velocity to simulation scale, and multiply by -1 - // because the camera is panning opposite to the pointer velocity. - let velocity = div(this.sim.pointer.latestVelocity, -this.sim.display.scale); - if (this.sim.getOption('compensate.timeScale')) { - velocity = div(velocity, this.sim.timeScale); - } - // Also add current panning - velocity = add(velocity, this.velocity); - this.velocity = velocity; - } - setVelocity(velocity) { this.velocity = velocity; if (!this.sim.playing) { diff --git a/pointer.js b/pointer.js index 0977cb4..479f734 100644 --- a/pointer.js +++ b/pointer.js @@ -3,24 +3,21 @@ import { MODE_OBJECT_SELECT, MODE_PAN_VIEW, POINTER_HISTORY_SIZE, - TOOLBAR_CLASSNAME, ZOOM_IN_FACTOR, - ZOOM_OUT_FACTOR, + ZOOM_OUT_FACTOR } from './config.js'; export class Pointer { sim = undefined; pointerHistory = []; - touchStart = undefined; // {x: undefined, y: undefined, t: undefined}; - touchLatest = undefined; // {x: undefined, y: undefined, t: undefined}; suppressClick = false; constructor(sim) { this.sim = sim; // Monitor mouse movements - const el = window; + const el = this.sim.display.canvas; el.addEventListener('pointermove', e => { if (this.sim.getOption('debug.cursorInfo')) { @@ -30,14 +27,6 @@ export class Pointer { }); el.addEventListener('pointerdown', e => { - let target = e.target; - while (target && !target.classList?.contains(TOOLBAR_CLASSNAME)) { - target = target.parentNode; - } - if (target) { - return; - } - this.handlePointerDown({x: e.clientX, y: e.clientY}); }); diff --git a/select.js b/select.js index 67144c4..29479ef 100644 --- a/select.js +++ b/select.js @@ -1,4 +1,4 @@ -import {copy} from './vector.js'; +import {add, copy, mult} from './vector.js'; export class Select { sim = undefined; @@ -67,8 +67,14 @@ export class Select { this.selectedSingle = this.selectedGroup[0] ?? undefined; } - frame() { + frame(elapsedTime) { if (!this.box.start) return; - this.sim.display.drawBox(this.box) + // If panning, let's update the position of our box so it doesn't drift away + const {velocity} = this.sim.panning; + const delta = mult(velocity, elapsedTime); + this.box.start = add(this.box.start, delta); + this.box.end = add(this.box.end, delta); + // Display the box + this.sim.display.drawBox(this.box.start, this.box.end); } } diff --git a/sim-options.js b/sim-options.js index f4a6cd5..c79a310 100644 --- a/sim-options.js +++ b/sim-options.js @@ -11,7 +11,8 @@ export const simOptions = { dashedTraces: ['Dashed', 'boolean', false, {tall: true, showIf: 'display.traces'}], velocityScale: ['Velocity
Vec Scale', 'number', 80, {showIf: 'display.velocity'}], accelerationScale: ['Accel
Vec Scale', 'number', 800, {showIf: 'display.acceleration'}], - zoomVectors: ['Zoom Vectors', 'boolean', true] + zoomVectors: ['Zoom Vectors', 'boolean', true], + panningSpeed: ['Pan
Speed', 'number', 0.1], }, compensate: { timeScale: ['Time Scale Compensator', 'boolean', false, {wide: true}], diff --git a/sim-tools.js b/sim-tools.js index 0abf9ab..a626e53 100644 --- a/sim-tools.js +++ b/sim-tools.js @@ -1,3 +1,4 @@ +import {CameraTool} from './tool/camera.js'; import {ModeSwitch} from './tool/modes.js'; import {ObjectTool} from './tool/object.js'; import {ObjectsTool} from './tool/objects.js'; @@ -11,6 +12,9 @@ import {Toolbar} from './toolbar.js'; export function initializeTools(sim) { sim.toolbarGroups = { left: new ToolbarGroup(sim) + .addToolbar(new Toolbar(sim, 'Camera') + .addTool(new CameraTool()) + ) .addToolbar(new Toolbar(sim, 'Tools') .addTool(new Zoom()) .addTool(new PlayPause()) diff --git a/style.css b/style.css index 22652d5..4c78af1 100644 --- a/style.css +++ b/style.css @@ -33,6 +33,10 @@ div[id=simulator] > canvas { left: 0; } +div.lhg-toolbar-group button { + /* opacity: 0.8; */ +} + /* normal toolbar group */ div.lhg-toolbar-group div.lhg-tool { width: 12em; @@ -102,7 +106,7 @@ div.lhg-tool button, div.lhg-tool input { box-sizing: border-box; } -div.lhg-tool button:hover { +div.lhg-tool button:hover, div.lhg-tool input:hover { background-color: #444; } @@ -122,7 +126,7 @@ div.lhg-toolbar-header > * { display: inline-block; } -div.lhg-tool .lhg-tool-info { +div.lhg-tool .lhg-tool-info, div.lhg-tool .lhg-tool-info:hover { background-color: #111; border-color: #000; border-width: 2px; diff --git a/tool/camera.js b/tool/camera.js new file mode 100644 index 0000000..a645a58 --- /dev/null +++ b/tool/camera.js @@ -0,0 +1,42 @@ +import {VELOCITY_VECTOR_COLOR} from '../config.js'; +import {Tool} from '../tool.js'; +import {add, components, direction, div, magnitude} from '../vector.js'; + +export class CameraTool extends Tool { + setContainer(container) { + super.setContainer(container); + + // Use the main sim display, but create a placeholder and draw inside it. + // That way we aren't blocking the main display more than necessary + } + + constructor() { + super(); + + this.div.style.width = '150px'; + this.div.style.height = '150px'; + } + + frame() { + if (!this.container.expanded) return; + + const {display, panning} = this.sim; + const {left, top, width, height} = this.div.getBoundingClientRect(); + const vecScale = this.sim.getOption('display.velocityScale'); + + // Draw a vector for the camera velocity + const offset = add(display.viewOrigin, div({x: left, y: top}, display.scale)); + const start = add(offset, div({x: width, y: height}, 2 * display.scale)); + let speed = magnitude(panning.velocity); + let arrowLength = Math.log10(speed + 1) * vecScale; + const arrowDirection = direction(panning.velocity); + if (!this.sim.getOption('display.zoomVectors')) { + arrowLength /= display.scale; + } + const end = add(start, components(arrowLength, arrowDirection)); + display.drawArrow(start.x, start.y, end.x, end.y, { + style: VELOCITY_VECTOR_COLOR, + ifShort: 'head', + }); + } +} diff --git a/tool/options.js b/tool/options.js index fa81043..76027da 100644 --- a/tool/options.js +++ b/tool/options.js @@ -60,7 +60,6 @@ export class OptionsTool extends Tool { } for (const next of item.items) { const optionEl = this.visitItem(next, path); - // const option = {itemEl: optionEl}; group.items.push(next); if (this.shouldShow(next)) { groupEl.appendChild(optionEl); From f05d1ed3997cea5b05fd538c727196e610dc7d05 Mon Sep 17 00:00:00 2001 From: Ladd Date: Sun, 4 Jan 2026 16:44:25 -0600 Subject: [PATCH 3/6] Feature: Objects Tool --- Readme.md | 15 +++++----- config.js | 3 ++ helper.js | 3 +- pointer.js | 20 ++++++------- select.js | 32 +++++++++++++-------- system.js | 54 ++++++++++++++++++++++++----------- tool/objects.js | 75 +++++++++++++++++++++++++++++++++++++++++++++++-- tool/options.js | 1 + tool/utility.js | 59 +++++++++++++++++++------------------- tool/zoom.js | 5 ++-- 10 files changed, 187 insertions(+), 80 deletions(-) diff --git a/Readme.md b/Readme.md index 0f374ab..0b7a18d 100644 --- a/Readme.md +++ b/Readme.md @@ -16,13 +16,13 @@ TODO ---- - [x] Selection Box -- [ ] Feature: Object List -- [ ] Feature: Object Detail -- [ ] Feature: Zoom to Object +- [x] Feature: Object List +- [x] Feature: Object Detail +- [x] Feature: Zoom to Object - [ ] Feature: Teleport Object - [ ] Enhancement: Create Time class and refactor to use -- [ ] Enhancement: Create Vector class and refactor to use -- [ ] Enhancement: Create Panning class and refactor to use +- [x] Enhancement: Create Vector class and refactor to use +- [x] Enhancement: Create Panning class and refactor to use - [ ] Enhancement: Handle pointerleave or other mechanism when window loses focus - [ ] Enhancement: Calculate Work as FxD as measure of energy flux - [ ] Feature: Automatically slow time when energy flux is greater @@ -32,7 +32,7 @@ TODO - [ ] Feature: Polar Coordinates - [ ] Feature: Cylindrical Coordinates - [ ] Feature: Spherical Coordinates -- [ ] Feature: Camera Velocity Display +- [x] Feature: Camera Velocity Display - [ ] Enhancement: World State Snapshots - [ ] Feature: List / Save / Load World States - [ ] Feature: Left Button Panning @@ -52,5 +52,4 @@ TODO - [ ] Feature: Time Control: Reverse Time - [ ] Feature: Lossy Rescaling To Widen Zoom (Handling overflow/underflow) - [ ] Enhancement: Track farthest reaches, min/max in each dimension (x, y) -- [x] Task: Verify stationary pointer leads to zero pointer velocity -- [ ] Fix: Unpause panning when initiated while sim is paused +- [x] Fix: Unpause panning when initiated while sim is paused diff --git a/config.js b/config.js index c67d7f8..cb706d7 100644 --- a/config.js +++ b/config.js @@ -54,6 +54,9 @@ export const EVENT_MODE_ENTER = 'lhg-mode-enter'; export const EVENT_ZOOM = 'lhg-zoom-event'; export const EVENT_OPTION_SET = 'lhg-option-set'; export const EVENT_PLAY_PAUSE = 'lhg-play-pause'; +export const EVENT_SELECT = 'lhg-select'; +export const EVENT_OBJECT_CREATE = 'lhg-object-create'; +export const EVENT_OBJECT_MERGE = 'lhg-object-merge'; // MODES export const MODE_MASS_GENERATION = 'mass-gen'; diff --git a/helper.js b/helper.js index 79cd9b4..b3b9a0e 100644 --- a/helper.js +++ b/helper.js @@ -36,7 +36,8 @@ export function show({items, item, parentEl, itemEl}) { item.hidden = false; } -export function hide({item, parentEl, itemEl}) { +export function hide({items, item, parentEl, itemEl}) { + if (items.indexOf(item) < 0) return; parentEl.removeChild(itemEl); item.hidden = true; } diff --git a/pointer.js b/pointer.js index 479f734..2aaeca1 100644 --- a/pointer.js +++ b/pointer.js @@ -17,38 +17,38 @@ export class Pointer { this.sim = sim; // Monitor mouse movements - const el = this.sim.display.canvas; + const {canvas} = this.sim.display; - el.addEventListener('pointermove', e => { + window.addEventListener('pointermove', e => { if (this.sim.getOption('debug.cursorInfo')) { this.sim.info['pointermove'] = [`${e.clientX.toPrecision(6)}, `, `${e.clientY.toPrecision(6)}`]; } this.handlePointerMove({x: e.clientX, y: e.clientY}); }); - el.addEventListener('pointerdown', e => { + canvas.addEventListener('pointerdown', e => { this.handlePointerDown({x: e.clientX, y: e.clientY}); }); - el.addEventListener('pointerup', e => { + window.addEventListener('pointerup', e => { this.handlePointerUp({x: e.clientX, y: e.clientY}); }); - el.addEventListener('pointerleave', e => { - this.handlePointerUp({x: e.clientX, y: e.clientY}); - }); + // window.addEventListener('pointerleave', e => { + // this.handlePointerUp({x: e.clientX, y: e.clientY}); + // }); // Monitor wheel events - el.addEventListener('wheel', e => { + canvas.addEventListener('wheel', e => { const factor = e.deltaY < 0 ? ZOOM_IN_FACTOR : ZOOM_OUT_FACTOR; const {x, y} = this.sim.screenToSim(e.clientX, e.clientY); this.sim.scheduleZoom({x, y}, factor); }); - el.addEventListener('focus', () => { + window.addEventListener('focus', () => { console.log('window focus'); }); - el.addEventListener('blur', () => { + window.addEventListener('blur', () => { console.log('window blur'); }); } diff --git a/select.js b/select.js index 29479ef..fd0e064 100644 --- a/select.js +++ b/select.js @@ -1,3 +1,4 @@ +import {EVENT_SELECT} from './config.js'; import {add, copy, mult} from './vector.js'; export class Select { @@ -30,7 +31,7 @@ export class Select { handlePointerDown({x: clientX, y: clientY}) { this.box.start = this.sim.screenToSim(clientX, clientY); this.box.end = this.box.start; - this.getSelectedObjects(); + // this.getSelectedObjects(); } handlePointerMove({x: clientX, y: clientY}) { @@ -51,22 +52,13 @@ export class Select { y: Math.max(start.y, end.y), }; this.getSelectedObjects(); + this.sim.div.dispatchEvent(new CustomEvent(EVENT_SELECT)); this.box = { start: undefined, end: undefined, }; } - getSelectedObjects() { - const {start, end} = this.box; - if (!start) return; - this.selectedGroup = this.sim.system.filter(({position: {x, y}}) => { - return x >= start.x && x <= end.x && y >= start.y && y <= end.y; - }); - // For now, first object in group is selected single - this.selectedSingle = this.selectedGroup[0] ?? undefined; - } - frame(elapsedTime) { if (!this.box.start) return; // If panning, let's update the position of our box so it doesn't drift away @@ -77,4 +69,22 @@ export class Select { // Display the box this.sim.display.drawBox(this.box.start, this.box.end); } + + getSelectedObjects() { + const {start, end} = this.box; + if (!start) return; + this.selectedGroup = this.sim.system.filter(({position: {x, y}}) => { + return x >= start.x && x <= end.x && y >= start.y && y <= end.y; + }); + // For now, first object in group is selected single + this.selectedSingle = this.selectedGroup[0] ?? undefined; + } + + // cb: ({selectedGroup, selectedSingle}) => undefined + onSelect(cb) { + this.sim.div.addEventListener(EVENT_SELECT, () => { + const {selectedGroup, selectedSingle} = this; + cb({selectedGroup, selectedSingle}); + }); + } } diff --git a/system.js b/system.js index 637fd34..518a2a1 100644 --- a/system.js +++ b/system.js @@ -1,4 +1,4 @@ -import {OBJECT_HISTORY_SIZE} from './config.js'; +import {EVENT_OBJECT_CREATE, EVENT_OBJECT_MERGE, OBJECT_HISTORY_SIZE} from './config.js'; import {MassObject} from './object.js'; import { add, copy, cross, degrees, @@ -134,6 +134,8 @@ export class System { }; T.alive = false; T.forces = []; + const e = new CustomEvent(EVENT_OBJECT_MERGE, {detail: {surviving: S, merged: T}}); + this.sim.div.dispatchEvent(e); } }, {alive: true, startWith: i + 1}); }); @@ -212,8 +214,9 @@ export class System { if (this.sim.getOption('pauseDuring.creation')) { this.pause(); } - obj.velocity = copy(this.sim.panning.velocity); + const e = new CustomEvent(EVENT_OBJECT_CREATE, {detail: {obj}}); + this.sim.div.dispatchEvent(e); } doneCreatingObject() { @@ -223,6 +226,20 @@ export class System { } } + // cb: (obj) => undefined + onCreate(cb) { + this.sim.div.addEventListener(EVENT_OBJECT_CREATE, ({detail: {obj}}) => { + cb(obj); + }); + } + + // cb: ({surviving, merged}) => undefined + onMerge(cb) { + this.sim.div.addEventListener(EVENT_OBJECT_MERGE, ({detail: {surviving, merged}}) => { + cb({surviving, merged}); + }); + } + object(i) { return this.objects[i]; } @@ -253,21 +270,20 @@ export class System { return this.objects.length; } - get boundingBox() { + getBoundingBox(objects = []) { const box = this.reduce(({start, end}, obj) => { + if (objects.length && !objects.includes(obj)) return {start, end}; const lx = obj.position.x - obj.radius; const gx = obj.position.x + obj.radius; const ly = obj.position.y - obj.radius; const gy = obj.position.y + obj.radius; - let ret; if (start.x === undefined) { - ret = { + return { start: {x: lx, y: ly}, end: {x: gx, y: gy}, }; - return ret; } - ret = { + return { start: { x: Math.min(start.x, lx), y: Math.min(start.y, ly), @@ -277,7 +293,6 @@ export class System { y: Math.max(end.y, gy), } }; - return ret; }, { start: {x: undefined, y: undefined}, end: {x: undefined, y: undefined}, @@ -373,15 +388,18 @@ export class System { }); } - computeSystemCenter() { + computeSystemCenter(objects = []) { // Determine center of mass const {totalMass, count, totalMassLocation} = - this.reduce((acc, obj) => ({ - count: acc.count + 1, - totalMass: acc.totalMass + obj.mass, - totalMassLocation: add(acc.totalMassLocation, - mult(obj.position, obj.mass)), - }), { + this.reduce((acc, obj) => { + if (objects.length && !objects.includes(obj)) return acc; + return { + count: acc.count + 1, + totalMass: acc.totalMass + obj.mass, + totalMassLocation: add(acc.totalMassLocation, + mult(obj.position, obj.mass)), + }; + }, { totalMassLocation: {x: 0, y: 0}, totalMass: 0, count: 0, @@ -390,8 +408,10 @@ export class System { const centerOfMass = count ? div(totalMassLocation, totalMass) : zero; // Determine average momentum - const netMomentum = this.reduce((acc, obj) => - add(acc, mult(obj.velocity, obj.mass)), zero); + const netMomentum = this.reduce((acc, obj) => { + if (objects.length && !objects.includes(obj)) return acc; + return add(acc, mult(obj.velocity, obj.mass)); + }, zero); return {totalMass, count, totalMassLocation, centerOfMass, netMomentum}; } diff --git a/tool/objects.js b/tool/objects.js index a7d61d7..b9c4585 100644 --- a/tool/objects.js +++ b/tool/objects.js @@ -1,11 +1,82 @@ +import {hide, show} from '../helper.js'; import {Tool} from '../tool.js'; +import {add, magnitude, sub} from '../vector.js'; export class ObjectsTool extends Tool { + objects = []; + setContainer(container) { super.setContainer(container); + + // Display a list of the currently selected objects, + // or all objects if none are currently selected. + if (this.sim.select.selectedGroup.length) { + this.objects = this.sim.select.selectedGroup; + } else { + this.objects = this.sim.system.filter(obj => obj.alive); + } + + this.populate(); + + this.sim.select.onSelect(({selectedGroup}) => { + this.objects = selectedGroup; + this.depopulate(); + this.populate(); + }); + + this.sim.system.onCreate(obj => { + if (!this.sim.select.selectedGroup.length) { + this.objects.push(obj); + this.populate(); + } + }); + + this.sim.system.onMerge(({merged}) => { + if (!merged.objectsToolEl) return; + hide({ + items: this.objects, + item: merged, + parentEl: this.div, + itemEl: merged.objectsToolEl, + }); + }); } - constructor() { - super(); + frame() { + this.populate(); + } + + depopulate() { + while (this.div.firstChild) { + this.div.removeChild(this.div.firstChild); + } + } + + populate() { + for (const obj of this.objects) { + const objectEl = obj.objectsToolEl ?? document.createElement('div'); + obj.objectsToolEl = objectEl; + const {r, g, b} = obj.color; + // Distance from center of screen + const distance = magnitude(sub(obj.position, add(this.sim.display.viewOrigin, { + x: this.sim.display.width / 2, + y: this.sim.display.height / 2, + }))); + objectEl.innerHTML = ` + ` + + '  ' + + `${obj.mass.toPrecision(3)} ` + + `${distance.toPrecision(3)}`; + // `${magnitude(obj.velocity).toExponential(0)} ` + + // `${-degrees(direction(obj.velocity)).toFixed(0)}°`; + if (!obj.hidden) { + show({ + items: this.objects, + item: obj, + parentEl: this.div, + itemEl: objectEl, + }); + } + } } } diff --git a/tool/options.js b/tool/options.js index 76027da..83ac97f 100644 --- a/tool/options.js +++ b/tool/options.js @@ -76,6 +76,7 @@ export class OptionsTool extends Tool { }); } else { hide({ + items: group.items, item: next, parentEl: groupEl, itemEl: optionEl, diff --git a/tool/utility.js b/tool/utility.js index 20d8c27..8c10baa 100644 --- a/tool/utility.js +++ b/tool/utility.js @@ -7,35 +7,6 @@ import { export class UtilityTool extends Tool { currentTimeEl = undefined; - get timeText() { - let time = this.sim.time; - // Time in milliseconds - const ms = Math.floor(time % 1000); - time = (time - ms) / 1000; - const s = Math.floor(time % 60); - time = (time - s) / 60; - const m = Math.floor(time % 60); - time = (time - m) / 60; - const h = Math.floor(time % 24); - time = (time - h) / 24; - const d = Math.floor(time); - return [ - d || undefined, - h.toString().padStart(2, '0'), - m.toString().padStart(2, '0'), - [ - s.toString().padStart(2, '0'), - ms.toString().padStart(3, '0'), - ].join('.') - ].filter(x => x !== undefined).join(':'); - } - - frame() { - if (this.currentTimeEl) { - this.currentTimeEl.innerHTML = this.timeText; - } - } - setContainer(container) { super.setContainer(container); this.currentTimeEl.innerHTML = this.timeText; @@ -73,4 +44,34 @@ export class UtilityTool extends Tool { this.sim.info = {}; }); } + + frame() { + if (this.currentTimeEl) { + this.currentTimeEl.innerHTML = this.timeText; + } + } + + get timeText() { + let time = this.sim.time; + // Time in milliseconds + const ms = Math.floor(time % 1000); + time = (time - ms) / 1000; + const s = Math.floor(time % 60); + time = (time - s) / 60; + const m = Math.floor(time % 60); + time = (time - m) / 60; + const h = Math.floor(time % 24); + time = (time - h) / 24; + const d = Math.floor(time); + return [ + d || undefined, + h.toString().padStart(2, '0'), + m.toString().padStart(2, '0'), + [ + s.toString().padStart(2, '0'), + ms.toString().padStart(3, '0'), + ].join('.') + ].filter(x => x !== undefined).join(':'); + } + } diff --git a/tool/zoom.js b/tool/zoom.js index f01e8ba..0c718e8 100644 --- a/tool/zoom.js +++ b/tool/zoom.js @@ -65,7 +65,8 @@ export class Zoom extends Tool { zoomAll.addEventListener('click', () => { // Determine bounding box - const box = this.sim.system.boundingBox; + const objects = this.sim.select.selectedGroup; + const box = this.sim.system.getBoundingBox(objects); const x = (box.start.x + box.end.x) / 2; const y = (box.start.y + box.end.y) / 2; const widthRatio = Math.abs(box.start.x - box.end.x) / this.sim.display.width; @@ -74,7 +75,7 @@ export class Zoom extends Tool { const factor = Math.ceil(Math.log2(1 / ratio)); // Determine average momentum and set panning velocity to match - const {netMomentum, totalMass} = this.sim.system.computeSystemCenter(); + const {netMomentum, totalMass} = this.sim.system.computeSystemCenter(objects); const netVelocity = { x: netMomentum.x / totalMass, y: netMomentum.y / totalMass, From a0e45f00b6e5a5aa9756c4132fdfebf7e4a993e6 Mon Sep 17 00:00:00 2001 From: Ladd Date: Sun, 4 Jan 2026 23:30:32 -0600 Subject: [PATCH 4/6] Feature: Save and load from url JSON strings --- Readme.md | 7 ++-- display.js | 12 +++++++ helper.js | 8 +++++ object.js | 36 +++++++++++++++---- panning.js | 12 +++++++ sim-tools.js | 4 +++ simulator.js | 37 +++++++++++++++++--- style.css | 2 +- system.js | 22 ++++++++++-- tool/state.js | 93 +++++++++++++++++++++++++++++++++++++++++++++++++ tool/utility.js | 4 +-- 11 files changed, 219 insertions(+), 18 deletions(-) create mode 100644 tool/state.js diff --git a/Readme.md b/Readme.md index 0b7a18d..f7c273f 100644 --- a/Readme.md +++ b/Readme.md @@ -33,8 +33,10 @@ TODO - [ ] Feature: Cylindrical Coordinates - [ ] Feature: Spherical Coordinates - [x] Feature: Camera Velocity Display -- [ ] Enhancement: World State Snapshots -- [ ] Feature: List / Save / Load World States +- [x] Enhancement: World State Snapshots +- [x] Feature: List / Save / Load World States +- [ ] Enhancement: Save / Load Snapshots from Local Storage +- [ ] Feature: Import / Export / Share Snapshots - [ ] Feature: Left Button Panning - [ ] Feature: Middle Button Pause - [ ] Feature: Parameter Slider (Invisible, mouse/touch drag) @@ -53,3 +55,4 @@ TODO - [ ] Feature: Lossy Rescaling To Widen Zoom (Handling overflow/underflow) - [ ] Enhancement: Track farthest reaches, min/max in each dimension (x, y) - [x] Fix: Unpause panning when initiated while sim is paused +- [ ] Enhancement: Refactor to use viewOrigin as center of display canvas diff --git a/display.js b/display.js index 1e48cc2..db26bb3 100644 --- a/display.js +++ b/display.js @@ -20,6 +20,18 @@ export class Display { } } + toJSON() { + return { + scalePower: this.scalePower, + viewOrigin: this.viewOrigin, + }; + } + + fromJSON({scalePower, viewOrigin}) { + this.scalePower = scalePower; + this.viewOrigin = viewOrigin; + } + frame() { // Clear canvas in preparation for other modules to render this frame this.fillCanvas(); diff --git a/helper.js b/helper.js index b3b9a0e..16a2a1f 100644 --- a/helper.js +++ b/helper.js @@ -41,3 +41,11 @@ export function hide({items, item, parentEl, itemEl}) { parentEl.removeChild(itemEl); item.hidden = true; } + +// Copied from https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest +export async function hash(text) { + const msgUint8 = new TextEncoder().encode(text); // encode as (utf-8) Uint8Array + const hashBuffer = await window.crypto.subtle.digest("SHA-256", msgUint8); // hash the message + const hashHex = new Uint8Array(hashBuffer).toHex(); // Convert ArrayBuffer to hex string. + return hashHex; +} diff --git a/object.js b/object.js index a1336c5..ab403e9 100644 --- a/object.js +++ b/object.js @@ -16,7 +16,7 @@ import { VELOCITY_VECTOR_COLOR, VELOCITY_VECTOR_WIDTH, } from './config.js'; -import {add, components, direction, div, magnitude, zero} from './vector.js'; +import {add, components, copy, direction, div, magnitude, zero} from './vector.js'; export class MassObject { sim = undefined; @@ -27,7 +27,7 @@ export class MassObject { velocity = zero; acceleration = zero; color = {r: undefined, g: undefined, b: undefined}; - created = undefined; + timeCreated = undefined; forces = []; // [{x, y}] history = []; alive = true; @@ -44,17 +44,39 @@ export class MassObject { this.color.g = Math.random() * 256; this.color.b = Math.random() * 256; this.timeCreated = this.sim.time; - this.rawTimeCreated = this.sim.rawTime; + } + + toJSON() { + return { + id: this.id, + mass: this.mass, + density: this.density, + position: this.position, + velocity: this.velocity, + color: this.color, + timeCreated: this.timeCreated, + alive: this.alive, + // TODO: optional export history + }; + } + + fromJSON(obj) { + this.id = obj.id; + this.mass = obj.mass; + this.density = obj.density; + this.position = copy(obj.position); + this.velocity = copy(obj.velocity); + this.color = obj.color; + this.timeCreated = obj.timeCreated; + this.alive = obj.alive; + // TODO: optional import history + this.history = []; } get age() { return this.sim.time - this.timeCreated; } - get rawAge() { - return this.sim.rawTime - this.rawTimeCreated; - } - get radius() { // radius should be proportional to cube root of mass return Math.pow(this.mass / this.density, 1 / 3); diff --git a/panning.js b/panning.js index 6bdfafc..d53463b 100644 --- a/panning.js +++ b/panning.js @@ -27,6 +27,18 @@ export class Panning { }; } + toJSON() { + return { + velocity: this.velocity, + paused: this.paused, + }; + } + + fromJSON({velocity, paused}) { + this.velocity = copy(velocity); + this.paused = paused; + } + handlePointerDown({x, y}) { this.initializeTouch({x, y}); if (this.paused) { diff --git a/sim-tools.js b/sim-tools.js index a626e53..81da2e6 100644 --- a/sim-tools.js +++ b/sim-tools.js @@ -4,6 +4,7 @@ import {ObjectTool} from './tool/object.js'; import {ObjectsTool} from './tool/objects.js'; import {OptionsTool} from './tool/options.js'; import {PlayPause} from './tool/play-pause.js'; +import {StateTool} from './tool/state.js'; import {UtilityTool} from './tool/utility.js'; import {Zoom} from './tool/zoom.js'; import {ToolbarGroup} from './toolbar-group.js'; @@ -15,6 +16,9 @@ export function initializeTools(sim) { .addToolbar(new Toolbar(sim, 'Camera') .addTool(new CameraTool()) ) + .addToolbar(new Toolbar(sim, 'State') + .addTool(new StateTool()) + ) .addToolbar(new Toolbar(sim, 'Tools') .addTool(new Zoom()) .addTool(new PlayPause()) diff --git a/simulator.js b/simulator.js index 4d02bd8..c124a65 100644 --- a/simulator.js +++ b/simulator.js @@ -4,15 +4,15 @@ import { FRAMERATE_SAMPLE_DURATION, } from './config.js'; import {Display} from './display.js'; -import {System} from './system.js'; -import {Overlay} from './overlay.js'; -import {Pointer} from './pointer.js'; import {Options} from './options.js'; -import {Zoom} from './zoom.js'; +import {Overlay} from './overlay.js'; import {Panning} from './panning.js'; +import {Pointer} from './pointer.js'; import {Select} from './select.js'; import {simOptions} from './sim-options.js'; import {initializeTools} from './sim-tools.js'; +import {System} from './system.js'; +import {Zoom} from './zoom.js'; export class Sim { info = {}; @@ -88,13 +88,19 @@ export class Sim { this.zoom.frame(elapsedTime); this.panning.frame(elapsedTime); this.pointer.frame(elapsedTime); + // The display.frame() wipes out the canvas, so all + // main canvas drawing routines must come after the next line. this.display.frame(elapsedTime); this.select.frame(elapsedTime); + // The system.frame() renders objects this.system.frame(elapsedTime); this.overlay.frame(elapsedTime); for (const group in this.toolbarGroups) { this.toolbarGroups[group].frame(elapsedTime); } + // Schedule our next iteration + // TODO: Consider waiting until the next frame is likely to execute, + // in order to aim closer to the target frame rate requestAnimationFrame(t => this.frame(t)); } @@ -122,6 +128,29 @@ export class Sim { return false; } + toJSON() { + return { + dateSaved: new Date().toISOString(), + system: this.system.toJSON(), + panning: this.panning.toJSON(), + display: this.display.toJSON(), + playing: this.playing, + time: this.time, + timeScale: this.timeScale, + currentMode: this.getCurrentMode(), + }; + } + + fromJSON(state) { + this.system.fromJSON(state.system); + this.panning.fromJSON(state.panning); + this.display.fromJSON(state.display); + this.playing = state.playing; + this.time = state.time; + this.timeScale = state.timeScale; + this.setCurrentMode(state.currentMode); + } + // velocity should be in Sim coordinate scale scheduleZoom({x, y}, factor, velocity) { this.zoom.scheduleZoom({x, y}, factor, velocity); diff --git a/style.css b/style.css index 4c78af1..ee9410f 100644 --- a/style.css +++ b/style.css @@ -87,7 +87,7 @@ div.lhg-tool div.lhg-wide { flex-direction: row; } -div.lhg-tool button, div.lhg-tool input { +div.lhg-tool button, div.lhg-tool input, div.lhg-tool a { font-family: monospace; font-size: 10pt; background-color: #333; diff --git a/system.js b/system.js index 518a2a1..34d4c38 100644 --- a/system.js +++ b/system.js @@ -11,13 +11,30 @@ export class System { creatingObject = undefined; selectedObject = undefined; selectObjectStart = undefined; - panVelocityPaused = undefined; paused = false; constructor(sim) { this.sim = sim; } + toJSON() { + return { + objects: this.objects.map(obj => obj.toJSON()), + } + } + + fromJSON({objects} = {}) { + objects = objects ?? []; + // Replace current state with the provided one. + // Assumes a backup has already been saved if desired. + this.objects = []; + for (const objectJSON of objects) { + const obj = new MassObject(this.sim, 0, 0); + obj.fromJSON(objectJSON); + this.objects.push(obj); + } + } + handlePointerDown({x, y}) { // If pointer is touching an object, select the object const touchingObject = this.objectAtLocation(x, y); @@ -191,6 +208,8 @@ export class System { this.drawObjects(); } + // Pause and resume to enable automatic pause on object create/select + // in this mode (mass generation) pause() { this.sim.pause(); this.paused = true; @@ -431,5 +450,4 @@ export class System { return acc + obj.mass * s / d; }, 0); } - } diff --git a/tool/state.js b/tool/state.js new file mode 100644 index 0000000..a055d2f --- /dev/null +++ b/tool/state.js @@ -0,0 +1,93 @@ +import {TOOL_INFO_CLASSNAME, WIDE_CLASSNAME} from '../config.js'; +import {hash} from '../helper.js'; +import {Tool} from '../tool.js'; + +export class StateTool extends Tool { + stored = []; + + setContainer(container) { + super.setContainer(container); + + const buttons = document.createElement('div'); + const save = document.createElement('button'); + const list = document.createElement('div'); + + save.innerHTML = 'Save'; + + save.classList.add(WIDE_CLASSNAME); + buttons.style.display = 'flex'; + buttons.style.flexDirection = 'row'; + buttons.appendChild(save); + list.style.display = 'flex'; + list.style.flexDirection = 'column'; + this.div.appendChild(buttons); + this.div.appendChild(list); + + save.addEventListener('click', async () => { + const state = this.sim.toJSON(); + this.stored.push(state); + const item = await this.createItem(state); + list.appendChild(item); + }); + + // Check url query parameter, and load specified state if found + const paramsString = window.location.search; + const searchParams = new URLSearchParams(paramsString); + const stateEnc = searchParams.get("state"); // a + if (stateEnc) { + const stateText = decodeURI(stateEnc); + const state = JSON.parse(stateText); + // Tools in this system can be very powerful + this.sim.fromJSON(state); + } + } + + getStateDescription(state) { + const d = new Date(state.dateSaved); + return `${d.toLocaleDateString()} ${d.toLocaleTimeString()}`; + } + + async createItem(state) { + const item = document.createElement('div'); + item.style.display = 'flex'; + item.style.flexDirection = 'row'; + item.style.flexWrap = 'wrap'; + + const description = document.createElement('button'); + description.style.flex = '2'; + description.classList.add(TOOL_INFO_CLASSNAME); + description.innerHTML = this.getStateDescription(state); + + const load = document.createElement('button'); + load.style.flex = '1'; + load.innerHTML = 'Load'; + + const link = document.createElement('a'); + const {url, digest} = await this.toUrl(state); + link.classList.add(TOOL_INFO_CLASSNAME); + link.classList.add(WIDE_CLASSNAME); + link.href = url; + link.innerHTML = digest.slice(0, 6); + + item.appendChild(description); + item.appendChild(load); + item.appendChild(link); + + load.addEventListener('click', () => { + // Tools in this system can wield great power + this.sim.fromJSON(state); + }); + + return item; + } + + async toUrl(state) { + const stateText = JSON.stringify(state); + // const stateB64 = window.btoa(stateText); + const rawUrl = `./?state=${stateText}`; + const url = encodeURI(rawUrl); + const digest = await hash(stateText); + return {url, digest}; + } + +} diff --git a/tool/utility.js b/tool/utility.js index 8c10baa..5a3cfac 100644 --- a/tool/utility.js +++ b/tool/utility.js @@ -12,8 +12,8 @@ export class UtilityTool extends Tool { this.currentTimeEl.innerHTML = this.timeText; } - constructor(container) { - super(container); + constructor() { + super(); const clearTraces = document.createElement('button'); const currentTime = document.createElement('button'); From 0a0c9da3af95f70d917f4b87845fa14a5d9a2944 Mon Sep 17 00:00:00 2001 From: Ladd Date: Mon, 5 Jan 2026 00:45:04 -0600 Subject: [PATCH 5/6] improve load from url --- ; | 95 ++++++++++++++++++++++++++++++++++++++++ Readme.md | 7 ++- gravity-simulator-6.png | Bin 0 -> 115943 bytes simulator.js | 2 - style.css | 7 ++- system.js | 27 ++++++++---- tool/state.js | 55 ++++++++++++++--------- tool/zoom.js | 4 +- 8 files changed, 162 insertions(+), 35 deletions(-) create mode 100644 ; create mode 100644 gravity-simulator-6.png diff --git a/; b/; new file mode 100644 index 0000000..1fb6e4b --- /dev/null +++ b/; @@ -0,0 +1,95 @@ +import {TOOL_INFO_CLASSNAME, WIDE_CLASSNAME} from '../config.js'; +import {hash} from '../helper.js'; +import {Tool} from '../tool.js'; + +export class StateTool extends Tool { + stored = []; + + setContainer(container) { + super.setContainer(container); + + const buttons = document.createElement('div'); + const save = document.createElement('button'); + const list = document.createElement('div'); + + save.innerHTML = 'Save'; + + save.classList.add(WIDE_CLASSNAME); + buttons.style.display = 'flex'; + buttons.style.flexDirection = 'row'; + buttons.appendChild(save); + list.style.display = 'flex'; + list.style.flexDirection = 'column'; + this.div.appendChild(buttons); + this.div.appendChild(list); + + save.addEventListener('click', async () => { + const state = this.sim.toJSON(); + this.stored.push(state); + const item = await this.createItem(state); + list.appendChild(item); + }); + + // Check url query parameter, and load specified state if found + const paramsString = window.location.search; + const searchParams = new URLSearchParams(paramsString); + const stateEnc = searchParams.get("state"); // a + if (stateEnc) { + const stateText = decodeURI(stateEnc); + const state = JSON.parse(stateText); + // Tools in this system can be very powerful + this.sim.fromJSON(state); + } + } + + getStateDescription(state) { + const d = new Date(state.dateSaved); + return `${d.toLocaleDateString()} ${d.toLocaleTimeString()}`; + } + + async createItem(state) { + const item = document.createElement('div'); + item.style.display = 'flex'; + item.style.flexDirection = 'row'; + item.style.flexWrap = 'wrap'; + + const description = document.createElement('button'); + description.style.flex = '2'; + description.classList.add(TOOL_INFO_CLASSNAME); + description.innerHTML = this.getStateDescription(state); + + const load = document.createElement('button'); + load.style.flex = '1'; + load.innerHTML = 'Load'; + + const link = document.createElement('a'); + const {url, digest} = await this.toUrl(state); + link.classList.add(TOOL_INFO_CLASSNAME); + link.classList.add(WIDE_CLASSNAME); + link.href = url; + link.innerHTML = digest.slice(0, 6); + + load.appendChild(link); + + item.appendChild(description); + item.appendChild(load); + + load.addEventListener('click', (e) => { + e.preventDefault(); + // Tools in this system can wield great power + this.sim.fromJSON(state); + }); + + return item; + } + + async toUrl(state) { + const stateText = JSON.stringify(state); + // const stateB64 = window.btoa(stateText); + const rawUrl = `./?state=${stateText}`; + const url = encodeURI(rawUrl); + const digest = await hash(stateText); + return {url, digest}; + } + +} diff --git a/Readme.md b/Readme.md index f7c273f..40ba8ab 100644 --- a/Readme.md +++ b/Readme.md @@ -12,6 +12,9 @@ Screenshots ![A greater mass attracts others which had been on escape trajectories](./gravity-simulator-5.png "Gravity Simulator Screenshot 5") +![A small object orbits a more massive binary pair](./gravity-simulator-6.png "Gravity Simulator Screenshot 6") + + TODO ---- @@ -24,7 +27,7 @@ TODO - [x] Enhancement: Create Vector class and refactor to use - [x] Enhancement: Create Panning class and refactor to use - [ ] Enhancement: Handle pointerleave or other mechanism when window loses focus -- [ ] Enhancement: Calculate Work as FxD as measure of energy flux +- [x] Enhancement: Calculate Work as FxD as measure of energy flux - [ ] Feature: Automatically slow time when energy flux is greater - [ ] Enhancement: Add z-axis (Initially nothing changes, z = 0) - [ ] Feature: Isometric 3d View @@ -36,7 +39,7 @@ TODO - [x] Enhancement: World State Snapshots - [x] Feature: List / Save / Load World States - [ ] Enhancement: Save / Load Snapshots from Local Storage -- [ ] Feature: Import / Export / Share Snapshots +- [x] Feature: Import / Export / Share Snapshots - [ ] Feature: Left Button Panning - [ ] Feature: Middle Button Pause - [ ] Feature: Parameter Slider (Invisible, mouse/touch drag) diff --git a/gravity-simulator-6.png b/gravity-simulator-6.png new file mode 100644 index 0000000000000000000000000000000000000000..d0117d2274f5bfcb167c000d3b124d2313bc0b7d GIT binary patch literal 115943 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�AQ;!k~dp|%^9sFVwBn5n)sFa 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 literal 0 HcmV?d00001 diff --git a/simulator.js b/simulator.js index c124a65..d63fea7 100644 --- a/simulator.js +++ b/simulator.js @@ -134,7 +134,6 @@ export class Sim { system: this.system.toJSON(), panning: this.panning.toJSON(), display: this.display.toJSON(), - playing: this.playing, time: this.time, timeScale: this.timeScale, currentMode: this.getCurrentMode(), @@ -145,7 +144,6 @@ export class Sim { this.system.fromJSON(state.system); this.panning.fromJSON(state.panning); this.display.fromJSON(state.display); - this.playing = state.playing; this.time = state.time; this.timeScale = state.timeScale; this.setCurrentMode(state.currentMode); diff --git a/style.css b/style.css index ee9410f..943ecc3 100644 --- a/style.css +++ b/style.css @@ -87,7 +87,7 @@ div.lhg-tool div.lhg-wide { flex-direction: row; } -div.lhg-tool button, div.lhg-tool input, div.lhg-tool a { +div.lhg-tool button, div.lhg-tool input { font-family: monospace; font-size: 10pt; background-color: #333; @@ -106,6 +106,11 @@ div.lhg-tool button, div.lhg-tool input, div.lhg-tool a { box-sizing: border-box; } +div.lhg-tool button a { + color: #5f5; + text-decoration: none; +} + div.lhg-tool button:hover, div.lhg-tool input:hover { background-color: #444; } diff --git a/system.js b/system.js index 34d4c38..90f7317 100644 --- a/system.js +++ b/system.js @@ -1,9 +1,8 @@ import {EVENT_OBJECT_CREATE, EVENT_OBJECT_MERGE, OBJECT_HISTORY_SIZE} from './config.js'; import {MassObject} from './object.js'; import { - add, copy, cross, degrees, - direction, div, magnitude, mult, - square, sub, weightedAvg, zero + add, copy, cross, degrees, direction, div, + dot, magnitude, mult, square, sub, weightedAvg, zero } from './vector.js'; export class System { @@ -93,12 +92,11 @@ export class System { if (this.sim.playing) { // Predict positions (Velocity verlet method) this.forEachObject(obj => { - obj.currentAcceleration = {...obj.acceleration}; + obj.currentAcceleration = copy(obj.acceleration); - // If this object is being created/selected, clamp its position - if (obj.id === this.getSelectedOrCreating()?.id) { - return; - } + // If this object is being created/selected, we're not going to let it move... + // but we can calculate the work being done by holding it in place. + obj.currentPosition = copy(obj.position); obj.position = add(obj.position, mult( elapsedTime, @@ -162,10 +160,21 @@ export class System { // Predict velocities this.forEachObject(obj => { - const acceleration = {...obj.acceleration}; + const acceleration = copy(obj.acceleration); obj.acceleration = div(add(obj.currentAcceleration, acceleration), 2); obj.velocity = add(obj.velocity, mult(obj.acceleration, elapsedTime)); + + // If the user is positioning this object, we'll leave its position unchanged; + // but let's compute how much work we're doing to accomplish it! + if (obj.id === this.getSelectedOrCreating()?.id) { + const delta = sub(obj.currentPosition, obj.position); + const netForce = mult(obj.acceleration, obj.mass); + const work = dot(netForce, delta); + console.log('work', work); + obj.position = obj.currentPosition; + } + // Append to object history obj.history.push({position: {...obj.position}}); diff --git a/tool/state.js b/tool/state.js index a055d2f..e7ab3ba 100644 --- a/tool/state.js +++ b/tool/state.js @@ -5,7 +5,7 @@ import {Tool} from '../tool.js'; export class StateTool extends Tool { stored = []; - setContainer(container) { + async setContainer(container) { super.setContainer(container); const buttons = document.createElement('div'); @@ -31,20 +31,45 @@ export class StateTool extends Tool { }); // Check url query parameter, and load specified state if found + await this.fromUrl(); + } + + async toUrl(state) { + const stateText = JSON.stringify(state); + const digest = await hash(stateText); + const rawUrl = `./?state=${stateText}&digest=${digest}`; + const url = encodeURI(rawUrl); + return {url, digest}; + } + + async fromUrl() { const paramsString = window.location.search; const searchParams = new URLSearchParams(paramsString); - const stateEnc = searchParams.get("state"); // a + const stateEnc = searchParams.get("state"); + const rxDigest = searchParams.get("digest"); if (stateEnc) { const stateText = decodeURI(stateEnc); + console.log('decoded state text', stateText); const state = JSON.parse(stateText); + const digest = await hash(stateText); + if (digest !== rxDigest) { + throw new Error('state query parameter does not match digest query parameter'); + } // Tools in this system can be very powerful + this.sim.pause(); this.sim.fromJSON(state); } } getStateDescription(state) { - const d = new Date(state.dateSaved); - return `${d.toLocaleDateString()} ${d.toLocaleTimeString()}`; + const date = new Date(state.dateSaved); + const Y = date.getFullYear().toString(); + const M = (date.getMonth() + 1).toString().padStart(2, '0'); + const D = date.getDate().toString().padStart(2, '0'); + const h = date.getHours().toString().padStart(2, '0'); + const m = date.getMinutes().toString().padStart(2, '0'); + const s = date.getSeconds().toString().padStart(2, '0'); + return `${Y}-${M}-${D} ${h}:${m}:${s}`; } async createItem(state) { @@ -60,34 +85,24 @@ export class StateTool extends Tool { const load = document.createElement('button'); load.style.flex = '1'; - load.innerHTML = 'Load'; const link = document.createElement('a'); const {url, digest} = await this.toUrl(state); - link.classList.add(TOOL_INFO_CLASSNAME); - link.classList.add(WIDE_CLASSNAME); link.href = url; - link.innerHTML = digest.slice(0, 6); + link.innerHTML = digest.slice(0, 5); + + load.appendChild(link); item.appendChild(description); item.appendChild(load); - item.appendChild(link); - load.addEventListener('click', () => { + load.addEventListener('click', (e) => { + e.preventDefault(); // Tools in this system can wield great power + this.sim.pause(); this.sim.fromJSON(state); }); return item; } - - async toUrl(state) { - const stateText = JSON.stringify(state); - // const stateB64 = window.btoa(stateText); - const rawUrl = `./?state=${stateText}`; - const url = encodeURI(rawUrl); - const digest = await hash(stateText); - return {url, digest}; - } - } diff --git a/tool/zoom.js b/tool/zoom.js index 0c718e8..545b9fd 100644 --- a/tool/zoom.js +++ b/tool/zoom.js @@ -85,7 +85,9 @@ export class Zoom extends Tool { zeroVelocity.addEventListener('click', () => { // Determine center of mass and average momentum - const {totalMass, netMomentum} = this.sim.system.computeSystemCenter(); + const objects = this.sim.select.selectedGroup; + const {netMomentum} = this.sim.system.computeSystemCenter(objects); + const {totalMass} = this.sim.system.computeSystemCenter(); const netVelocity = { x: netMomentum.x / totalMass, y: netMomentum.y / totalMass, From a6f0f4368301b48665a204c149eb08c4742b4450 Mon Sep 17 00:00:00 2001 From: Ladd Date: Mon, 5 Jan 2026 00:46:04 -0600 Subject: [PATCH 6/6] deleted accidental file --- ; | 95 --------------------------------------------------------------- 1 file changed, 95 deletions(-) delete mode 100644 ; diff --git a/; b/; deleted file mode 100644 index 1fb6e4b..0000000 --- a/; +++ /dev/null @@ -1,95 +0,0 @@ -import {TOOL_INFO_CLASSNAME, WIDE_CLASSNAME} from '../config.js'; -import {hash} from '../helper.js'; -import {Tool} from '../tool.js'; - -export class StateTool extends Tool { - stored = []; - - setContainer(container) { - super.setContainer(container); - - const buttons = document.createElement('div'); - const save = document.createElement('button'); - const list = document.createElement('div'); - - save.innerHTML = 'Save'; - - save.classList.add(WIDE_CLASSNAME); - buttons.style.display = 'flex'; - buttons.style.flexDirection = 'row'; - buttons.appendChild(save); - list.style.display = 'flex'; - list.style.flexDirection = 'column'; - this.div.appendChild(buttons); - this.div.appendChild(list); - - save.addEventListener('click', async () => { - const state = this.sim.toJSON(); - this.stored.push(state); - const item = await this.createItem(state); - list.appendChild(item); - }); - - // Check url query parameter, and load specified state if found - const paramsString = window.location.search; - const searchParams = new URLSearchParams(paramsString); - const stateEnc = searchParams.get("state"); // a - if (stateEnc) { - const stateText = decodeURI(stateEnc); - const state = JSON.parse(stateText); - // Tools in this system can be very powerful - this.sim.fromJSON(state); - } - } - - getStateDescription(state) { - const d = new Date(state.dateSaved); - return `${d.toLocaleDateString()} ${d.toLocaleTimeString()}`; - } - - async createItem(state) { - const item = document.createElement('div'); - item.style.display = 'flex'; - item.style.flexDirection = 'row'; - item.style.flexWrap = 'wrap'; - - const description = document.createElement('button'); - description.style.flex = '2'; - description.classList.add(TOOL_INFO_CLASSNAME); - description.innerHTML = this.getStateDescription(state); - - const load = document.createElement('button'); - load.style.flex = '1'; - load.innerHTML = 'Load'; - - const link = document.createElement('a'); - const {url, digest} = await this.toUrl(state); - link.classList.add(TOOL_INFO_CLASSNAME); - link.classList.add(WIDE_CLASSNAME); - link.href = url; - link.innerHTML = digest.slice(0, 6); - - load.appendChild(link); - - item.appendChild(description); - item.appendChild(load); - - load.addEventListener('click', (e) => { - e.preventDefault(); - // Tools in this system can wield great power - this.sim.fromJSON(state); - }); - - return item; - } - - async toUrl(state) { - const stateText = JSON.stringify(state); - // const stateB64 = window.btoa(stateText); - const rawUrl = `./?state=${stateText}`; - const url = encodeURI(rawUrl); - const digest = await hash(stateText); - return {url, digest}; - } - -}