path traces; collisions
This commit is contained in:
parent
952ea553c7
commit
103fd7f694
@ -6,6 +6,7 @@ export const DISPLAY_CURRENT_SCALE = true;
|
|||||||
export const DISPLAY_CURRENT_MODE = false;
|
export const DISPLAY_CURRENT_MODE = false;
|
||||||
export const DISPLAY_VELOCITY_VECTORS = true;
|
export const DISPLAY_VELOCITY_VECTORS = true;
|
||||||
export const DISPLAY_ACCELERATION_VECTORS = true;
|
export const DISPLAY_ACCELERATION_VECTORS = true;
|
||||||
|
export const DISPLAY_PATH_TRACES = false;
|
||||||
|
|
||||||
// VELOCITY
|
// VELOCITY
|
||||||
export const VELOCITY_VECTOR_SCALE = 5E0;
|
export const VELOCITY_VECTOR_SCALE = 5E0;
|
||||||
@ -19,6 +20,11 @@ export const ACCELERATION_VECTOR_COLOR = 'rgb(0, 128, 0)'; // optionally set to
|
|||||||
export const ACCELERATION_VECTOR_WIDTH = 1.5;
|
export const ACCELERATION_VECTOR_WIDTH = 1.5;
|
||||||
export const ACCELERATION_VECTOR_ARROWHEAD = true;
|
export const ACCELERATION_VECTOR_ARROWHEAD = true;
|
||||||
|
|
||||||
|
// PATH TRACES
|
||||||
|
// export const PATH_TRACES_COLOR = 'rgb(128, 128, 0)'; // optionally set to 'object color'
|
||||||
|
export const PATH_TRACES_COLOR = 'object color';
|
||||||
|
export const PATH_TRACES_WIDTH = 1.5;
|
||||||
|
|
||||||
// SCALING FACTORS
|
// SCALING FACTORS
|
||||||
export const MASS_CREATION_RATE = 1E1;
|
export const MASS_CREATION_RATE = 1E1;
|
||||||
export const POINTER_HISTORY_SIZE = 15;
|
export const POINTER_HISTORY_SIZE = 15;
|
||||||
@ -45,3 +51,4 @@ export const MODE_PAN_VIEW = 'pan-view';
|
|||||||
// OPTIONS
|
// OPTIONS
|
||||||
export const PAUSE_DURING_CREATION = true;
|
export const PAUSE_DURING_CREATION = true;
|
||||||
export const PAUSE_DURING_SELECTION = true;
|
export const PAUSE_DURING_SELECTION = true;
|
||||||
|
export const MERGE_ON_COLLIDE = true;
|
||||||
|
|||||||
63
display.js
63
display.js
@ -13,8 +13,8 @@ import {
|
|||||||
OFFSCREEN_OBJECT_LINE_WIDTH,
|
OFFSCREEN_OBJECT_LINE_WIDTH,
|
||||||
OFFSCREEN_OBJECT_ARROWHEAD_LENGTH,
|
OFFSCREEN_OBJECT_ARROWHEAD_LENGTH,
|
||||||
DISPLAY_CANVAS_SIZE,
|
DISPLAY_CANVAS_SIZE,
|
||||||
DISPLAY_VELOCITY_VECTORS,
|
PATH_TRACES_COLOR,
|
||||||
DISPLAY_ACCELERATION_VECTORS,
|
PATH_TRACES_WIDTH,
|
||||||
} from './config.js';
|
} from './config.js';
|
||||||
|
|
||||||
export class Display {
|
export class Display {
|
||||||
@ -63,6 +63,13 @@ export class Display {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
simToScreen(x, y) {
|
||||||
|
return {
|
||||||
|
x: (x - this.viewOrigin.x) * this.scale,
|
||||||
|
y: (y - this.viewOrigin.y) * this.scale,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fullscreen() {
|
fullscreen() {
|
||||||
this.canvas.width = document.documentElement.clientWidth;
|
this.canvas.width = document.documentElement.clientWidth;
|
||||||
this.canvas.height = document.documentElement.clientHeight;
|
this.canvas.height = document.documentElement.clientHeight;
|
||||||
@ -77,8 +84,7 @@ export class Display {
|
|||||||
ctx.fillRect(this.viewOrigin.x, this.viewOrigin.y, this.width, this.height);
|
ctx.fillRect(this.viewOrigin.x, this.viewOrigin.y, this.width, this.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawObject(idx) {
|
drawObject(obj) {
|
||||||
const obj = this.sim.objects.object(idx);
|
|
||||||
const ctx = this.ctx;
|
const ctx = this.ctx;
|
||||||
const {r, g, b} = obj.color;
|
const {r, g, b} = obj.color;
|
||||||
const {x, y} = obj.position;
|
const {x, y} = obj.position;
|
||||||
@ -90,6 +96,47 @@ export class Display {
|
|||||||
const oy = this.viewOrigin.y;
|
const oy = this.viewOrigin.y;
|
||||||
const cx = ox + W / 2;
|
const cx = ox + W / 2;
|
||||||
const cy = oy + H / 2;
|
const cy = oy + H / 2;
|
||||||
|
|
||||||
|
// Draw path traces
|
||||||
|
if (this.sim.getOption('display.traces') && obj.history?.length) {
|
||||||
|
ctx.strokeStyle = PATH_TRACES_COLOR === 'object color' ?
|
||||||
|
`rgb(${r}, ${g}, ${b})` : PATH_TRACES_COLOR;
|
||||||
|
ctx.lineWidth = PATH_TRACES_WIDTH / this.scale;
|
||||||
|
ctx.beginPath();
|
||||||
|
let dash = false;
|
||||||
|
const skip = 1;
|
||||||
|
let skipped = 0;
|
||||||
|
{
|
||||||
|
const [{position}] = obj.history;
|
||||||
|
const x = position.x;
|
||||||
|
const y = position.y;
|
||||||
|
|
||||||
|
if (Math.abs(x - cx) <= W / 2 &&
|
||||||
|
Math.abs(y - cy) <= H / 2) {
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
dash = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 1; i < obj.history.length; i++) {
|
||||||
|
if (++skipped < skip) continue;
|
||||||
|
skipped = 0;
|
||||||
|
const {position} = obj.history[i];
|
||||||
|
const x = position.x;
|
||||||
|
const y = position.y;
|
||||||
|
if (dash) {
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
dash = false;
|
||||||
|
} else if (Math.abs(x - cx) <= W / 2 &&
|
||||||
|
Math.abs(y - cy) <= H / 2) {
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
dash = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!obj.alive) return;
|
||||||
|
|
||||||
// If the object is outside the display area, draw an arrow at the edge of the display
|
// If the object is outside the display area, draw an arrow at the edge of the display
|
||||||
if (Math.abs(x - cx) - radius >= W / 2 ||
|
if (Math.abs(x - cx) - radius >= W / 2 ||
|
||||||
Math.abs(y - cy) - radius >= H / 2) {
|
Math.abs(y - cy) - radius >= H / 2) {
|
||||||
@ -146,7 +193,7 @@ export class Display {
|
|||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
||||||
// Draw arrow for the velocity
|
// Draw arrow for the velocity
|
||||||
if (DISPLAY_VELOCITY_VECTORS) {
|
if (this.sim.getOption('show.velocity')) {
|
||||||
const speed = Math.sqrt(vx ** 2 + vy ** 2);
|
const speed = Math.sqrt(vx ** 2 + vy ** 2);
|
||||||
const endVx = x + VELOCITY_VECTOR_SCALE * vx / speed * Math.log(speed);
|
const endVx = x + VELOCITY_VECTOR_SCALE * vx / speed * Math.log(speed);
|
||||||
const endVy = y + VELOCITY_VECTOR_SCALE * vy / speed * Math.log(speed);
|
const endVy = y + VELOCITY_VECTOR_SCALE * vy / speed * Math.log(speed);
|
||||||
@ -162,7 +209,7 @@ export class Display {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw arrow for acceleration
|
// Draw arrow for acceleration
|
||||||
if (DISPLAY_ACCELERATION_VECTORS) {
|
if (this.sim.getOption('show.acceleration')) {
|
||||||
const accelerationMagnitude = Math.sqrt(acceleration.x ** 2 + acceleration.y ** 2);
|
const accelerationMagnitude = Math.sqrt(acceleration.x ** 2 + acceleration.y ** 2);
|
||||||
const endAx = x + ACCELERATION_VECTOR_SCALE * acceleration.x /
|
const endAx = x + ACCELERATION_VECTOR_SCALE * acceleration.x /
|
||||||
accelerationMagnitude * Math.log(accelerationMagnitude);
|
accelerationMagnitude * Math.log(accelerationMagnitude);
|
||||||
@ -181,9 +228,7 @@ export class Display {
|
|||||||
}
|
}
|
||||||
|
|
||||||
drawObjects() {
|
drawObjects() {
|
||||||
for (let i = 0; i < this.sim.objects.length; i++) {
|
this.sim.objects.forEachObject(obj => this.drawObject(obj), null);
|
||||||
this.drawObject(i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
drawArrow(startX, startY, endX, endY, {style, width, arrowhead, arrowheadLength, fill, ifShort}) {
|
drawArrow(startX, startY, endX, endY, {style, width, arrowhead, arrowheadLength, fill, ifShort}) {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ body {
|
|||||||
background-color: #000;
|
background-color: #000;
|
||||||
color: #8f8;
|
color: #8f8;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 14pt;
|
font-size: 12pt;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
export class MassObject {
|
export class MassObject {
|
||||||
index = undefined;
|
|
||||||
mass = 0;
|
mass = 0;
|
||||||
density = 1;
|
density = 1;
|
||||||
position = {x: undefined, y: undefined};
|
position = {x: undefined, y: undefined};
|
||||||
@ -8,9 +7,10 @@ export class MassObject {
|
|||||||
color = {r: undefined, g: undefined, b: undefined};
|
color = {r: undefined, g: undefined, b: undefined};
|
||||||
created = undefined;
|
created = undefined;
|
||||||
forces = []; // [{x, y}]
|
forces = []; // [{x, y}]
|
||||||
|
history = [];
|
||||||
|
alive = true;
|
||||||
|
|
||||||
constructor(x, y, index) {
|
constructor(x, y) {
|
||||||
this.index = index;
|
|
||||||
this.position.x = x;
|
this.position.x = x;
|
||||||
this.position.y = y;
|
this.position.y = y;
|
||||||
this.color.r = Math.random() * 256;
|
this.color.r = Math.random() * 256;
|
||||||
|
|||||||
95
objects.js
95
objects.js
@ -83,8 +83,7 @@ export class Objects {
|
|||||||
start: {x: undefined, y: undefined},
|
start: {x: undefined, y: undefined},
|
||||||
end: {x: undefined, y: undefined},
|
end: {x: undefined, y: undefined},
|
||||||
};
|
};
|
||||||
for (let i = 0; i < this.objects.length; i++) {
|
this.forEachObject(obj => {
|
||||||
const obj = this.objects[i];
|
|
||||||
if (box.start.x === undefined) {
|
if (box.start.x === undefined) {
|
||||||
box.start = {...obj.position};
|
box.start = {...obj.position};
|
||||||
box.end = {...obj.position};
|
box.end = {...obj.position};
|
||||||
@ -94,19 +93,18 @@ export class Objects {
|
|||||||
if (obj.position.y < box.start.y) box.start.y = obj.position.y;
|
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;
|
if (obj.position.y > box.end.y) box.end.y = obj.position.y;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
objectAtLocation(x, y) {
|
objectAtLocation(x, y) {
|
||||||
for (let i = 0; i < this.objects.length; i++) {
|
this.forEachObject(obj => {
|
||||||
const obj = this.objects[i];
|
|
||||||
// If distance to object is less than object's radius, we are touching the object
|
// If distance to object is less than object's radius, we are touching the object
|
||||||
const dist = Math.pow((obj.position.x - x)**2 + (obj.position.y - y)**2, 1/2);
|
const dist = Math.pow((obj.position.x - x)**2 + (obj.position.y - y)**2, 1/2);
|
||||||
if (dist <= obj.radius) {
|
if (dist <= obj.radius) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePointerDown({x, y}) {
|
handlePointerDown({x, y}) {
|
||||||
@ -138,15 +136,23 @@ export class Objects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cb: (obj, idx) => {}
|
||||||
|
forEachObject(cb, alive = true, startWith = 0) {
|
||||||
|
for (let i = startWith; i < this.objects.length; i++) {
|
||||||
|
const obj = this.objects[i];
|
||||||
|
if (alive === null || alive == obj.alive) {
|
||||||
|
cb(obj, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
computeForces() {
|
computeForces() {
|
||||||
if (this.objects.length < 2) return;
|
if (this.objects.length < 2) return;
|
||||||
for (let i = 0; i < this.objects.length; i++) {
|
this.forEachObject(obj => {
|
||||||
this.objects[i].forces = [];
|
obj.forces = [];
|
||||||
}
|
});
|
||||||
for (let i = 0; i < this.objects.length; i++) {
|
this.forEachObject((A, i) => {
|
||||||
const A = this.objects[i];
|
this.forEachObject(B => {
|
||||||
for (let j = i + 1; j < this.objects.length; j++) {
|
|
||||||
const B = this.objects[j];
|
|
||||||
const dx = (B.position.x - A.position.x);
|
const dx = (B.position.x - A.position.x);
|
||||||
const dy = (B.position.y - A.position.y);
|
const dy = (B.position.y - A.position.y);
|
||||||
const dSquared = dx ** 2 + dy ** 2;
|
const dSquared = dx ** 2 + dy ** 2;
|
||||||
@ -156,14 +162,12 @@ 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);
|
||||||
}
|
});
|
||||||
// Also compute acceleration
|
// Also compute acceleration
|
||||||
for (let i = 0; i < this.objects.length; i++) {
|
this.forEachObject(obj => {
|
||||||
const obj = this.objects[i];
|
|
||||||
obj.acceleration = obj.getAcceleration();
|
obj.acceleration = obj.getAcceleration();
|
||||||
}
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
computeFrame(elapsedTime) {
|
computeFrame(elapsedTime) {
|
||||||
@ -181,22 +185,55 @@ export class Objects {
|
|||||||
|
|
||||||
if (this.sim.playing) {
|
if (this.sim.playing) {
|
||||||
// Predict positions (Velocity verlet method)
|
// Predict positions (Velocity verlet method)
|
||||||
for (let i = 0; i < this.objects.length; i++) {
|
this.forEachObject(obj => {
|
||||||
const obj = this.objects[i];
|
|
||||||
obj.currentAcceleration = {...obj.acceleration};
|
obj.currentAcceleration = {...obj.acceleration};
|
||||||
|
|
||||||
obj.position.x += elapsedTime *
|
obj.position.x += elapsedTime *
|
||||||
(obj.velocity.x + 1/2 * obj.currentAcceleration.x * elapsedTime);
|
(obj.velocity.x + 1/2 * obj.currentAcceleration.x * elapsedTime);
|
||||||
obj.position.y += elapsedTime *
|
obj.position.y += elapsedTime *
|
||||||
(obj.velocity.y + 1/2 * obj.currentAcceleration.y * elapsedTime);
|
(obj.velocity.y + 1/2 * obj.currentAcceleration.y * elapsedTime);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Collisions
|
||||||
|
this.forEachObject((A, i) => {
|
||||||
|
this.forEachObject((B, j) => {
|
||||||
|
const dx = (B.position.x - A.position.x);
|
||||||
|
const dy = (B.position.y - A.position.y);
|
||||||
|
const dSquared = dx ** 2 + dy ** 2;
|
||||||
|
const d = Math.sqrt(dSquared);
|
||||||
|
if (d < A.radius + B.radius) {
|
||||||
|
// Merge B into A:
|
||||||
|
// Set position = center of mass
|
||||||
|
// Set velocity = total momentum / total mass
|
||||||
|
// Combine forces
|
||||||
|
// Add masses
|
||||||
|
// Average color?
|
||||||
|
// If B was selected or being created, select A instead
|
||||||
|
// Remove B using splice
|
||||||
|
A.position.x = (A.position.x * A.mass + B.position.x * B.mass) / (A.mass + B.mass);
|
||||||
|
A.position.y = (A.position.y * A.mass + B.position.y * B.mass) / (A.mass + B.mass);
|
||||||
|
A.velocity.x = (A.velocity.x * A.mass + B.velocity.x * B.mass) / (A.mass + B.mass);
|
||||||
|
A.velocity.y = (A.velocity.y * A.mass + B.velocity.y * B.mass) / (A.mass + B.mass);
|
||||||
|
A.forces.push(...B.forces);
|
||||||
|
A.mass += B.mass;
|
||||||
|
A.color = {
|
||||||
|
r: (A.color.r + B.color.r) / 2,
|
||||||
|
g: (A.color.g + B.color.g) / 2,
|
||||||
|
b: (A.color.b + B.color.b) / 2,
|
||||||
|
};
|
||||||
|
if (this.creatingObject === j) this.creatingObject = i;
|
||||||
|
if (this.selectedObject === j) this.selectedObject = i;
|
||||||
|
B.alive = false;
|
||||||
|
B.forces = [];
|
||||||
}
|
}
|
||||||
|
}, true, i + 1);
|
||||||
|
});
|
||||||
|
|
||||||
// Recompute forces
|
// Recompute forces
|
||||||
this.computeForces();
|
this.computeForces();
|
||||||
|
|
||||||
// Predict velocities
|
// Predict velocities
|
||||||
for (let i = 0; i < this.objects.length; i++) {
|
this.forEachObject(obj => {
|
||||||
const obj = this.objects[i];
|
|
||||||
const acceleration = {...obj.acceleration};
|
const acceleration = {...obj.acceleration};
|
||||||
obj.acceleration = {
|
obj.acceleration = {
|
||||||
x: (obj.currentAcceleration.x + acceleration.x) / 2,
|
x: (obj.currentAcceleration.x + acceleration.x) / 2,
|
||||||
@ -204,15 +241,17 @@ export class Objects {
|
|||||||
};
|
};
|
||||||
obj.velocity.x += obj.acceleration.x * elapsedTime;
|
obj.velocity.x += obj.acceleration.x * elapsedTime;
|
||||||
obj.velocity.y += obj.acceleration.y * elapsedTime;
|
obj.velocity.y += obj.acceleration.y * elapsedTime;
|
||||||
}
|
|
||||||
// TODO: Collisions
|
|
||||||
|
|
||||||
|
// Append to object history
|
||||||
|
// TODO: enforce object history length
|
||||||
|
// TODO: store object color changes in history
|
||||||
|
obj.history.push({position: {...obj.position}});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display objects info
|
// Display objects info
|
||||||
if (DISPLAY_OBJECTS_INFO) {
|
if (DISPLAY_OBJECTS_INFO) {
|
||||||
for (let i = 0; i < this.objects.length; i++) {
|
this.forEachObject(obj => {
|
||||||
const obj = this.objects[i];
|
|
||||||
const speed = Math.pow(obj.velocity.x ** 2 + obj.velocity.y ** 2, 1/2);
|
const speed = Math.pow(obj.velocity.x ** 2 + obj.velocity.y ** 2, 1/2);
|
||||||
// Invert y so that the angle is counterclockwise from x-axis
|
// Invert y so that the angle is counterclockwise from x-axis
|
||||||
const direction = Math.atan2(-obj.velocity.y, obj.velocity.x) * 180 / Math.PI;
|
const direction = Math.atan2(-obj.velocity.y, obj.velocity.x) * 180 / Math.PI;
|
||||||
@ -222,7 +261,7 @@ export class Objects {
|
|||||||
`${obj.mass.toPrecision(6)} kg, `,
|
`${obj.mass.toPrecision(6)} kg, `,
|
||||||
`${speed.toPrecision(2)} m/s, ${direction.toPrecision(2)}°`,
|
`${speed.toPrecision(2)} m/s, ${direction.toPrecision(2)}°`,
|
||||||
];
|
];
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
DISPLAY_VELOCITY_VECTORS,
|
DISPLAY_VELOCITY_VECTORS,
|
||||||
PAUSE_DURING_CREATION,
|
PAUSE_DURING_CREATION,
|
||||||
PAUSE_DURING_SELECTION,
|
PAUSE_DURING_SELECTION,
|
||||||
|
MERGE_ON_COLLIDE,
|
||||||
} from '../config.js';
|
} from '../config.js';
|
||||||
|
|
||||||
export class Options extends Tool {
|
export class Options extends Tool {
|
||||||
@ -14,12 +15,17 @@ export class Options extends Tool {
|
|||||||
{ type: 'boolean', name: 'creation', title: 'Creation', default: PAUSE_DURING_CREATION },
|
{ type: 'boolean', name: 'creation', title: 'Creation', default: PAUSE_DURING_CREATION },
|
||||||
{ type: 'boolean', name: 'selection', title: 'Selection', default: PAUSE_DURING_SELECTION },
|
{ type: 'boolean', name: 'selection', title: 'Selection', default: PAUSE_DURING_SELECTION },
|
||||||
]}, {
|
]}, {
|
||||||
type: 'group', name: 'show', title: 'Show Vectors',
|
type: 'group', name: 'display', title: 'Display',
|
||||||
items: [
|
items: [
|
||||||
{ type: 'boolean', name: 'velocity', title: 'Velocity', default: DISPLAY_VELOCITY_VECTORS },
|
{ type: 'boolean', name: 'velocity', title: 'Velocity', default: DISPLAY_VELOCITY_VECTORS },
|
||||||
{ type: 'boolean', name: 'acceleration', title: 'Acceleration', default: DISPLAY_ACCELERATION_VECTORS },
|
{ type: 'boolean', name: 'acceleration', title: 'Acceleration', default: DISPLAY_ACCELERATION_VECTORS },
|
||||||
]
|
{ type: 'boolean', name: 'traces', title: 'Trace Path', default: DISPLAY_ACCELERATION_VECTORS },
|
||||||
}];
|
]}, {
|
||||||
|
type: 'group', name: 'collision', title: 'Collisions',
|
||||||
|
items: [
|
||||||
|
{ type: 'boolean', name: 'merge', title: 'Merge', default: MERGE_ON_COLLIDE },
|
||||||
|
]},
|
||||||
|
];
|
||||||
|
|
||||||
values = {};
|
values = {};
|
||||||
|
|
||||||
@ -58,6 +64,7 @@ export class Options extends Tool {
|
|||||||
const heading = document.createElement('h2');
|
const heading = document.createElement('h2');
|
||||||
heading.innerHTML = 'Options';
|
heading.innerHTML = 'Options';
|
||||||
this.div.appendChild(heading);
|
this.div.appendChild(heading);
|
||||||
|
this.div.style.paddingTop = '0px';
|
||||||
for (const item of this.options) {
|
for (const item of this.options) {
|
||||||
const child = this.visitItem(item);
|
const child = this.visitItem(item);
|
||||||
this.div.appendChild(child);
|
this.div.appendChild(child);
|
||||||
|
|||||||
31
tool/zoom.js
31
tool/zoom.js
@ -11,22 +11,18 @@ export class Zoom extends Tool {
|
|||||||
const zoomOut = document.createElement('button');
|
const zoomOut = document.createElement('button');
|
||||||
const zoomIn = document.createElement('button');
|
const zoomIn = document.createElement('button');
|
||||||
const zoomAll = document.createElement('button');
|
const zoomAll = document.createElement('button');
|
||||||
const zeroNetMomentum = document.createElement('button');
|
|
||||||
|
|
||||||
this.div.appendChild(zoomOut);
|
this.div.appendChild(zoomOut);
|
||||||
this.div.appendChild(zoomIn);
|
this.div.appendChild(zoomIn);
|
||||||
this.div.appendChild(document.createElement('br'));
|
this.div.appendChild(document.createElement('br'));
|
||||||
this.div.appendChild(zoomAll);
|
this.div.appendChild(zoomAll);
|
||||||
this.div.appendChild(document.createElement('br'));
|
this.div.appendChild(document.createElement('br'));
|
||||||
this.div.appendChild(zeroNetMomentum);
|
|
||||||
|
|
||||||
zoomAll.classList.add('wide');
|
zoomAll.classList.add('wide');
|
||||||
zeroNetMomentum.classList.add('wide');
|
|
||||||
|
|
||||||
zoomOut.innerHTML = '<h2>Zoom<br>Out</h2>';
|
zoomOut.innerHTML = '<h2>Zoom<br>Out</h2>';
|
||||||
zoomIn.innerHTML = '<h2>Zoom<br>In</h2>';
|
zoomIn.innerHTML = '<h2>Zoom<br>In</h2>';
|
||||||
zoomAll.innerHTML = '<h2>Zoom to Fit</h2>';
|
zoomAll.innerHTML = '<h2>Zoom to Fit</h2>';
|
||||||
zeroNetMomentum.innerHTML = '<h2>Zero Net Momentum</h2>';
|
|
||||||
|
|
||||||
zoomOut.addEventListener('click', (e) => {
|
zoomOut.addEventListener('click', (e) => {
|
||||||
// Aim at center of view
|
// Aim at center of view
|
||||||
@ -60,32 +56,5 @@ export class Zoom extends Tool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
zeroNetMomentum.addEventListener('click', (e) => {
|
|
||||||
const { objects } = this.sim.objects;
|
|
||||||
|
|
||||||
// Find total momentum
|
|
||||||
let totalMomentum = objects.reduce((total, obj) => {
|
|
||||||
const px = obj.mass * obj.velocity.x;
|
|
||||||
const py = obj.mass * obj.velocity.y;
|
|
||||||
return {
|
|
||||||
x: total.x + px,
|
|
||||||
y: total.y + py,
|
|
||||||
};
|
|
||||||
}, {x: 0, y: 0});
|
|
||||||
|
|
||||||
// Find average momentum
|
|
||||||
let averageMomentum = {
|
|
||||||
x: totalMomentum.x / objects.length,
|
|
||||||
y: totalMomentum.y / objects.length,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Subtract average from each
|
|
||||||
for (let i = 0; i < objects.length; i++) {
|
|
||||||
const obj = objects[i];
|
|
||||||
obj.velocity.x -= averageMomentum.x / obj.mass;
|
|
||||||
obj.velocity.y -= averageMomentum.y / obj.mass;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ export class Toolbar {
|
|||||||
this.div = div;
|
this.div = div;
|
||||||
this.sim.div.appendChild(div);
|
this.sim.div.appendChild(div);
|
||||||
div.style.position = 'relative';
|
div.style.position = 'relative';
|
||||||
div.style.width = 'fit-content';
|
div.style.width = '20EM';
|
||||||
div.style.top = 0;
|
div.style.top = 0;
|
||||||
div.style.left = 0;
|
div.style.left = 0;
|
||||||
div.style.zIndex = 2;
|
div.style.zIndex = 2;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user