started vector helper refactor
This commit is contained in:
parent
3a855b478f
commit
0483a8ab52
@ -46,6 +46,7 @@ export class Pointer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
el.addEventListener('pointerleave', e => {
|
el.addEventListener('pointerleave', e => {
|
||||||
|
console.log('pointerleave', {x: e.clientX, y: e.clientY});
|
||||||
this.handlePointerUp({x: e.clientX, y: e.clientY});
|
this.handlePointerUp({x: e.clientX, y: e.clientY});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -55,6 +56,13 @@ export class Pointer {
|
|||||||
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', () => {
|
||||||
|
console.log('window focus');
|
||||||
|
});
|
||||||
|
el.addEventListener('blur', () => {
|
||||||
|
console.log('window blur');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePointerDown({x: clientX, y: clientY}) {
|
handlePointerDown({x: clientX, y: clientY}) {
|
||||||
|
|||||||
103
system.js
103
system.js
@ -1,5 +1,10 @@
|
|||||||
import {OBJECT_HISTORY_SIZE} from './config.js';
|
import {OBJECT_HISTORY_SIZE} from './config.js';
|
||||||
import {MassObject} from './object.js';
|
import {MassObject} from './object.js';
|
||||||
|
import {
|
||||||
|
add, cross, direction, div, magnitude, mult,
|
||||||
|
square,
|
||||||
|
sub, weightedAvg, zero
|
||||||
|
} from './vector.js';
|
||||||
|
|
||||||
export class System {
|
export class System {
|
||||||
objects = [];
|
objects = [];
|
||||||
@ -31,41 +36,31 @@ export class System {
|
|||||||
this.doneCreatingObject();
|
this.doneCreatingObject();
|
||||||
this.deselect();
|
this.deselect();
|
||||||
// Convert pointer velocity to simulation scale
|
// Convert pointer velocity to simulation scale
|
||||||
const pointer = {...this.sim.pointer.latestVelocity};
|
obj.velocity = div(this.sim.pointer.latestVelocity, this.sim.display.scale);
|
||||||
obj.velocity.x = pointer.x / this.sim.display.scale;
|
|
||||||
obj.velocity.y = pointer.y / this.sim.display.scale;
|
|
||||||
|
|
||||||
// Including time scale - if time is slow, our motion is relatively faster
|
// Including time scale - if time is slow, our motion is relatively faster
|
||||||
if (this.sim.getOption('compensate.timeScale')) {
|
if (this.sim.getOption('compensate.timeScale')) {
|
||||||
obj.velocity.x /= this.sim.timeScale;
|
obj.velocity = div(obj.velocity, this.sim.timeScale);
|
||||||
obj.velocity.y /= this.sim.timeScale;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.sim.panning?.velocity) {
|
if (this.sim.panning?.velocity) {
|
||||||
obj.velocity.x += this.sim.panning.velocity.x;
|
obj.velocity = add(obj.velocity, this.sim.panning.velocity);
|
||||||
obj.velocity.y += this.sim.panning.velocity.y;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePointerMove({x, y}) {
|
handlePointerMove(r) {
|
||||||
// If the cursor moves while creating an object, or while an object is selected,
|
// If the cursor moves while creating an object, or while an object is selected,
|
||||||
// update the position using the pointer motion but the velocity using the pointer velocity
|
// update the position using the pointer motion but the velocity using the pointer velocity
|
||||||
const obj = this.getSelectedOrCreating();
|
const obj = this.getSelectedOrCreating();
|
||||||
if (obj === undefined) return;
|
if (obj === undefined) return;
|
||||||
const start = this.selectedObjectStart;
|
const start = this.selectedObjectStart;
|
||||||
obj.position.x = start.x + (x - start.pointer.x);
|
obj.position = add(start, sub(r, start.pointer));
|
||||||
obj.position.y = start.y + (y - start.pointer.y);
|
obj.velocity = zero;
|
||||||
obj.velocity.x = 0;
|
|
||||||
obj.velocity.y = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// elapsedTime is given in milliseconds
|
|
||||||
frame(elapsedTime) {
|
frame(elapsedTime) {
|
||||||
// If we're creating an object, increment its mass
|
// If we're creating an object, increment its mass
|
||||||
// with the mass creation rate accelerating over time
|
// with the mass creation rate accelerating over time
|
||||||
|
|
||||||
// Scaling this parameter because of millisecond conversion
|
|
||||||
|
|
||||||
if (this.creatingObject !== undefined) {
|
if (this.creatingObject !== undefined) {
|
||||||
const obj = this.objects[this.creatingObject];
|
const obj = this.objects[this.creatingObject];
|
||||||
let massCreationRate = this.sim.getOption('param.massCreationRate');
|
let massCreationRate = this.sim.getOption('param.massCreationRate');
|
||||||
@ -90,10 +85,13 @@ export class System {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.position.x += elapsedTime *
|
obj.position = add(obj.position, mult(
|
||||||
(obj.velocity.x + 1 / 2 * obj.currentAcceleration.x * elapsedTime);
|
elapsedTime,
|
||||||
obj.position.y += elapsedTime *
|
add(
|
||||||
(obj.velocity.y + 1 / 2 * obj.currentAcceleration.y * elapsedTime);
|
obj.velocity,
|
||||||
|
mult(obj.currentAcceleration, elapsedTime / 2)
|
||||||
|
),
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Collisions
|
// Collisions
|
||||||
@ -125,12 +123,10 @@ export class System {
|
|||||||
// Set position = center of mass
|
// Set position = center of mass
|
||||||
// Set velocity = total momentum / total mass
|
// Set velocity = total momentum / total mass
|
||||||
// Combine forces
|
// Combine forces
|
||||||
// Sdd masses
|
// Add masses
|
||||||
// Sverage color
|
// Average color
|
||||||
S.position.x = (S.position.x * S.mass + T.position.x * T.mass) / (S.mass + T.mass);
|
S.position = weightedAvg(S.position, S.mass, T.position, T.mass);
|
||||||
S.position.y = (S.position.y * S.mass + T.position.y * T.mass) / (S.mass + T.mass);
|
S.velocity = weightedAvg(S.velocity, S.mass, T.velocity, T.mass);
|
||||||
S.velocity.x = (S.velocity.x * S.mass + T.velocity.x * T.mass) / (S.mass + T.mass);
|
|
||||||
S.velocity.y = (S.velocity.y * S.mass + T.velocity.y * T.mass) / (S.mass + T.mass);
|
|
||||||
S.forces.push(...T.forces);
|
S.forces.push(...T.forces);
|
||||||
S.mass += T.mass;
|
S.mass += T.mass;
|
||||||
S.color = {
|
S.color = {
|
||||||
@ -150,12 +146,8 @@ export class System {
|
|||||||
// Predict velocities
|
// Predict velocities
|
||||||
this.forEachObject(obj => {
|
this.forEachObject(obj => {
|
||||||
const acceleration = {...obj.acceleration};
|
const acceleration = {...obj.acceleration};
|
||||||
obj.acceleration = {
|
obj.acceleration = div(add(obj.currentAcceleration, acceleration), 2);
|
||||||
x: (obj.currentAcceleration.x + acceleration.x) / 2,
|
obj.velocity = add(obj.velocity, mult(obj.acceleration, elapsedTime));
|
||||||
y: (obj.currentAcceleration.y + acceleration.y) / 2,
|
|
||||||
};
|
|
||||||
obj.velocity.x += obj.acceleration.x * elapsedTime;
|
|
||||||
obj.velocity.y += obj.acceleration.y * elapsedTime;
|
|
||||||
|
|
||||||
// Append to object history
|
// Append to object history
|
||||||
obj.history.push({position: {...obj.position}});
|
obj.history.push({position: {...obj.position}});
|
||||||
@ -177,18 +169,18 @@ export class System {
|
|||||||
if (this.sim.getOption('debug.objectsInfo')) {
|
if (this.sim.getOption('debug.objectsInfo')) {
|
||||||
const aliveOnly = this.sim.getOption('debug.aliveObjects');
|
const aliveOnly = this.sim.getOption('debug.aliveObjects');
|
||||||
this.forEachObject((obj, i) => {
|
this.forEachObject((obj, i) => {
|
||||||
const speed = Math.pow(obj.velocity.x ** 2 + obj.velocity.y ** 2, 1 / 2);
|
const speed = magnitude(obj.velocity);
|
||||||
const accel = Math.pow(obj.acceleration.x ** 2 + obj.acceleration.y ** 2, 1 / 2);
|
const accel = magnitude(obj.acceleration);
|
||||||
// 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 velocityDir = direction(obj.velocity);
|
||||||
const accelDir = Math.atan2(-obj.acceleration.y, obj.acceleration.x) * 180 / Math.PI;
|
const accelDir = direction(obj.acceleration);
|
||||||
const {r, g, b} = obj.color;
|
const {r, g, b} = obj.color;
|
||||||
this.sim.info[`Object ${i}`] = [
|
this.sim.info[`Object ${i}`] = [
|
||||||
`<span style="background-color: rgb(${r},${g},${b});"> </span>`,
|
`<span style="background-color: rgb(${r},${g},${b});"> </span>`,
|
||||||
`${obj.position.x.toPrecision(4)}, `,
|
`${obj.position.x.toPrecision(4)}, `,
|
||||||
`${obj.position.y.toPrecision(4)}, `,
|
`${obj.position.y.toPrecision(4)}, `,
|
||||||
`${obj.mass.toPrecision(4)} kg, `,
|
`${obj.mass.toPrecision(4)} kg, `,
|
||||||
`${speed.toPrecision(2)} m/s, ${direction.toPrecision(2)}°`,
|
`${speed.toPrecision(2)} m/s, ${velocityDir.toPrecision(2)}°`,
|
||||||
`${accel.toPrecision(2)} m/s<sup>2</sup>, ${accelDir.toPrecision(2)}°`,
|
`${accel.toPrecision(2)} m/s<sup>2</sup>, ${accelDir.toPrecision(2)}°`,
|
||||||
`Alive: ${obj.alive}`,
|
`Alive: ${obj.alive}`,
|
||||||
];
|
];
|
||||||
@ -307,7 +299,7 @@ export class System {
|
|||||||
this.selectedObjectStart = undefined;
|
this.selectedObjectStart = undefined;
|
||||||
this.forEachObject((obj, i) => {
|
this.forEachObject((obj, 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 = magnitude(sub(obj.position, {x, y}));
|
||||||
if (dist <= obj.radius) {
|
if (dist <= obj.radius) {
|
||||||
idx = i;
|
idx = i;
|
||||||
return null;
|
return null;
|
||||||
@ -352,13 +344,13 @@ export class System {
|
|||||||
});
|
});
|
||||||
this.forEachObject((A, i) => {
|
this.forEachObject((A, i) => {
|
||||||
this.forEachObject(B => {
|
this.forEachObject(B => {
|
||||||
const dx = (B.position.x - A.position.x);
|
const r = sub(B.position, A.position);
|
||||||
const dy = (B.position.y - A.position.y);
|
const dSquared = square(r);
|
||||||
const dSquared = dx ** 2 + dy ** 2;
|
|
||||||
const d = Math.sqrt(dSquared);
|
const d = Math.sqrt(dSquared);
|
||||||
const F = gravity * A.mass * B.mass / dSquared;
|
const F = gravity * A.mass * B.mass / dSquared;
|
||||||
const Fx = F * dx / d;
|
const Fx = F * r.x / d;
|
||||||
const Fy = F * dy / d;
|
const Fy = F * r.y / d;
|
||||||
|
// Equal and opposite forces
|
||||||
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});
|
||||||
}, {alive: true, startWith: i + 1});
|
}, {alive: true, startWith: i + 1});
|
||||||
@ -375,26 +367,19 @@ export class System {
|
|||||||
this.reduce((acc, obj) => ({
|
this.reduce((acc, obj) => ({
|
||||||
count: acc.count + 1,
|
count: acc.count + 1,
|
||||||
totalMass: acc.totalMass + obj.mass,
|
totalMass: acc.totalMass + obj.mass,
|
||||||
totalMassLocation: {
|
totalMassLocation: add(acc.totalMassLocation,
|
||||||
x: acc.totalMassLocation.x + obj.position.x * obj.mass,
|
mult(obj.position, obj.mass)),
|
||||||
y: acc.totalMassLocation.y + obj.position.y * obj.mass,
|
|
||||||
},
|
|
||||||
}), {
|
}), {
|
||||||
totalMassLocation: {x: 0, y: 0},
|
totalMassLocation: {x: 0, y: 0},
|
||||||
totalMass: 0,
|
totalMass: 0,
|
||||||
count: 0,
|
count: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const centerOfMass = count ? {
|
const centerOfMass = count ? div(totalMassLocation, totalMass) : zero;
|
||||||
x: totalMassLocation.x / totalMass,
|
|
||||||
y: totalMassLocation.y / totalMass,
|
|
||||||
} : {x: 0, y: 0};
|
|
||||||
|
|
||||||
// Determine average momentum
|
// Determine average momentum
|
||||||
const netMomentum = this.reduce((acc, obj) => ({
|
const netMomentum = this.reduce((acc, obj) =>
|
||||||
x: acc.x + obj.mass * obj.velocity.x,
|
add(acc, mult(obj.velocity, obj.mass)), zero);
|
||||||
y: acc.y + obj.mass * obj.velocity.y,
|
|
||||||
}), {x: 0, y: 0});
|
|
||||||
|
|
||||||
return {totalMass, count, totalMassLocation, centerOfMass, netMomentum};
|
return {totalMass, count, totalMassLocation, centerOfMass, netMomentum};
|
||||||
}
|
}
|
||||||
@ -408,12 +393,8 @@ export class System {
|
|||||||
// Angular momentum for each object is m * s / d
|
// Angular momentum for each object is m * s / d
|
||||||
// where d is the distance of the object from the global center of mass
|
// 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
|
// and s is the magnitude of the cross product of v and r
|
||||||
const r = {
|
const r = sub(obj.position, centerOfMass);
|
||||||
x: obj.position.x - centerOfMass.x,
|
const s = cross(obj.velocity, r);
|
||||||
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);
|
const d = Math.sqrt(r.x ** 2 + r.y ** 2);
|
||||||
return acc + obj.mass * s / d;
|
return acc + obj.mass * s / d;
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|||||||
46
vector.js
Normal file
46
vector.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
export const dot = (a, b) => a.x * b.x + a.y * b.y;
|
||||||
|
export const cross = (a, b) => a.x * b.y - a.y * b.x;
|
||||||
|
export const add = (a, b) => ({x: a.x + b.x, y: a.y + b.y});
|
||||||
|
export const sub = (a, b) => ({x: a.x - b.x, y: a.y - b.y});
|
||||||
|
export const square = ({x, y}) => x ** 2 + y ** 2;
|
||||||
|
export const magnitude = ({x, y}) => Math.sqrt(square({x, y}));
|
||||||
|
export const mult = (v, m) => {
|
||||||
|
if (v.x !== undefined) return {x: v.x * m, y: v.y * m};
|
||||||
|
else return {x: m.x * v, y: m.y * v};
|
||||||
|
};
|
||||||
|
export const div = (v, m) => ({x: v.x / m, y: v.y / m});
|
||||||
|
export const zero = {x: 0, y: 0};
|
||||||
|
export const weightedAvg = (items) => {
|
||||||
|
let res = zero;
|
||||||
|
let W = 0;
|
||||||
|
for (const [v, w] of items) {
|
||||||
|
res = add(res, mult(v, w));
|
||||||
|
W += w;
|
||||||
|
}
|
||||||
|
return div(res, W);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Angle is given counterclockwise, assuming screen coordinates
|
||||||
|
export const direction = ({x, y}) => Math.atan2(-y, x) * 180 / Math.PI;
|
||||||
|
|
||||||
|
export class Vector {
|
||||||
|
x = undefined;
|
||||||
|
y = undefined;
|
||||||
|
|
||||||
|
constructor({x, y}) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
dot(v) {return dot(this, v);}
|
||||||
|
cross(v) {return cross(this, v);}
|
||||||
|
add(v) {return new Vector(add(this, v));}
|
||||||
|
sub(v) {return new Vector(sub(this, v));}
|
||||||
|
magnitude() {return magnitude(this);}
|
||||||
|
mult(m) {return new Vector(mult(this, m));}
|
||||||
|
div(m) {return new Vector(div(this, m));}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {x: this.x, y: this.y};
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user