parent
c6a43ca622
commit
b7491fa07f
14 changed files with 587 additions and 192 deletions
Before Width: | Height: | Size: 318 B |
After Width: | Height: | Size: 346 B |
After Width: | Height: | Size: 218 B |
After Width: | Height: | Size: 598 B |
@ -1,31 +1,66 @@ |
||||
class Manager { |
||||
|
||||
nPendula: NPendulum[] = [] |
||||
pendulums: Pendulum[] = [] |
||||
|
||||
h = 0.07 |
||||
timescale = 1; |
||||
gravity = 9.81; |
||||
|
||||
playing = false; |
||||
|
||||
constructor() { |
||||
p.colorMode(p.HSB, 100); |
||||
let count = 500; |
||||
let count = 100; |
||||
for (let i = 0; i < count; i++){ |
||||
let rad = i / count / 1e3 + p.PI * 1.05; |
||||
let hue = i / count * 100; |
||||
let color = p.color(hue, 100, 100); |
||||
this.nPendula.push( |
||||
new NPendulum([200, 200], [2, 2], rad, color) |
||||
this.pendulums.push( |
||||
new Pendulum([1, 1, 1, 1, 1, 1, 1, 1], [50, 50, 50, 50, 50, 50, 50, 100 + i / count / 1000000], color) |
||||
); |
||||
} |
||||
p.colorMode(p.RGB); |
||||
|
||||
} |
||||
|
||||
init(){ |
||||
// @ts-ignore
|
||||
const {createApp} = Vue; |
||||
createApp({ |
||||
data() { |
||||
return { |
||||
gravity: manager.gravity, |
||||
timescale: manager.timescale, |
||||
playingBtn: "play" |
||||
}; |
||||
}, |
||||
watch: { |
||||
gravity(newGravity: string) { |
||||
manager.gravity = parseFloat(newGravity); |
||||
}, |
||||
timescale(newScale: string){ |
||||
manager.timescale = parseFloat(newScale); |
||||
} |
||||
}, |
||||
methods: { |
||||
togglePlay(){ |
||||
manager.playing = !manager.playing; |
||||
this.playingBtn = manager.playing ? "pause" : "play"; |
||||
} |
||||
} |
||||
}).mount("#simulation"); |
||||
} |
||||
|
||||
update(){ |
||||
this.nPendula.forEach(p => p.update(this.h)); |
||||
if (this.playing) { |
||||
const h = this.timescale / (p.frameRate() || 60); |
||||
this.pendulums.forEach(p => p.update(h)); |
||||
} |
||||
} |
||||
|
||||
draw(){ |
||||
p.push() |
||||
p.translate(p.width / 2, p.height / 2); |
||||
this.nPendula.forEach(p => p.draw()); |
||||
this.pendulums.forEach(p => p.draw()); |
||||
p.pop(); |
||||
} |
||||
|
||||
|
@ -1,100 +1,88 @@ |
||||
const g = 9.81 |
||||
|
||||
class NPendulum { |
||||
|
||||
pendula: Pendulum[] = [] |
||||
|
||||
constructor(lengths, masses, startRad, color) { |
||||
switch (lengths.length) { |
||||
case 1: |
||||
this.pendula.push(new Pendulum(lengths[0], masses[0], startRad, color)); |
||||
break; |
||||
case 2: |
||||
let p1 = new Pendulum(lengths[0], masses[0], startRad, color); |
||||
let p2 = new Pendulum(lengths[1], masses[1], startRad, color); |
||||
p1.calcAcc = function(pendula){ |
||||
let p2 = pendula[1]; |
||||
return -g / this.l * p.sin(this.rad) - p2.l * p2.m / this.l / (this.m + p2.m) |
||||
* (p.cos(this.rad - p2.rad) * p2.acc + p.sin(this.rad - p2.rad) * p.pow(p2.vel, 2)); |
||||
} |
||||
p2.calcAcc = function (pendula){ |
||||
let p1 = pendula[0]; |
||||
return -g / this.l * p.sin(this.rad) - p1.l / this.l |
||||
* (p.cos(p1.rad - this.rad) * p1.acc - p.sin(p1.rad - this.rad) * p.pow(p1.vel, 2)); |
||||
} |
||||
this.pendula.push(p1, p2); |
||||
break; |
||||
} |
||||
this.pendula[0].origin = p.createVector(0, 0); |
||||
} |
||||
class Pendulum { |
||||
|
||||
updateOrigins(){ |
||||
this.pendula.forEach((p, i) => { |
||||
if (i > 0){ |
||||
let before = this.pendula[i - 1]; |
||||
p.origin = p5.Vector.add(before.origin, before.pos); |
||||
} |
||||
}); |
||||
} |
||||
X: Vector[] = []; |
||||
V: Vector[] = []; |
||||
|
||||
update(h){ |
||||
this.pendula.forEach((p, i) => { |
||||
p.update(h, this.pendula); |
||||
}); |
||||
this.updateOrigins(); |
||||
} |
||||
size: number; |
||||
|
||||
draw(){ |
||||
this.pendula.forEach(p => p.draw()); |
||||
constructor(readonly M: number[], readonly L: number[], readonly color: p5.Color) { |
||||
console.assert(M.length === L.length, M, L, "Masses and Lengths are not of equal length!"); |
||||
|
||||
this.size = M.length; |
||||
|
||||
let currentPosition = new Vector(0, 0); |
||||
for (let i = 0; i < this.size; i++){ |
||||
let a = Math.sqrt(L[i] * L[i] / 2); |
||||
currentPosition.addC(-L[i], 0); |
||||
this.X.push(currentPosition.copy()); |
||||
this.V.push(new Vector(0, 0)); |
||||
} |
||||
} |
||||
|
||||
} |
||||
// using position based dynamics
|
||||
update(h: number) { |
||||
const subSteps = 50; |
||||
|
||||
h /= subSteps; |
||||
|
||||
class Pendulum { |
||||
for (let k = 0; k < subSteps; k++) { |
||||
|
||||
l: number |
||||
m: number |
||||
let previousP = new Vector(0, 0); |
||||
|
||||
acc: number = 0 |
||||
vel: number = 0 |
||||
rad: number |
||||
for (let i = 0; i < this.size; i++) { |
||||
// apply external force (gravity)
|
||||
this.V[i].addC(0, manager.gravity * h * 50); |
||||
|
||||
origin: p5.Vector |
||||
// euler step
|
||||
let currentP = Vector.Add(this.X[i],Vector.Mult(this.V[i], h)); |
||||
|
||||
color: p5.Color |
||||
// solve distance constraint
|
||||
let w1 = i === 0 ? 0 : 1 / this.M[i - 1]; |
||||
let w2 = 1 / this.M[i]; |
||||
|
||||
let s = Vector.Sub(previousP, currentP); |
||||
let n = s.copy(); |
||||
n.normalize(); |
||||
let l = s.mag(); |
||||
|
||||
constructor(l, m, rad, color) { |
||||
this.l = l; |
||||
this.m = m; |
||||
this.rad = rad; |
||||
this.color = color; |
||||
} |
||||
let deltaP1 = Vector.Mult(n, -w1 / (w1 + w2) * (l - this.L[i])); |
||||
let deltaP2 = Vector.Mult(n, w2 / (w1 + w2) * (l - this.L[i])); |
||||
|
||||
calcAcc(pendula){ |
||||
return -g / this.l * p.sin(this.rad); |
||||
} |
||||
previousP.add(deltaP1); |
||||
currentP.add(deltaP2); |
||||
|
||||
// integrate
|
||||
if (i > 0){ |
||||
this.V[i - 1] = Vector.Mult(Vector.Sub(previousP, this.X[i - 1]), 1 / h); |
||||
this.X[i - 1] = previousP; |
||||
} |
||||
|
||||
update(h, pendula = []){ |
||||
this.acc = this.calcAcc(pendula); |
||||
this.vel += this.acc * h; |
||||
this.rad += this.vel * h; |
||||
previousP = currentP; |
||||
} |
||||
|
||||
this.V[this.size - 1] = Vector.Mult(Vector.Sub(previousP, this.X[this.size - 1]), 1 / h); |
||||
this.X[this.size - 1] = previousP; |
||||
} |
||||
} |
||||
|
||||
draw(){ |
||||
let pos = this.pos; |
||||
p.push(); |
||||
p.translate(this.origin); |
||||
|
||||
p.stroke(this.color); |
||||
p.strokeWeight(3); |
||||
p.line(0, 0, pos.x, pos.y); |
||||
p.ellipse(pos.x, pos.y, this.m * 5, this.m * 5); |
||||
p.pop(); |
||||
} |
||||
p.strokeWeight(2); |
||||
p.fill(255); |
||||
|
||||
let p1 = new Vector(0, 0); |
||||
for (let p2 of this.X){ |
||||
p.line(p1.x, p1.y, p2.x, p2.y); |
||||
p1 = p2.copy(); |
||||
} |
||||
|
||||
get pos(){ |
||||
return p5.Vector.mult(p.createVector(p.sin(this.rad), p.cos(this.rad)), this.l); |
||||
for (let p2 of this.X){ |
||||
p.ellipse(p2.x, p2.y, 10, 10); |
||||
} |
||||
|
||||
p.pop(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,38 @@ |
||||
class Vector { |
||||
constructor(public x: number, public y: number) {} |
||||
|
||||
copy(){ |
||||
return new Vector(this.x, this.y); |
||||
} |
||||
|
||||
static Add(v1: Vector, v2: Vector){ |
||||
return new Vector(v1.x + v2.x, v1.y + v2.y); |
||||
} |
||||
static Sub(v1: Vector, v2: Vector){ |
||||
return new Vector(v1.x - v2.x, v1.y - v2.y); |
||||
} |
||||
static Mult(v: Vector, n: number){ |
||||
return new Vector(v.x * n, v.y * n); |
||||
} |
||||
|
||||
add(v: Vector){ |
||||
this.x += v.x; |
||||
this.y += v.y; |
||||
} |
||||
addC(x: number, y: number){ |
||||
this.x += x; |
||||
this.y += y; |
||||
} |
||||
mult(n: number){ |
||||
this.x *= n; |
||||
this.y *= n; |
||||
} |
||||
|
||||
mag(){ |
||||
return Math.sqrt(this.x * this.x + this.y * this.y); |
||||
} |
||||
|
||||
normalize(){ |
||||
this.mult(1 / this.mag()); |
||||
} |
||||
} |
@ -1,7 +0,0 @@ |
||||
{ |
||||
"collision": false, |
||||
"colorPicker": false, |
||||
"cookie": false, |
||||
"prototypes": false, |
||||
"technical": false |
||||
} |
@ -1,23 +0,0 @@ |
||||
{ |
||||
"project": { |
||||
"name": "pendulum", |
||||
"author": "BenjoCraeft", |
||||
"version": "0.0.0", |
||||
"playerCounts": [], |
||||
"online": { |
||||
"iceServers": [ |
||||
{"urls": "stun:stun.l.google.com:19302"}, |
||||
{ |
||||
"urls": "turn:numb.viagenie.ca", |
||||
"credential": "muazkh", |
||||
"username": "webrtc@live.com" |
||||
} |
||||
] |
||||
} |
||||
}, |
||||
"frameWork": { |
||||
"frameRate": 60, |
||||
"width": null, |
||||
"height": null |
||||
} |
||||
} |
Loading…
Reference in new issue