Implemented adjustable elasticity and adjustable merge threshold

This parameter represents the percentage of energy that will be
conserved in collisions.
0 -> Perfectly inelastic
1 -> Perfectly elastic
This commit is contained in:
Ladd 2026-01-31 18:22:56 -06:00
parent adda66782e
commit 68464b0460
11 changed files with 36 additions and 22 deletions

View File

@ -8,11 +8,11 @@ Uses `npm` for `eslint`.
Screenshots
-----------
![A binary pair orbiting a larger partner](./gravity-simulator-4.png "Gravity Simulator Screenshot 4")
![A binary pair orbiting a larger partner](./screenshots/gravity-simulator-4.png "Gravity Simulator Screenshot 4")
![A greater mass attracts others which had been on escape trajectories](./gravity-simulator-5.png "Gravity Simulator Screenshot 5")
![A greater mass attracts others which had been on escape trajectories](./screenshots/gravity-simulator-5.png "Gravity Simulator Screenshot 5")
![A small object orbits a more massive binary pair](./gravity-simulator-6.png "Gravity Simulator Screenshot 6")
![A small object orbits a more massive binary pair](./screenshots/gravity-simulator-6.png "Gravity Simulator Screenshot 6")
TODO

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

View File

@ -69,8 +69,6 @@ export class Panning {
// Additional scaling factor
velocity = mult(velocity, this.sim.getOption('display.panningSpeed'));
// TODO: Make it easier to slow down the camera
// Add pointer velocity to current panning velocity
this.velocity = add(this.velocity, velocity);
}
@ -82,10 +80,6 @@ export class Panning {
this.velocity = zero;
}
this.touchStart = undefined;
if (this.sim.getOption('compensate.fastPanning')) {
this.velocity = zero;
}
}
}

View File

Before

Width:  |  Height:  |  Size: 228 KiB

After

Width:  |  Height:  |  Size: 228 KiB

View File

Before

Width:  |  Height:  |  Size: 173 KiB

After

Width:  |  Height:  |  Size: 173 KiB

View File

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

View File

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -10,18 +10,19 @@ export const simOptions = {
traces: ['Path Traces', 'boolean', true],
dashedTraces: ['Dashed', 'boolean', false, {tall: true, showIf: 'display.traces'}],
velocityScale: ['Velocity<br>Vec Scale', 'number', 80, {showIf: 'display.velocity'}],
accelerationScale: ['Accel<br>Vec Scale', 'number', 800, {showIf: 'display.acceleration'}],
accelerationScale: ['Accel<br>Vec Scale', 'number', 80, {showIf: 'display.acceleration'}],
zoomVectors: ['Zoom Vectors', 'boolean', true],
panningSpeed: ['Pan<br>Speed', 'number', 0.1],
},
compensate: {
timeScale: ['Time Scale Compensator', 'boolean', false, {wide: true}],
fastPanning: ['Fast Panning', 'boolean', false],
},
param: {
gravity: ['Gravity', 'number', 1],
timeScale: ['Time Scale', 'number', 0.1],
massCreationRate: ['Mass Creation Rate', 'number', 1],
elasticity: ['Elasticity', 'number', 0.7, {min: 0, max: 1}],
mergeThreshold: ['Merge Threshold', 'number', 0.1, {min: 0, max: 1}],
},
debug: {
objectsInfo: ['Objects Info', 'boolean', false],

View File

@ -400,10 +400,13 @@ export class System {
bounceObjects(i, j) {
const A = this.object(i);
const B = this.object(j);
const totalMass = A.mass + B.mass;
// TODO: Handle scenario where an object overlaps more than one other object
// const elasticity = 1;
const autoMerge = true;
const Z = this.sim.getOption('param.elasticity'); // Elasticity: 0 = inelastic, 1 = elastic
const r = sub(A.position, B.position);
const normal = div(r, magnitude(r));
const tangent = {x: -normal.y, y: normal.x};
@ -421,21 +424,30 @@ export class System {
// 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;
// }
if (autoMerge) {
// const vSquared = Math.abs(vAn - vBn) ** 2;
const vSquared = square(sub(A.velocity, B.velocity));
const G = this.sim.getOption('param.gravity');
const mergeThreshold = this.sim.getOption('param.mergeThreshold');
if (vSquared < mergeThreshold * G * totalMass / magnitude(r)) {
// Neither object looks like it can escape!
this.mergeObjects(i, j);
return;
}
}
// Calculate the collision
const Zscaled = Math.sqrt(Z);
const vAnNew = (vAn * (A.mass - Zscaled * B.mass) + vBn * (1 + Zscaled) * B.mass) / totalMass;
// 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);
const vB = div(add(mult(A.velocity, A.mass), mult(B.velocity, B.mass), mult(vA, -A.mass)), B.mass);
// const Ki = A.kineticEnergy + B.kineticEnergy;
A.velocity = vA;
B.velocity = vB;
// const Kf = A.kineticEnergy + B.kineticEnergy;
// console.log('Collision: Zscaled', Zscaled, 'Energy before', Ki, 'Energy after', Kf, 'Fraction change', (Kf - Ki) / Ki);
}
mergeObjects(i, j) {

View File

@ -126,6 +126,13 @@ export class OptionsTool extends Tool {
input.addEventListener('change', () => {
this.sim.setOption(path, input.value);
// Enforce min/max if provided
if (item.max !== undefined && this.sim.getOption(path) > item.max) {
this.sim.setOption(path, item.max);
}
if (item.min !== undefined && this.sim.getOption(path) < item.min) {
this.sim.setOption(path, item.min);
}
});
this.sim.onOptionSet(path, ({value}) => {