Feature: Objects Tool
This commit is contained in:
parent
71854d2a95
commit
f05d1ed399
15
Readme.md
15
Readme.md
@ -16,13 +16,13 @@ TODO
|
|||||||
----
|
----
|
||||||
|
|
||||||
- [x] Selection Box
|
- [x] Selection Box
|
||||||
- [ ] Feature: Object List
|
- [x] Feature: Object List
|
||||||
- [ ] Feature: Object Detail
|
- [x] Feature: Object Detail
|
||||||
- [ ] Feature: Zoom to Object
|
- [x] Feature: Zoom to Object
|
||||||
- [ ] Feature: Teleport Object
|
- [ ] Feature: Teleport Object
|
||||||
- [ ] Enhancement: Create Time class and refactor to use
|
- [ ] Enhancement: Create Time class and refactor to use
|
||||||
- [ ] Enhancement: Create Vector class and refactor to use
|
- [x] Enhancement: Create Vector class and refactor to use
|
||||||
- [ ] 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
|
- [ ] 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
|
||||||
@ -32,7 +32,7 @@ TODO
|
|||||||
- [ ] Feature: Polar Coordinates
|
- [ ] Feature: Polar Coordinates
|
||||||
- [ ] Feature: Cylindrical Coordinates
|
- [ ] Feature: Cylindrical Coordinates
|
||||||
- [ ] Feature: Spherical Coordinates
|
- [ ] Feature: Spherical Coordinates
|
||||||
- [ ] Feature: Camera Velocity Display
|
- [x] Feature: Camera Velocity Display
|
||||||
- [ ] Enhancement: World State Snapshots
|
- [ ] Enhancement: World State Snapshots
|
||||||
- [ ] Feature: List / Save / Load World States
|
- [ ] Feature: List / Save / Load World States
|
||||||
- [ ] Feature: Left Button Panning
|
- [ ] Feature: Left Button Panning
|
||||||
@ -52,5 +52,4 @@ TODO
|
|||||||
- [ ] Feature: Time Control: Reverse Time
|
- [ ] Feature: Time Control: Reverse Time
|
||||||
- [ ] Feature: Lossy Rescaling To Widen Zoom (Handling overflow/underflow)
|
- [ ] Feature: Lossy Rescaling To Widen Zoom (Handling overflow/underflow)
|
||||||
- [ ] Enhancement: Track farthest reaches, min/max in each dimension (x, y)
|
- [ ] Enhancement: Track farthest reaches, min/max in each dimension (x, y)
|
||||||
- [x] Task: Verify stationary pointer leads to zero pointer velocity
|
- [x] Fix: Unpause panning when initiated while sim is paused
|
||||||
- [ ] Fix: Unpause panning when initiated while sim is paused
|
|
||||||
|
|||||||
@ -54,6 +54,9 @@ export const EVENT_MODE_ENTER = 'lhg-mode-enter';
|
|||||||
export const EVENT_ZOOM = 'lhg-zoom-event';
|
export const EVENT_ZOOM = 'lhg-zoom-event';
|
||||||
export const EVENT_OPTION_SET = 'lhg-option-set';
|
export const EVENT_OPTION_SET = 'lhg-option-set';
|
||||||
export const EVENT_PLAY_PAUSE = 'lhg-play-pause';
|
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
|
// MODES
|
||||||
export const MODE_MASS_GENERATION = 'mass-gen';
|
export const MODE_MASS_GENERATION = 'mass-gen';
|
||||||
|
|||||||
@ -36,7 +36,8 @@ export function show({items, item, parentEl, itemEl}) {
|
|||||||
item.hidden = false;
|
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);
|
parentEl.removeChild(itemEl);
|
||||||
item.hidden = true;
|
item.hidden = true;
|
||||||
}
|
}
|
||||||
|
|||||||
20
pointer.js
20
pointer.js
@ -17,38 +17,38 @@ export class Pointer {
|
|||||||
this.sim = sim;
|
this.sim = sim;
|
||||||
|
|
||||||
// Monitor mouse movements
|
// 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')) {
|
if (this.sim.getOption('debug.cursorInfo')) {
|
||||||
this.sim.info['pointermove'] = [`${e.clientX.toPrecision(6)}, `, `${e.clientY.toPrecision(6)}`];
|
this.sim.info['pointermove'] = [`${e.clientX.toPrecision(6)}, `, `${e.clientY.toPrecision(6)}`];
|
||||||
}
|
}
|
||||||
this.handlePointerMove({x: e.clientX, y: e.clientY});
|
this.handlePointerMove({x: e.clientX, y: e.clientY});
|
||||||
});
|
});
|
||||||
|
|
||||||
el.addEventListener('pointerdown', e => {
|
canvas.addEventListener('pointerdown', e => {
|
||||||
this.handlePointerDown({x: e.clientX, y: e.clientY});
|
this.handlePointerDown({x: e.clientX, y: e.clientY});
|
||||||
});
|
});
|
||||||
|
|
||||||
el.addEventListener('pointerup', e => {
|
window.addEventListener('pointerup', e => {
|
||||||
this.handlePointerUp({x: e.clientX, y: e.clientY});
|
this.handlePointerUp({x: e.clientX, y: e.clientY});
|
||||||
});
|
});
|
||||||
|
|
||||||
el.addEventListener('pointerleave', e => {
|
// window.addEventListener('pointerleave', e => {
|
||||||
this.handlePointerUp({x: e.clientX, y: e.clientY});
|
// this.handlePointerUp({x: e.clientX, y: e.clientY});
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Monitor wheel events
|
// Monitor wheel events
|
||||||
el.addEventListener('wheel', e => {
|
canvas.addEventListener('wheel', e => {
|
||||||
const factor = e.deltaY < 0 ? ZOOM_IN_FACTOR : ZOOM_OUT_FACTOR;
|
const factor = e.deltaY < 0 ? ZOOM_IN_FACTOR : ZOOM_OUT_FACTOR;
|
||||||
const {x, y} = this.sim.screenToSim(e.clientX, e.clientY);
|
const {x, y} = this.sim.screenToSim(e.clientX, e.clientY);
|
||||||
this.sim.scheduleZoom({x, y}, factor);
|
this.sim.scheduleZoom({x, y}, factor);
|
||||||
});
|
});
|
||||||
|
|
||||||
el.addEventListener('focus', () => {
|
window.addEventListener('focus', () => {
|
||||||
console.log('window focus');
|
console.log('window focus');
|
||||||
});
|
});
|
||||||
el.addEventListener('blur', () => {
|
window.addEventListener('blur', () => {
|
||||||
console.log('window blur');
|
console.log('window blur');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
32
select.js
32
select.js
@ -1,3 +1,4 @@
|
|||||||
|
import {EVENT_SELECT} from './config.js';
|
||||||
import {add, copy, mult} from './vector.js';
|
import {add, copy, mult} from './vector.js';
|
||||||
|
|
||||||
export class Select {
|
export class Select {
|
||||||
@ -30,7 +31,7 @@ export class Select {
|
|||||||
handlePointerDown({x: clientX, y: clientY}) {
|
handlePointerDown({x: clientX, y: clientY}) {
|
||||||
this.box.start = this.sim.screenToSim(clientX, clientY);
|
this.box.start = this.sim.screenToSim(clientX, clientY);
|
||||||
this.box.end = this.box.start;
|
this.box.end = this.box.start;
|
||||||
this.getSelectedObjects();
|
// this.getSelectedObjects();
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePointerMove({x: clientX, y: clientY}) {
|
handlePointerMove({x: clientX, y: clientY}) {
|
||||||
@ -51,22 +52,13 @@ export class Select {
|
|||||||
y: Math.max(start.y, end.y),
|
y: Math.max(start.y, end.y),
|
||||||
};
|
};
|
||||||
this.getSelectedObjects();
|
this.getSelectedObjects();
|
||||||
|
this.sim.div.dispatchEvent(new CustomEvent(EVENT_SELECT));
|
||||||
this.box = {
|
this.box = {
|
||||||
start: undefined,
|
start: undefined,
|
||||||
end: 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) {
|
frame(elapsedTime) {
|
||||||
if (!this.box.start) return;
|
if (!this.box.start) return;
|
||||||
// If panning, let's update the position of our box so it doesn't drift away
|
// 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
|
// Display the box
|
||||||
this.sim.display.drawBox(this.box.start, this.box.end);
|
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});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
46
system.js
46
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 {MassObject} from './object.js';
|
||||||
import {
|
import {
|
||||||
add, copy, cross, degrees,
|
add, copy, cross, degrees,
|
||||||
@ -134,6 +134,8 @@ export class System {
|
|||||||
};
|
};
|
||||||
T.alive = false;
|
T.alive = false;
|
||||||
T.forces = [];
|
T.forces = [];
|
||||||
|
const e = new CustomEvent(EVENT_OBJECT_MERGE, {detail: {surviving: S, merged: T}});
|
||||||
|
this.sim.div.dispatchEvent(e);
|
||||||
}
|
}
|
||||||
}, {alive: true, startWith: i + 1});
|
}, {alive: true, startWith: i + 1});
|
||||||
});
|
});
|
||||||
@ -212,8 +214,9 @@ export class System {
|
|||||||
if (this.sim.getOption('pauseDuring.creation')) {
|
if (this.sim.getOption('pauseDuring.creation')) {
|
||||||
this.pause();
|
this.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.velocity = copy(this.sim.panning.velocity);
|
obj.velocity = copy(this.sim.panning.velocity);
|
||||||
|
const e = new CustomEvent(EVENT_OBJECT_CREATE, {detail: {obj}});
|
||||||
|
this.sim.div.dispatchEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
doneCreatingObject() {
|
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) {
|
object(i) {
|
||||||
return this.objects[i];
|
return this.objects[i];
|
||||||
}
|
}
|
||||||
@ -253,21 +270,20 @@ export class System {
|
|||||||
return this.objects.length;
|
return this.objects.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
get boundingBox() {
|
getBoundingBox(objects = []) {
|
||||||
const box = this.reduce(({start, end}, obj) => {
|
const box = this.reduce(({start, end}, obj) => {
|
||||||
|
if (objects.length && !objects.includes(obj)) return {start, end};
|
||||||
const lx = obj.position.x - obj.radius;
|
const lx = obj.position.x - obj.radius;
|
||||||
const gx = obj.position.x + obj.radius;
|
const gx = obj.position.x + obj.radius;
|
||||||
const ly = obj.position.y - obj.radius;
|
const ly = obj.position.y - obj.radius;
|
||||||
const gy = obj.position.y + obj.radius;
|
const gy = obj.position.y + obj.radius;
|
||||||
let ret;
|
|
||||||
if (start.x === undefined) {
|
if (start.x === undefined) {
|
||||||
ret = {
|
return {
|
||||||
start: {x: lx, y: ly},
|
start: {x: lx, y: ly},
|
||||||
end: {x: gx, y: gy},
|
end: {x: gx, y: gy},
|
||||||
};
|
};
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
ret = {
|
return {
|
||||||
start: {
|
start: {
|
||||||
x: Math.min(start.x, lx),
|
x: Math.min(start.x, lx),
|
||||||
y: Math.min(start.y, ly),
|
y: Math.min(start.y, ly),
|
||||||
@ -277,7 +293,6 @@ export class System {
|
|||||||
y: Math.max(end.y, gy),
|
y: Math.max(end.y, gy),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return ret;
|
|
||||||
}, {
|
}, {
|
||||||
start: {x: undefined, y: undefined},
|
start: {x: undefined, y: undefined},
|
||||||
end: {x: undefined, y: undefined},
|
end: {x: undefined, y: undefined},
|
||||||
@ -373,15 +388,18 @@ export class System {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
computeSystemCenter() {
|
computeSystemCenter(objects = []) {
|
||||||
// Determine center of mass
|
// Determine center of mass
|
||||||
const {totalMass, count, totalMassLocation} =
|
const {totalMass, count, totalMassLocation} =
|
||||||
this.reduce((acc, obj) => ({
|
this.reduce((acc, obj) => {
|
||||||
|
if (objects.length && !objects.includes(obj)) return acc;
|
||||||
|
return {
|
||||||
count: acc.count + 1,
|
count: acc.count + 1,
|
||||||
totalMass: acc.totalMass + obj.mass,
|
totalMass: acc.totalMass + obj.mass,
|
||||||
totalMassLocation: add(acc.totalMassLocation,
|
totalMassLocation: add(acc.totalMassLocation,
|
||||||
mult(obj.position, obj.mass)),
|
mult(obj.position, obj.mass)),
|
||||||
}), {
|
};
|
||||||
|
}, {
|
||||||
totalMassLocation: {x: 0, y: 0},
|
totalMassLocation: {x: 0, y: 0},
|
||||||
totalMass: 0,
|
totalMass: 0,
|
||||||
count: 0,
|
count: 0,
|
||||||
@ -390,8 +408,10 @@ export class System {
|
|||||||
const centerOfMass = count ? div(totalMassLocation, totalMass) : zero;
|
const centerOfMass = count ? div(totalMassLocation, totalMass) : zero;
|
||||||
|
|
||||||
// Determine average momentum
|
// Determine average momentum
|
||||||
const netMomentum = this.reduce((acc, obj) =>
|
const netMomentum = this.reduce((acc, obj) => {
|
||||||
add(acc, mult(obj.velocity, obj.mass)), zero);
|
if (objects.length && !objects.includes(obj)) return acc;
|
||||||
|
return add(acc, mult(obj.velocity, obj.mass));
|
||||||
|
}, zero);
|
||||||
|
|
||||||
return {totalMass, count, totalMassLocation, centerOfMass, netMomentum};
|
return {totalMass, count, totalMassLocation, centerOfMass, netMomentum};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,82 @@
|
|||||||
|
import {hide, show} from '../helper.js';
|
||||||
import {Tool} from '../tool.js';
|
import {Tool} from '../tool.js';
|
||||||
|
import {add, magnitude, sub} from '../vector.js';
|
||||||
|
|
||||||
export class ObjectsTool extends Tool {
|
export class ObjectsTool extends Tool {
|
||||||
|
objects = [];
|
||||||
|
|
||||||
setContainer(container) {
|
setContainer(container) {
|
||||||
super.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
this.populate();
|
||||||
super();
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<span style="background-color: rgb(${r},${g},${b});">` +
|
||||||
|
' </span>' +
|
||||||
|
`${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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -76,6 +76,7 @@ export class OptionsTool extends Tool {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
hide({
|
hide({
|
||||||
|
items: group.items,
|
||||||
item: next,
|
item: next,
|
||||||
parentEl: groupEl,
|
parentEl: groupEl,
|
||||||
itemEl: optionEl,
|
itemEl: optionEl,
|
||||||
|
|||||||
@ -7,35 +7,6 @@ import {
|
|||||||
export class UtilityTool extends Tool {
|
export class UtilityTool extends Tool {
|
||||||
currentTimeEl = undefined;
|
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) {
|
setContainer(container) {
|
||||||
super.setContainer(container);
|
super.setContainer(container);
|
||||||
this.currentTimeEl.innerHTML = this.timeText;
|
this.currentTimeEl.innerHTML = this.timeText;
|
||||||
@ -73,4 +44,34 @@ export class UtilityTool extends Tool {
|
|||||||
this.sim.info = {};
|
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(':');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -65,7 +65,8 @@ export class Zoom extends Tool {
|
|||||||
|
|
||||||
zoomAll.addEventListener('click', () => {
|
zoomAll.addEventListener('click', () => {
|
||||||
// Determine bounding box
|
// 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 x = (box.start.x + box.end.x) / 2;
|
||||||
const y = (box.start.y + box.end.y) / 2;
|
const y = (box.start.y + box.end.y) / 2;
|
||||||
const widthRatio = Math.abs(box.start.x - box.end.x) / this.sim.display.width;
|
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));
|
const factor = Math.ceil(Math.log2(1 / ratio));
|
||||||
|
|
||||||
// Determine average momentum and set panning velocity to match
|
// 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 = {
|
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