improve load from url
This commit is contained in:
parent
a0e45f00b6
commit
0a0c9da3af
95
;
Normal file
95
;
Normal file
@ -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};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -12,6 +12,9 @@ Screenshots
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
----
|
----
|
||||||
|
|
||||||
@ -24,7 +27,7 @@ TODO
|
|||||||
- [x] Enhancement: Create Vector class and refactor to use
|
- [x] Enhancement: Create Vector class and refactor to use
|
||||||
- [x] Enhancement: Create Panning 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: 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
|
- [ ] Feature: Automatically slow time when energy flux is greater
|
||||||
- [ ] Enhancement: Add z-axis (Initially nothing changes, z = 0)
|
- [ ] Enhancement: Add z-axis (Initially nothing changes, z = 0)
|
||||||
- [ ] Feature: Isometric 3d View
|
- [ ] Feature: Isometric 3d View
|
||||||
@ -36,7 +39,7 @@ TODO
|
|||||||
- [x] Enhancement: World State Snapshots
|
- [x] Enhancement: World State Snapshots
|
||||||
- [x] Feature: List / Save / Load World States
|
- [x] Feature: List / Save / Load World States
|
||||||
- [ ] Enhancement: Save / Load Snapshots from Local Storage
|
- [ ] Enhancement: Save / Load Snapshots from Local Storage
|
||||||
- [ ] Feature: Import / Export / Share Snapshots
|
- [x] Feature: Import / Export / Share Snapshots
|
||||||
- [ ] Feature: Left Button Panning
|
- [ ] Feature: Left Button Panning
|
||||||
- [ ] Feature: Middle Button Pause
|
- [ ] Feature: Middle Button Pause
|
||||||
- [ ] Feature: Parameter Slider (Invisible, mouse/touch drag)
|
- [ ] Feature: Parameter Slider (Invisible, mouse/touch drag)
|
||||||
|
|||||||
BIN
gravity-simulator-6.png
Normal file
BIN
gravity-simulator-6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
@ -134,7 +134,6 @@ export class Sim {
|
|||||||
system: this.system.toJSON(),
|
system: this.system.toJSON(),
|
||||||
panning: this.panning.toJSON(),
|
panning: this.panning.toJSON(),
|
||||||
display: this.display.toJSON(),
|
display: this.display.toJSON(),
|
||||||
playing: this.playing,
|
|
||||||
time: this.time,
|
time: this.time,
|
||||||
timeScale: this.timeScale,
|
timeScale: this.timeScale,
|
||||||
currentMode: this.getCurrentMode(),
|
currentMode: this.getCurrentMode(),
|
||||||
@ -145,7 +144,6 @@ export class Sim {
|
|||||||
this.system.fromJSON(state.system);
|
this.system.fromJSON(state.system);
|
||||||
this.panning.fromJSON(state.panning);
|
this.panning.fromJSON(state.panning);
|
||||||
this.display.fromJSON(state.display);
|
this.display.fromJSON(state.display);
|
||||||
this.playing = state.playing;
|
|
||||||
this.time = state.time;
|
this.time = state.time;
|
||||||
this.timeScale = state.timeScale;
|
this.timeScale = state.timeScale;
|
||||||
this.setCurrentMode(state.currentMode);
|
this.setCurrentMode(state.currentMode);
|
||||||
|
|||||||
@ -87,7 +87,7 @@ div.lhg-tool div.lhg-wide {
|
|||||||
flex-direction: row;
|
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-family: monospace;
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
@ -106,6 +106,11 @@ div.lhg-tool button, div.lhg-tool input, div.lhg-tool a {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.lhg-tool button a {
|
||||||
|
color: #5f5;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
div.lhg-tool button:hover, div.lhg-tool input:hover {
|
div.lhg-tool button:hover, div.lhg-tool input:hover {
|
||||||
background-color: #444;
|
background-color: #444;
|
||||||
}
|
}
|
||||||
|
|||||||
27
system.js
27
system.js
@ -1,9 +1,8 @@
|
|||||||
import {EVENT_OBJECT_CREATE, EVENT_OBJECT_MERGE, 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 {MassObject} from './object.js';
|
||||||
import {
|
import {
|
||||||
add, copy, cross, degrees,
|
add, copy, cross, degrees, direction, div,
|
||||||
direction, div, magnitude, mult,
|
dot, magnitude, mult, square, sub, weightedAvg, zero
|
||||||
square, sub, weightedAvg, zero
|
|
||||||
} from './vector.js';
|
} from './vector.js';
|
||||||
|
|
||||||
export class System {
|
export class System {
|
||||||
@ -93,12 +92,11 @@ export class System {
|
|||||||
if (this.sim.playing) {
|
if (this.sim.playing) {
|
||||||
// Predict positions (Velocity verlet method)
|
// Predict positions (Velocity verlet method)
|
||||||
this.forEachObject(obj => {
|
this.forEachObject(obj => {
|
||||||
obj.currentAcceleration = {...obj.acceleration};
|
obj.currentAcceleration = copy(obj.acceleration);
|
||||||
|
|
||||||
// If this object is being created/selected, clamp its position
|
// If this object is being created/selected, we're not going to let it move...
|
||||||
if (obj.id === this.getSelectedOrCreating()?.id) {
|
// but we can calculate the work being done by holding it in place.
|
||||||
return;
|
obj.currentPosition = copy(obj.position);
|
||||||
}
|
|
||||||
|
|
||||||
obj.position = add(obj.position, mult(
|
obj.position = add(obj.position, mult(
|
||||||
elapsedTime,
|
elapsedTime,
|
||||||
@ -162,10 +160,21 @@ export class System {
|
|||||||
|
|
||||||
// Predict velocities
|
// Predict velocities
|
||||||
this.forEachObject(obj => {
|
this.forEachObject(obj => {
|
||||||
const acceleration = {...obj.acceleration};
|
const acceleration = copy(obj.acceleration);
|
||||||
obj.acceleration = div(add(obj.currentAcceleration, acceleration), 2);
|
obj.acceleration = div(add(obj.currentAcceleration, acceleration), 2);
|
||||||
obj.velocity = add(obj.velocity, mult(obj.acceleration, elapsedTime));
|
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
|
// Append to object history
|
||||||
obj.history.push({position: {...obj.position}});
|
obj.history.push({position: {...obj.position}});
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {Tool} from '../tool.js';
|
|||||||
export class StateTool extends Tool {
|
export class StateTool extends Tool {
|
||||||
stored = [];
|
stored = [];
|
||||||
|
|
||||||
setContainer(container) {
|
async setContainer(container) {
|
||||||
super.setContainer(container);
|
super.setContainer(container);
|
||||||
|
|
||||||
const buttons = document.createElement('div');
|
const buttons = document.createElement('div');
|
||||||
@ -31,20 +31,45 @@ export class StateTool extends Tool {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Check url query parameter, and load specified state if found
|
// 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 paramsString = window.location.search;
|
||||||
const searchParams = new URLSearchParams(paramsString);
|
const searchParams = new URLSearchParams(paramsString);
|
||||||
const stateEnc = searchParams.get("state"); // a
|
const stateEnc = searchParams.get("state");
|
||||||
|
const rxDigest = searchParams.get("digest");
|
||||||
if (stateEnc) {
|
if (stateEnc) {
|
||||||
const stateText = decodeURI(stateEnc);
|
const stateText = decodeURI(stateEnc);
|
||||||
|
console.log('decoded state text', stateText);
|
||||||
const state = JSON.parse(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
|
// Tools in this system can be very powerful
|
||||||
|
this.sim.pause();
|
||||||
this.sim.fromJSON(state);
|
this.sim.fromJSON(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getStateDescription(state) {
|
getStateDescription(state) {
|
||||||
const d = new Date(state.dateSaved);
|
const date = new Date(state.dateSaved);
|
||||||
return `${d.toLocaleDateString()} ${d.toLocaleTimeString()}`;
|
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) {
|
async createItem(state) {
|
||||||
@ -60,34 +85,24 @@ export class StateTool extends Tool {
|
|||||||
|
|
||||||
const load = document.createElement('button');
|
const load = document.createElement('button');
|
||||||
load.style.flex = '1';
|
load.style.flex = '1';
|
||||||
load.innerHTML = 'Load';
|
|
||||||
|
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
const {url, digest} = await this.toUrl(state);
|
const {url, digest} = await this.toUrl(state);
|
||||||
link.classList.add(TOOL_INFO_CLASSNAME);
|
|
||||||
link.classList.add(WIDE_CLASSNAME);
|
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.innerHTML = digest.slice(0, 6);
|
link.innerHTML = digest.slice(0, 5);
|
||||||
|
|
||||||
|
load.appendChild(link);
|
||||||
|
|
||||||
item.appendChild(description);
|
item.appendChild(description);
|
||||||
item.appendChild(load);
|
item.appendChild(load);
|
||||||
item.appendChild(link);
|
|
||||||
|
|
||||||
load.addEventListener('click', () => {
|
load.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
// Tools in this system can wield great power
|
// Tools in this system can wield great power
|
||||||
|
this.sim.pause();
|
||||||
this.sim.fromJSON(state);
|
this.sim.fromJSON(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
return item;
|
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};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,7 +85,9 @@ export class Zoom extends Tool {
|
|||||||
|
|
||||||
zeroVelocity.addEventListener('click', () => {
|
zeroVelocity.addEventListener('click', () => {
|
||||||
// Determine center of mass and average momentum
|
// 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 = {
|
const netVelocity = {
|
||||||
x: netMomentum.x / totalMass,
|
x: netMomentum.x / totalMass,
|
||||||
y: netMomentum.y / totalMass,
|
y: netMomentum.y / totalMass,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user