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
|
||||
----
|
||||
|
||||
@ -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)
|
||||
|
||||
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(),
|
||||
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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
27
system.js
27
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}});
|
||||
|
||||
|
||||
@ -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};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user