Compare commits
2 Commits
bb331a8a40
...
9c39c43672
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c39c43672 | |||
| e434726be9 |
17
config.js
17
config.js
@ -4,9 +4,6 @@ export const DISPLAY_CURSOR_INFO = false;
|
|||||||
export const DISPLAY_CANVAS_SIZE = false;
|
export const DISPLAY_CANVAS_SIZE = false;
|
||||||
export const DISPLAY_CURRENT_SCALE = false;
|
export const DISPLAY_CURRENT_SCALE = false;
|
||||||
export const DISPLAY_CURRENT_MODE = false;
|
export const DISPLAY_CURRENT_MODE = false;
|
||||||
export const DISPLAY_VELOCITY_VECTORS = true;
|
|
||||||
export const DISPLAY_ACCELERATION_VECTORS = true;
|
|
||||||
export const DISPLAY_PATH_TRACES = false;
|
|
||||||
|
|
||||||
// VELOCITY
|
// VELOCITY
|
||||||
export const VELOCITY_VECTOR_SCALE = 8E0;
|
export const VELOCITY_VECTOR_SCALE = 8E0;
|
||||||
@ -24,16 +21,11 @@ export const ACCELERATION_VECTOR_ARROWHEAD = true;
|
|||||||
export const PATH_TRACES_COLOR = 'object color';
|
export const PATH_TRACES_COLOR = 'object color';
|
||||||
export const PATH_TRACES_OPACITY = 0.8;
|
export const PATH_TRACES_OPACITY = 0.8;
|
||||||
export const PATH_TRACES_WIDTH = 1.5;
|
export const PATH_TRACES_WIDTH = 1.5;
|
||||||
export const PATH_TRACES_DASHED = false;
|
|
||||||
export const PATH_TRACES_DASHED_OPACITY = 1.0;
|
export const PATH_TRACES_DASHED_OPACITY = 1.0;
|
||||||
|
|
||||||
// SCALING FACTORS
|
|
||||||
export const MASS_CREATION_RATE = 10;
|
|
||||||
export const POINTER_HISTORY_SIZE = 20;
|
|
||||||
export const MOTION_TIME_SCALE = 0.3;
|
|
||||||
export const GRAVITATIONAL_CONSTANT = 2E4;
|
|
||||||
|
|
||||||
// SIZES
|
// SIZES
|
||||||
|
export const POINTER_HISTORY_SIZE = 20;
|
||||||
|
export const POINTER_DOWN_HISTORY_SIZE = 5;
|
||||||
export const ARROWHEAD_LENGTH = 7;
|
export const ARROWHEAD_LENGTH = 7;
|
||||||
export const ARROWHEAD_WIDTH = 5;
|
export const ARROWHEAD_WIDTH = 5;
|
||||||
export const OFFSCREEN_OBJECT_LINE_SCALE = 7;
|
export const OFFSCREEN_OBJECT_LINE_SCALE = 7;
|
||||||
@ -62,8 +54,3 @@ export const EVENT_OPTION_SET = 'lhg-option-set';
|
|||||||
export const MODE_MASS_GENERATION = 'mass-gen';
|
export const MODE_MASS_GENERATION = 'mass-gen';
|
||||||
export const MODE_PAN_VIEW = 'pan-view';
|
export const MODE_PAN_VIEW = 'pan-view';
|
||||||
export const MODE_OBJECT_SELECT = 'select';
|
export const MODE_OBJECT_SELECT = 'select';
|
||||||
|
|
||||||
// OPTIONS
|
|
||||||
export const PAUSE_DURING_CREATION = true;
|
|
||||||
export const PAUSE_DURING_SELECTION = true;
|
|
||||||
export const MERGE_ON_COLLIDE = true;
|
|
||||||
|
|||||||
@ -72,7 +72,7 @@ export class Display {
|
|||||||
}
|
}
|
||||||
|
|
||||||
drawObjects() {
|
drawObjects() {
|
||||||
this.sim.objects.forEachObject(obj => obj.drawObject(this.sim), null);
|
this.sim.objects.forEachObject(obj => obj.drawObject(this.sim), {alive: null});
|
||||||
}
|
}
|
||||||
|
|
||||||
drawArrow(startX, startY, endX, endY, {style, width, arrowhead, arrowheadLength, fill, ifShort}) {
|
drawArrow(startX, startY, endX, endY, {style, width, arrowhead, arrowheadLength, fill, ifShort}) {
|
||||||
|
|||||||
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
55
objects.js
55
objects.js
@ -86,25 +86,25 @@ export class Objects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get boundingBox() {
|
get boundingBox() {
|
||||||
const box = {
|
const box = this.reduce((acc, obj) => {
|
||||||
|
if (acc.start.x === undefined) {
|
||||||
|
acc.start = {...obj.position};
|
||||||
|
acc.end = {...obj.position};
|
||||||
|
} else {
|
||||||
|
if (obj.position.x < acc.start.x) acc.start.x = obj.position.x;
|
||||||
|
if (obj.position.x > acc.end.x) acc.end.x = obj.position.x;
|
||||||
|
if (obj.position.y < acc.start.y) acc.start.y = obj.position.y;
|
||||||
|
if (obj.position.y > acc.end.y) acc.end.y = obj.position.y;
|
||||||
|
}
|
||||||
|
}, {
|
||||||
start: {x: undefined, y: undefined},
|
start: {x: undefined, y: undefined},
|
||||||
end: {x: undefined, y: undefined},
|
end: {x: undefined, y: undefined},
|
||||||
};
|
|
||||||
this.forEachObject(obj => {
|
|
||||||
if (box.start.x === undefined) {
|
|
||||||
box.start = {...obj.position};
|
|
||||||
box.end = {...obj.position};
|
|
||||||
} else {
|
|
||||||
if (obj.position.x < box.start.x) box.start.x = obj.position.x;
|
|
||||||
if (obj.position.x > box.end.x) box.end.x = obj.position.x;
|
|
||||||
if (obj.position.y < box.start.y) box.start.y = obj.position.y;
|
|
||||||
if (obj.position.y > box.end.y) box.end.y = obj.position.y;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
box.start.x -= ZOOM_TO_FIT_PADDING;
|
|
||||||
box.start.y -= ZOOM_TO_FIT_PADDING;
|
box.start.x = (box.start.x ?? 0) - ZOOM_TO_FIT_PADDING;
|
||||||
box.end.x += ZOOM_TO_FIT_PADDING;
|
box.start.y = (box.start.y ?? 0) - ZOOM_TO_FIT_PADDING;
|
||||||
box.end.y += ZOOM_TO_FIT_PADDING;
|
box.end.x = (box.end.x ?? 0) + ZOOM_TO_FIT_PADDING;
|
||||||
|
box.end.y = (box.end.y ?? 0) + ZOOM_TO_FIT_PADDING;
|
||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,9 +155,9 @@ export class Objects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// cb: (obj, idx) => {}
|
// cb: (obj, idx) => {}
|
||||||
// TODO: Reducer
|
forEachObject(cb, {alive, startWith} = {}) {
|
||||||
forEachObject(cb, alive = true, startWith = 0) {
|
if (alive === undefined) alive = true;
|
||||||
for (let i = startWith; i < this.objects.length; i++) {
|
for (let i = startWith ?? 0; i < this.objects.length; i++) {
|
||||||
const obj = this.objects[i];
|
const obj = this.objects[i];
|
||||||
if (alive === null || alive == obj.alive) {
|
if (alive === null || alive == obj.alive) {
|
||||||
const ret = cb(obj, i);
|
const ret = cb(obj, i);
|
||||||
@ -166,6 +166,19 @@ export class Objects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cb: (acc, obj, idx) => {}
|
||||||
|
reduce(cb, initial, opts) {
|
||||||
|
let acc = initial;
|
||||||
|
console.log('reduce, initial', acc);
|
||||||
|
this.forEachObject((obj, idx) => {
|
||||||
|
const ret = cb(acc, obj, idx);
|
||||||
|
if (ret !== undefined) {
|
||||||
|
acc = ret;
|
||||||
|
}
|
||||||
|
}, opts);
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
computeForces() {
|
computeForces() {
|
||||||
const gravity = this.sim.getOption('param.gravity');
|
const gravity = this.sim.getOption('param.gravity');
|
||||||
if (this.objects.length < 2) return;
|
if (this.objects.length < 2) return;
|
||||||
@ -183,7 +196,7 @@ export class Objects {
|
|||||||
const Fy = F * dy / d;
|
const Fy = F * dy / d;
|
||||||
A.forces.push({ x: Fx, y: Fy });
|
A.forces.push({ x: Fx, y: Fy });
|
||||||
B.forces.push({ x: -Fx, y: -Fy });
|
B.forces.push({ x: -Fx, y: -Fy });
|
||||||
}, true, i + 1);
|
}, {alive: true, startWith: i + 1});
|
||||||
});
|
});
|
||||||
// Also compute acceleration
|
// Also compute acceleration
|
||||||
this.forEachObject(obj => {
|
this.forEachObject(obj => {
|
||||||
@ -262,7 +275,7 @@ export class Objects {
|
|||||||
T.alive = false;
|
T.alive = false;
|
||||||
T.forces = [];
|
T.forces = [];
|
||||||
}
|
}
|
||||||
}, true, i + 1);
|
}, {alive: true, startWith: i + 1});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
30
options.js
30
options.js
@ -1,35 +1,25 @@
|
|||||||
import {
|
import {
|
||||||
DISPLAY_ACCELERATION_VECTORS,
|
|
||||||
DISPLAY_VELOCITY_VECTORS,
|
|
||||||
GRAVITATIONAL_CONSTANT,
|
|
||||||
MASS_CREATION_RATE,
|
|
||||||
MERGE_ON_COLLIDE,
|
|
||||||
MOTION_TIME_SCALE,
|
|
||||||
PATH_TRACES_DASHED,
|
|
||||||
PAUSE_DURING_CREATION,
|
|
||||||
PAUSE_DURING_SELECTION,
|
|
||||||
EVENT_OPTION_SET,
|
EVENT_OPTION_SET,
|
||||||
} from './config.js';
|
} from './config.js';
|
||||||
|
|
||||||
export const optionsLayout = {
|
export const optionsLayout = {
|
||||||
pauseDuring: {
|
pauseDuring: {
|
||||||
creation: ['Pause While Creating', 'boolean', PAUSE_DURING_CREATION],
|
creation: ['Pause While Creating', 'boolean', true],
|
||||||
creation2: ['Pause While Creating', 'boolean', PAUSE_DURING_CREATION],
|
selection: ['Pause While Selecting', 'boolean', true],
|
||||||
selection: ['Pause While Selecting', 'boolean', PAUSE_DURING_SELECTION],
|
|
||||||
},
|
},
|
||||||
display: {
|
display: {
|
||||||
velocity: ['Velocity Vectors', 'boolean', DISPLAY_VELOCITY_VECTORS],
|
velocity: ['Velocity Vectors', 'boolean', true],
|
||||||
acceleration: ['Accel. Vectors', 'boolean', DISPLAY_ACCELERATION_VECTORS],
|
acceleration: ['Accel. Vectors', 'boolean', true],
|
||||||
traces: ['Path Traces', 'boolean', DISPLAY_ACCELERATION_VECTORS],
|
traces: ['Path Traces', 'boolean', true],
|
||||||
dashedTraces: ['Dashed Traces', 'boolean', PATH_TRACES_DASHED],
|
dashedTraces: ['Dashed Traces', 'boolean', false],
|
||||||
},
|
},
|
||||||
collision: {
|
collision: {
|
||||||
merge: ['Merge Masses<br>on Collision', 'boolean', MERGE_ON_COLLIDE, {wide: true}],
|
merge: ['Merge Masses<br>on Collision', 'boolean', true, {wide: true}],
|
||||||
},
|
},
|
||||||
param: {
|
param: {
|
||||||
gravity: ['Gravity', 'number', GRAVITATIONAL_CONSTANT],
|
gravity: ['Gravity', 'number', 5E4],
|
||||||
timeScale: ['Time Scale', 'number', MOTION_TIME_SCALE],
|
timeScale: ['Time Scale', 'number', 0.3],
|
||||||
massCreationRate: ['Mass Creation Rate', 'number', MASS_CREATION_RATE],
|
massCreationRate: ['Mass Creation Rate', 'number', 10],
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
MODE_OBJECT_SELECT,
|
MODE_OBJECT_SELECT,
|
||||||
MODE_PAN_VIEW,
|
MODE_PAN_VIEW,
|
||||||
POINTER_HISTORY_SIZE,
|
POINTER_HISTORY_SIZE,
|
||||||
|
POINTER_DOWN_HISTORY_SIZE,
|
||||||
TOOLBAR_CLASSNAME,
|
TOOLBAR_CLASSNAME,
|
||||||
ZOOM_IN_FACTOR,
|
ZOOM_IN_FACTOR,
|
||||||
ZOOM_OUT_FACTOR,
|
ZOOM_OUT_FACTOR,
|
||||||
@ -89,7 +90,7 @@ export class Pointer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handlePointerDown({x: clientX, y: clientY}) {
|
handlePointerDown({x: clientX, y: clientY}) {
|
||||||
this.clearPointerHistory(5);
|
this.clearPointerHistory(POINTER_DOWN_HISTORY_SIZE);
|
||||||
this.updatePointer({x: clientX, y: clientY});
|
this.updatePointer({x: clientX, y: clientY});
|
||||||
|
|
||||||
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
|
if (this.sim.isCurrentMode(MODE_MASS_GENERATION)) {
|
||||||
|
|||||||
@ -92,7 +92,7 @@ export class PlayPause extends Tool {
|
|||||||
// Obliterate object histories
|
// Obliterate object histories
|
||||||
this.sim.objects.forEachObject(obj => {
|
this.sim.objects.forEachObject(obj => {
|
||||||
obj.history = [];
|
obj.history = [];
|
||||||
}, null);
|
}, {alive: null});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
67
tool/zoom.js
67
tool/zoom.js
@ -90,31 +90,70 @@ export class Zoom extends Tool {
|
|||||||
});
|
});
|
||||||
|
|
||||||
zeroVelocity.addEventListener('click', () => {
|
zeroVelocity.addEventListener('click', () => {
|
||||||
|
// TODO: Zero net angular momentum
|
||||||
|
// Determine center of mass
|
||||||
|
const { totalMass, count, totalMassLocation } =
|
||||||
|
this.sim.objects.reduce((acc, obj) => ({
|
||||||
|
count: acc.count + 1,
|
||||||
|
totalMass: acc.totalMass + obj.mass,
|
||||||
|
totalMassLocation: {
|
||||||
|
x: acc.totalMassLocation.x + obj.position.x * obj.mass,
|
||||||
|
y: acc.totalMassLocation.y + obj.position.y * obj.mass,
|
||||||
|
},
|
||||||
|
}), {
|
||||||
|
totalMassLocation: {x: 0, y: 0},
|
||||||
|
totalMass: 0,
|
||||||
|
count: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!count) return;
|
||||||
|
|
||||||
|
const centerOfMass = {
|
||||||
|
x: totalMassLocation.x / totalMass,
|
||||||
|
y: totalMassLocation.y / totalMass,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log({totalMass, count, totalMassLocation, centerOfMass});
|
||||||
|
|
||||||
|
// Determine total angular momentum
|
||||||
|
const netAngularMomentum = this.sim.objects.reduce((acc, obj, idx) => {
|
||||||
|
// Angular momentum for each object is m * s / d
|
||||||
|
// where d is the distance of the object from the global center of mass
|
||||||
|
// and s is the magnitude of the cross product of v and r
|
||||||
|
const r = {
|
||||||
|
x: obj.position.x - centerOfMass.x,
|
||||||
|
y: obj.position.y - centerOfMass.y,
|
||||||
|
};
|
||||||
|
const v = obj.velocity;
|
||||||
|
const s = v.x * r.y - v.y * r.x;
|
||||||
|
const d = Math.sqrt(r.x ** 2 + r.y ** 2);
|
||||||
|
console.log(`obj ${idx} s`, s, 'd', d);
|
||||||
|
return acc + obj.mass * s / d;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
console.log('net angular momentum', netAngularMomentum);
|
||||||
|
const netAngularVelocity = netAngularMomentum / totalMass;
|
||||||
|
console.log('net angular velocity', netAngularVelocity);
|
||||||
|
|
||||||
|
// TODO: Apply rotation...
|
||||||
|
|
||||||
// Determine average momentum
|
// Determine average momentum
|
||||||
const netMomentum = {x: 0, y: 0};
|
const netMomentum = this.sim.objects.reduce((acc, obj) => ({
|
||||||
let totalMass = 0;
|
x: acc.x + obj.mass * obj.velocity.x,
|
||||||
let count = 0;
|
y: acc.y + obj.mass * obj.velocity.y,
|
||||||
this.sim.objects.forEachObject(obj => {
|
}), { x: 0, y: 0 });
|
||||||
count++;
|
|
||||||
netMomentum.x += obj.mass * obj.velocity.x;
|
|
||||||
netMomentum.y += obj.mass * obj.velocity.y;
|
|
||||||
totalMass += obj.mass;
|
|
||||||
});
|
|
||||||
if (!count) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const netVelocity = {
|
const netVelocity = {
|
||||||
x: netMomentum.x / totalMass,
|
x: netMomentum.x / totalMass,
|
||||||
y: netMomentum.y / totalMass,
|
y: netMomentum.y / totalMass,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply offset to all object velocities
|
// Apply offset to all object velocities
|
||||||
this.sim.objects.forEachObject(obj => {
|
this.sim.objects.forEachObject(obj => {
|
||||||
obj.velocity.x -= netVelocity.x;
|
obj.velocity.x -= netVelocity.x;
|
||||||
obj.velocity.y -= netVelocity.y;
|
obj.velocity.y -= netVelocity.y;
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Zero net angular momentum
|
|
||||||
|
|
||||||
// Cancel panning
|
// Cancel panning
|
||||||
this.sim.pointer.panning = undefined;
|
this.sim.pointer.panning = undefined;
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user