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 { |
class Manager { |
||||||
|
|
||||||
nPendula: NPendulum[] = [] |
pendulums: Pendulum[] = [] |
||||||
|
|
||||||
h = 0.07 |
timescale = 1; |
||||||
|
gravity = 9.81; |
||||||
|
|
||||||
|
playing = false; |
||||||
|
|
||||||
constructor() { |
constructor() { |
||||||
p.colorMode(p.HSB, 100); |
p.colorMode(p.HSB, 100); |
||||||
let count = 500; |
let count = 100; |
||||||
for (let i = 0; i < count; i++){ |
for (let i = 0; i < count; i++){ |
||||||
let rad = i / count / 1e3 + p.PI * 1.05; |
let rad = i / count / 1e3 + p.PI * 1.05; |
||||||
let hue = i / count * 100; |
let hue = i / count * 100; |
||||||
let color = p.color(hue, 100, 100); |
let color = p.color(hue, 100, 100); |
||||||
this.nPendula.push( |
this.pendulums.push( |
||||||
new NPendulum([200, 200], [2, 2], rad, color) |
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); |
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(){ |
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(){ |
draw(){ |
||||||
p.push() |
p.push() |
||||||
p.translate(p.width / 2, p.height / 2); |
p.translate(p.width / 2, p.height / 2); |
||||||
this.nPendula.forEach(p => p.draw()); |
this.pendulums.forEach(p => p.draw()); |
||||||
p.pop(); |
p.pop(); |
||||||
} |
} |
||||||
|
|
||||||
|
@ -1,100 +1,88 @@ |
|||||||
const g = 9.81 |
class Pendulum { |
||||||
|
|
||||||
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); |
|
||||||
} |
|
||||||
|
|
||||||
updateOrigins(){ |
X: Vector[] = []; |
||||||
this.pendula.forEach((p, i) => { |
V: Vector[] = []; |
||||||
if (i > 0){ |
|
||||||
let before = this.pendula[i - 1]; |
|
||||||
p.origin = p5.Vector.add(before.origin, before.pos); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
update(h){ |
size: number; |
||||||
this.pendula.forEach((p, i) => { |
|
||||||
p.update(h, this.pendula); |
|
||||||
}); |
|
||||||
this.updateOrigins(); |
|
||||||
} |
|
||||||
|
|
||||||
draw(){ |
constructor(readonly M: number[], readonly L: number[], readonly color: p5.Color) { |
||||||
this.pendula.forEach(p => p.draw()); |
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 |
let previousP = new Vector(0, 0); |
||||||
m: number |
|
||||||
|
|
||||||
acc: number = 0 |
for (let i = 0; i < this.size; i++) { |
||||||
vel: number = 0 |
// apply external force (gravity)
|
||||||
rad: number |
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) { |
let deltaP1 = Vector.Mult(n, -w1 / (w1 + w2) * (l - this.L[i])); |
||||||
this.l = l; |
let deltaP2 = Vector.Mult(n, w2 / (w1 + w2) * (l - this.L[i])); |
||||||
this.m = m; |
|
||||||
this.rad = rad; |
|
||||||
this.color = color; |
|
||||||
} |
|
||||||
|
|
||||||
calcAcc(pendula){ |
previousP.add(deltaP1); |
||||||
return -g / this.l * p.sin(this.rad); |
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 = []){ |
previousP = currentP; |
||||||
this.acc = this.calcAcc(pendula); |
} |
||||||
this.vel += this.acc * h; |
|
||||||
this.rad += this.vel * h; |
this.V[this.size - 1] = Vector.Mult(Vector.Sub(previousP, this.X[this.size - 1]), 1 / h); |
||||||
|
this.X[this.size - 1] = previousP; |
||||||
|
} |
||||||
} |
} |
||||||
|
|
||||||
draw(){ |
draw(){ |
||||||
let pos = this.pos; |
|
||||||
p.push(); |
p.push(); |
||||||
p.translate(this.origin); |
|
||||||
p.stroke(this.color); |
p.stroke(this.color); |
||||||
p.strokeWeight(3); |
p.strokeWeight(2); |
||||||
p.line(0, 0, pos.x, pos.y); |
p.fill(255); |
||||||
p.ellipse(pos.x, pos.y, this.m * 5, this.m * 5); |
|
||||||
p.pop(); |
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(){ |
for (let p2 of this.X){ |
||||||
return p5.Vector.mult(p.createVector(p.sin(this.rad), p.cos(this.rad)), this.l); |
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