implemented elastic collisions
This commit is contained in:
parent
f707618ec6
commit
21820c53af
11
object.js
11
object.js
@ -16,7 +16,7 @@ import {
|
|||||||
VELOCITY_VECTOR_COLOR,
|
VELOCITY_VECTOR_COLOR,
|
||||||
VELOCITY_VECTOR_WIDTH,
|
VELOCITY_VECTOR_WIDTH,
|
||||||
} from './config.js';
|
} from './config.js';
|
||||||
import {add, components, copy, direction, div, magnitude, zero} from './vector.js';
|
import {add, components, copy, direction, div, magnitude, mult, square, zero} from './vector.js';
|
||||||
|
|
||||||
export class MassObject {
|
export class MassObject {
|
||||||
sim = undefined;
|
sim = undefined;
|
||||||
@ -31,7 +31,6 @@ export class MassObject {
|
|||||||
forces = []; // [{x, y}]
|
forces = []; // [{x, y}]
|
||||||
history = [];
|
history = [];
|
||||||
alive = true;
|
alive = true;
|
||||||
kineticEnergy = 0;
|
|
||||||
workDoneByPointer = 0;
|
workDoneByPointer = 0;
|
||||||
workDoneByForces = 0;
|
workDoneByForces = 0;
|
||||||
|
|
||||||
@ -92,6 +91,14 @@ export class MassObject {
|
|||||||
return div(netForce, this.mass);
|
return div(netForce, this.mass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get kineticEnergy() {
|
||||||
|
return this.mass * square(this.velocity) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
get momentum() {
|
||||||
|
return mult(this.mass, this.velocity);
|
||||||
|
}
|
||||||
|
|
||||||
drawPath(display) {
|
drawPath(display) {
|
||||||
const {ctx} = display;
|
const {ctx} = display;
|
||||||
const {color: {r, g, b}} = this;
|
const {color: {r, g, b}} = this;
|
||||||
|
|||||||
@ -53,7 +53,12 @@ export class Pointer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePointerDown({x: clientX, y: clientY}) {
|
handlePointerDown({x: clientX, y: clientY, touches}) {
|
||||||
|
// TODO: Check if pointer was already down;
|
||||||
|
// equivalently, e.touches.length > 1
|
||||||
|
|
||||||
|
// Global pinch to zoom? Or only in certain modes?
|
||||||
|
|
||||||
this.updatePointer({x: clientX, y: clientY});
|
this.updatePointer({x: clientX, y: clientY});
|
||||||
|
|
||||||
switch (this.sim.getCurrentMode()) {
|
switch (this.sim.getCurrentMode()) {
|
||||||
|
|||||||
@ -51,6 +51,8 @@ export class Sim {
|
|||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
if (url.host === 'gravity.dev.laddhoffman.com') {
|
if (url.host === 'gravity.dev.laddhoffman.com') {
|
||||||
document.title += ' [Dev]';
|
document.title += ' [Dev]';
|
||||||
|
} else if (url.host === 'gravity.local') {
|
||||||
|
document.title += ' [Local]';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.options = new Options(this, simOptions);
|
this.options = new Options(this, simOptions);
|
||||||
@ -109,6 +111,7 @@ export class Sim {
|
|||||||
requestAnimationFrame(t => this.frame(t));
|
requestAnimationFrame(t => this.frame(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record frame timestamp information
|
||||||
markFrame(t) {
|
markFrame(t) {
|
||||||
const {recentFrames: rfs} = this;
|
const {recentFrames: rfs} = this;
|
||||||
if (!rfs.length) {
|
if (!rfs.length) {
|
||||||
@ -133,6 +136,7 @@ export class Sim {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toJSON is used to export the state of the simulation
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
dateSaved: new Date().toISOString(),
|
dateSaved: new Date().toISOString(),
|
||||||
@ -145,6 +149,7 @@ export class Sim {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fromJSON is used to import the state of the simulation
|
||||||
fromJSON(state) {
|
fromJSON(state) {
|
||||||
this.pause();
|
this.pause();
|
||||||
this.system.fromJSON(state.system);
|
this.system.fromJSON(state.system);
|
||||||
@ -165,6 +170,7 @@ export class Sim {
|
|||||||
return this.display.screenToSim(x, y);
|
return this.display.screenToSim(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start the simulation if it's not already running
|
||||||
play() {
|
play() {
|
||||||
this.playing = true;
|
this.playing = true;
|
||||||
|
|
||||||
@ -176,6 +182,7 @@ export class Sim {
|
|||||||
this.div.dispatchEvent(e);
|
this.div.dispatchEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pause the simulation if it's currently running
|
||||||
pause() {
|
pause() {
|
||||||
this.playing = false;
|
this.playing = false;
|
||||||
|
|
||||||
@ -187,6 +194,7 @@ export class Sim {
|
|||||||
this.div.dispatchEvent(e);
|
this.div.dispatchEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate the display content representing the current scale of the simulation
|
||||||
getScaleDisplay() {
|
getScaleDisplay() {
|
||||||
const scale = 2 ** Math.abs(this.display.scalePower);
|
const scale = 2 ** Math.abs(this.display.scalePower);
|
||||||
const scaleText = this.display.scalePower >= 0 ? `${scale}` : `1/${scale}`;
|
const scaleText = this.display.scalePower >= 0 ? `${scale}` : `1/${scale}`;
|
||||||
|
|||||||
189
system.js
189
system.js
@ -72,6 +72,15 @@ export class System {
|
|||||||
// TODO: Calculate work done by pointer here?
|
// TODO: Calculate work done by pointer here?
|
||||||
// Either interpolate the acceleration and use m*a, or
|
// Either interpolate the acceleration and use m*a, or
|
||||||
// measure the change in the object's kinetic energy
|
// measure the change in the object's kinetic energy
|
||||||
|
// Or both!
|
||||||
|
// In the frame loop, for the selected object we already calculate
|
||||||
|
// the work done by the pointer in preventing the acceleration the object
|
||||||
|
// would have experienced.
|
||||||
|
// But that doesn't capture the work done by the pointer moving the object.
|
||||||
|
// If the sim is paused, the dot(force, displacement) method loses accuracy.
|
||||||
|
// If the sim is paused though, then channge in kinetic energy will be easy to measure.
|
||||||
|
// If the sim is not paused, we can use dot(force, displacement) method.
|
||||||
|
|
||||||
obj.position = add(start, delta);
|
obj.position = add(start, delta);
|
||||||
obj.velocity = zero;
|
obj.velocity = zero;
|
||||||
}
|
}
|
||||||
@ -111,54 +120,6 @@ export class System {
|
|||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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) {
|
|
||||||
let S, T;
|
|
||||||
// Merge the older into the newer, in order to provide mass creation rate continuity
|
|
||||||
if (A.age > B.age) {
|
|
||||||
// A merges into B; B survives
|
|
||||||
S = B;
|
|
||||||
T = A;
|
|
||||||
// If A was selected or being created, select S instead
|
|
||||||
if (this.creatingObject === i) this.creatingObject = j;
|
|
||||||
if (this.selectedObject === i) this.selectedObject = j;
|
|
||||||
} else {
|
|
||||||
// B merges into A; A survives
|
|
||||||
S = A;
|
|
||||||
T = B;
|
|
||||||
// If B was selected or being created, select S instead
|
|
||||||
if (this.creatingObject === j) this.creatingObject = i;
|
|
||||||
if (this.selectedObject === j) this.selectedObject = i;
|
|
||||||
}
|
|
||||||
// Merge T into S:
|
|
||||||
// Set position = center of mass
|
|
||||||
// Set velocity = total momentum / total mass
|
|
||||||
// Combine forces
|
|
||||||
// Add masses
|
|
||||||
// Average color
|
|
||||||
S.position = weightedAvg([[S.position, S.mass], [T.position, T.mass]]);
|
|
||||||
S.velocity = weightedAvg([[S.velocity, S.mass], [T.velocity, T.mass]]);
|
|
||||||
S.forces.push(...T.forces);
|
|
||||||
S.mass += T.mass;
|
|
||||||
S.color = {
|
|
||||||
r: (S.mass * S.color.r + T.mass * T.color.r) / (S.mass + T.mass),
|
|
||||||
g: (S.mass * S.color.g + T.mass * T.color.g) / (S.mass + T.mass),
|
|
||||||
b: (S.mass * S.color.b + T.mass * T.color.b) / (S.mass + T.mass),
|
|
||||||
};
|
|
||||||
T.alive = false;
|
|
||||||
T.forces = [];
|
|
||||||
const e = new CustomEvent(EVENT_OBJECT_MERGE, {detail: {surviving: S, merged: T}});
|
|
||||||
this.sim.div.dispatchEvent(e);
|
|
||||||
}
|
|
||||||
}, {alive: true, startWith: i + 1});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Recompute forces
|
// Recompute forces
|
||||||
this.computeForces();
|
this.computeForces();
|
||||||
|
|
||||||
@ -189,8 +150,41 @@ export class System {
|
|||||||
obj.history.shift();
|
obj.history.shift();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Collisions
|
||||||
|
this.forEachObject((A, i) => {
|
||||||
|
this.forEachObject((B, j) => {
|
||||||
|
if (this.objectsOverlap(A, B)) {
|
||||||
|
// this.mergeObjects(i, j);
|
||||||
|
this.bounceObjects(i, j);
|
||||||
|
}
|
||||||
|
}, {alive: true, startWith: i + 1});
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display total energy
|
||||||
|
const totalKE = this.reduce((total, obj) => {
|
||||||
|
const energy = obj.mass * square(obj.velocity) / 2;
|
||||||
|
return total + energy;
|
||||||
|
}, 0, {alive: true});
|
||||||
|
|
||||||
|
const G = this.sim.getOption('param.gravity');
|
||||||
|
|
||||||
|
const totalGPE = this.reduce((total, A, i) => {
|
||||||
|
return total + this.reduce((objTotal, B) => {
|
||||||
|
const energy = -G * A.mass * B.mass / magnitude(sub(A.position, B.position));
|
||||||
|
return objTotal + energy;
|
||||||
|
}, 0, {alive: true, startWith: i + 1});
|
||||||
|
}, 0, {alive: true});
|
||||||
|
|
||||||
|
const netMomentum = this.reduce((net, obj) => add(net, obj.momentum), zero);
|
||||||
|
|
||||||
|
this.sim.info['Total E'] = (totalKE + totalGPE).toExponential(2);
|
||||||
|
this.sim.info['Total K'] = totalKE.toExponential(2);
|
||||||
|
this.sim.info['Total U<sub>g</sub>'] = totalGPE.toExponential(2);
|
||||||
|
this.sim.info['Net Momentum'] = magnitude(netMomentum).toExponential(2);
|
||||||
|
|
||||||
// Display objects info
|
// Display objects info
|
||||||
// First clear info from previous frame
|
// First clear info from previous frame
|
||||||
this.forEachObject((_obj, i) => {
|
this.forEachObject((_obj, i) => {
|
||||||
@ -395,6 +389,95 @@ export class System {
|
|||||||
return objects;
|
return objects;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
objectsOverlap(A, B) {
|
||||||
|
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);
|
||||||
|
return d < A.radius + B.radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
bounceObjects(i, j) {
|
||||||
|
const A = this.object(i);
|
||||||
|
const B = this.object(j);
|
||||||
|
|
||||||
|
// TODO: Handle scenario where an object overlaps more than one other object
|
||||||
|
|
||||||
|
// const elasticity = 1;
|
||||||
|
const r = sub(A.position, B.position);
|
||||||
|
const normal = div(r, magnitude(r));
|
||||||
|
const tangent = {x: -normal.y, y: normal.x};
|
||||||
|
const vAt = dot(A.velocity, tangent);
|
||||||
|
|
||||||
|
// Require that objects are moving toward each other!
|
||||||
|
// Normal is directed toward A, so they are moving toward each other if vBn > vAn
|
||||||
|
const vAn = dot(A.velocity, normal);
|
||||||
|
const vBn = dot(B.velocity, normal);
|
||||||
|
if (vAn >= vBn) {
|
||||||
|
// The objects are already moving away from each other
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Are these objects sticking together?
|
||||||
|
// One way to determine this is,
|
||||||
|
// does either object have enough KE to escape their gravitational potential?
|
||||||
|
// const vRel = sub(A.velocity, B.velocity);
|
||||||
|
// const G = this.sim.getOption('param.gravity');
|
||||||
|
// if (square(vRel) < 0.1 * G * (A.mass + B.mass) / magnitude(r)) {
|
||||||
|
// // Neither object looks like it can escape!
|
||||||
|
// this.mergeObjects(i, j);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Continue with rebound calculations
|
||||||
|
const vAnNew = (vAn * (A.mass - B.mass) + vBn * 2 * B.mass) / (A.mass + B.mass);
|
||||||
|
const vA = add(mult(tangent, vAt), mult(normal, vAnNew));
|
||||||
|
const vB = div(add(mult(A.velocity, A.mass), mult(B.velocity, B.mass), mult(vA, -1 * A.mass)), B.mass);
|
||||||
|
|
||||||
|
A.velocity = vA;
|
||||||
|
B.velocity = vB;
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeObjects(i, j) {
|
||||||
|
const A = this.object(i);
|
||||||
|
const B = this.object(j);
|
||||||
|
let S, T;
|
||||||
|
// Merge the older into the newer, in order to provide mass creation rate continuity
|
||||||
|
if (A.age > B.age) {
|
||||||
|
// A merges into B; B survives
|
||||||
|
S = B;
|
||||||
|
T = A;
|
||||||
|
// If A was selected or being created, select S instead
|
||||||
|
if (this.creatingObject === i) this.creatingObject = j;
|
||||||
|
if (this.selectedObject === i) this.selectedObject = j;
|
||||||
|
} else {
|
||||||
|
// B merges into A; A survives
|
||||||
|
S = A;
|
||||||
|
T = B;
|
||||||
|
// If B was selected or being created, select S instead
|
||||||
|
if (this.creatingObject === j) this.creatingObject = i;
|
||||||
|
if (this.selectedObject === j) this.selectedObject = i;
|
||||||
|
}
|
||||||
|
// Merge T into S:
|
||||||
|
// Set position = center of mass
|
||||||
|
// Set velocity = total momentum / total mass
|
||||||
|
// Combine forces
|
||||||
|
// Add masses
|
||||||
|
// Average color
|
||||||
|
S.position = weightedAvg([[S.position, S.mass], [T.position, T.mass]]);
|
||||||
|
S.velocity = weightedAvg([[S.velocity, S.mass], [T.velocity, T.mass]]);
|
||||||
|
S.mass += T.mass;
|
||||||
|
S.color = {
|
||||||
|
r: (S.mass * S.color.r + T.mass * T.color.r) / (S.mass + T.mass),
|
||||||
|
g: (S.mass * S.color.g + T.mass * T.color.g) / (S.mass + T.mass),
|
||||||
|
b: (S.mass * S.color.b + T.mass * T.color.b) / (S.mass + T.mass),
|
||||||
|
};
|
||||||
|
T.alive = false;
|
||||||
|
T.forces = [];
|
||||||
|
const e = new CustomEvent(EVENT_OBJECT_MERGE, {detail: {surviving: S, merged: T}});
|
||||||
|
this.sim.div.dispatchEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
@ -406,6 +489,11 @@ export class System {
|
|||||||
const r = sub(B.position, A.position);
|
const r = sub(B.position, A.position);
|
||||||
const dSquared = square(r);
|
const dSquared = square(r);
|
||||||
const d = Math.sqrt(dSquared);
|
const d = Math.sqrt(dSquared);
|
||||||
|
// If the objects are overlapping, don't apply gravity;
|
||||||
|
// This should give them a chance to fully rebound, and avoid accidental capture.
|
||||||
|
// if (d <= A.radius + B.radius) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
const F = gravity * A.mass * B.mass / dSquared;
|
const F = gravity * A.mass * B.mass / dSquared;
|
||||||
const Fx = F * r.x / d;
|
const Fx = F * r.x / d;
|
||||||
const Fy = F * r.y / d;
|
const Fy = F * r.y / d;
|
||||||
@ -423,9 +511,6 @@ export class System {
|
|||||||
this.forEachObject(obj => {
|
this.forEachObject(obj => {
|
||||||
// Acceleration
|
// Acceleration
|
||||||
obj.acceleration = obj.getAcceleration();
|
obj.acceleration = obj.getAcceleration();
|
||||||
|
|
||||||
// Kinetic Energy
|
|
||||||
obj.kineticEnergy = obj.mass * square(obj.velocity) / 2;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -452,7 +537,7 @@ export class System {
|
|||||||
const netMomentum = this.reduce((acc, obj) => {
|
const netMomentum = this.reduce((acc, obj) => {
|
||||||
if (objects.length && !objects.includes(obj)) return acc;
|
if (objects.length && !objects.includes(obj)) return acc;
|
||||||
return add(acc, mult(obj.velocity, obj.mass));
|
return add(acc, mult(obj.velocity, obj.mass));
|
||||||
}, zero);
|
}, zero, {alive: true});
|
||||||
|
|
||||||
return {totalMass, count, totalMassLocation, centerOfMass, netMomentum};
|
return {totalMass, count, totalMassLocation, centerOfMass, netMomentum};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import {hide, show} from '../helper.js';
|
import {hide, show} from '../helper.js';
|
||||||
import {Tool} from '../tool.js';
|
import {Tool} from '../tool.js';
|
||||||
|
import {magnitude} from '../vector.js';
|
||||||
|
|
||||||
export class ObjectsTool extends Tool {
|
export class ObjectsTool extends Tool {
|
||||||
objects = [];
|
objects = [];
|
||||||
@ -63,9 +64,12 @@ export class ObjectsTool extends Tool {
|
|||||||
// })));
|
// })));
|
||||||
objectEl.innerHTML =
|
objectEl.innerHTML =
|
||||||
`<span style="background-color: rgb(${r},${g},${b});"> </span>` +
|
`<span style="background-color: rgb(${r},${g},${b});"> </span>` +
|
||||||
`E<sub>k</sub>: ${obj.kineticEnergy.toFixed(0)}` +
|
`<div style="display:inline-block; vertical-align:middle; padding: 2px;">` +
|
||||||
`<br> W<sub>p</sub>: ${obj.workDoneByPointer.toFixed(0)}` +
|
`E<sub>k</sub>: ${obj.kineticEnergy.toFixed(0)}<br>` +
|
||||||
`<br> W<sub>f</sub>: ${obj.workDoneByForces.toFixed(0)}`;
|
`|p|: ${magnitude(obj.momentum).toFixed(0)}<br>` +
|
||||||
|
// `<br> W<sub>p</sub>: ${obj.workDoneByPointer.toFixed(0)}` +
|
||||||
|
// `<br> W<sub>f</sub>: ${obj.workDoneByForces.toFixed(0)}`;
|
||||||
|
'</div>';
|
||||||
|
|
||||||
// `${obj.mass.toPrecision(3)} ` +
|
// `${obj.mass.toPrecision(3)} ` +
|
||||||
// `${distance.toPrecision(3)}`;
|
// `${distance.toPrecision(3)}`;
|
||||||
|
|||||||
12
vector.js
12
vector.js
@ -1,7 +1,7 @@
|
|||||||
export const copy = (v) => ({x: v.x, y: v.y});
|
export const copy = (v) => ({x: v.x, y: v.y});
|
||||||
export const dot = (a, b) => a.x * b.x + a.y * b.y;
|
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 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});
|
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 sub = (a, b) => ({x: a.x - b.x, y: a.y - b.y});
|
||||||
export const square = ({x, y}) => x ** 2 + y ** 2;
|
export const square = ({x, y}) => x ** 2 + y ** 2;
|
||||||
export const magnitude = ({x, y}) => Math.sqrt(square({x, y}));
|
export const magnitude = ({x, y}) => Math.sqrt(square({x, y}));
|
||||||
@ -20,7 +20,7 @@ export const weightedAvg = (items) => {
|
|||||||
let res = zero;
|
let res = zero;
|
||||||
let W = 0;
|
let W = 0;
|
||||||
for (const [v, w] of items) {
|
for (const [v, w] of items) {
|
||||||
res = add(res, mult(v, w));
|
res = _add(res, mult(v, w));
|
||||||
W += w;
|
W += w;
|
||||||
}
|
}
|
||||||
return div(res, W);
|
return div(res, W);
|
||||||
@ -33,3 +33,11 @@ export const components = (mag, dir) => mult(mag, {
|
|||||||
|
|
||||||
export const direction = ({x, y}) => Math.atan2(y, x);
|
export const direction = ({x, y}) => Math.atan2(y, x);
|
||||||
export const degrees = (rad) => rad * 180 / Math.PI;
|
export const degrees = (rad) => rad * 180 / Math.PI;
|
||||||
|
|
||||||
|
export const add = (...vectors) => {
|
||||||
|
let res = zero;
|
||||||
|
for (const v of vectors) {
|
||||||
|
res = _add(res, v);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|||||||
27
vector.test.js
Normal file
27
vector.test.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import {add, weightedAvg} from "./vector.js";
|
||||||
|
|
||||||
|
const assert = (expected, actual) => {
|
||||||
|
if (expected !== actual) {
|
||||||
|
console.error(`fail: ${expected} !== ${actual}`);
|
||||||
|
throw new Error('fail');
|
||||||
|
}
|
||||||
|
console.log(`${expected} === ${actual}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// add
|
||||||
|
{
|
||||||
|
const v1 = {x: 1, y: 1};
|
||||||
|
const v = add(v1, v1, v1);
|
||||||
|
assert('{"x":3,"y":3}', JSON.stringify(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
// weightedAvg
|
||||||
|
|
||||||
|
{
|
||||||
|
const v1 = {x: 1, y: 1};
|
||||||
|
const v2 = {x: -1, y: -1};
|
||||||
|
const v3 = {x: 2, y: 2};
|
||||||
|
assert('{"x":0,"y":0}', JSON.stringify(weightedAvg([[v1, 1], [v2, 1]])));
|
||||||
|
assert('{"x":1,"y":1}', JSON.stringify(weightedAvg([[v1, 1], [v1, 1]])));
|
||||||
|
assert('{"x":1.25,"y":1.25}', JSON.stringify(weightedAvg([[v1, 3], [v3, 1]])));
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user