function CreateBall(x, y, radius, res) {
var offset = 0;
for (var i = 0; i < Math.PI 2; i += Math.PI 2 / res) {
var cx = x + Math.cos(i) * radius;
var cy = y + Math.sin(i) * radius;
var point = new Point(cx, cy, 1);
points.push(point);
offset++;
}
var start = points.length - offset;
for (var i = start; i < points.length; i++) {
var p1 = points[i];
var p2 = points[i + 1] || points[start];
var spring = new Spring(p1, p2, Stiffness, Damping);
springs.push(spring);
}
}
Point Object
class Point {
constructor(x, y, mass) {
this.x = x;
this.y = y;
this.vx = 0;
this.vy = 0;
this.mass = mass;
}
update(dt) {
this.vx += gravity.x * dt;
this.vy += gravity.y * dt;
var minY = -5;
var C = minY - this.y;
if (C > 0) {
var impulse = getConstraintImpulse([0, -1], [this.vx, this.vy], [this.mass, this.mass], C, 0.3, dt);
this.vx += impulse[0] / this.mass;
this.vy += impulse[1] / this.mass;
}
this.x += this.vx * dt;
this.y += this.vy * dt;
}
render() {
graphics.fillStyle('0xff0000');
graphics.fillCircle(width / 2 + this.x * 25, height / 2 - this.y * 25, 3)
}
}
Spring Object
class Spring {
constructor(a, b, stiffness, damping, len) {
if (!a || !b) throw "A or B is null";
this.a = a;
this.b = b;
this.stiffness = stiffness;
this.damping = damping;
this.len = len || Distance.getDistance(this.a.x, this.a.y, this.b.x, this.b.y); // natural length
}
update(dt) {
var distance = Distance.getDistance(this.a.x, this.a.y, this.b.x, this.b.y);
var error = distance - this.len;
var dirVector = Vec2.normalizeVector({x: this.a.x - this.b.x, y: this.a.y - this.b.y });
var relativeVelocity = { x: this.a.vx - this.b.vx, y: this.a.vy - this.b.vy}
var dampingScalar = Vec2.dot(relativeVelocity, dirVector) * this.damping;
var dampingForce = { x: dirVector.x * dampingScalar, y: dirVector.y * dampingScalar };
this.a.vx += -(dirVector.x * error * this.stiffness + dampingForce.x) * 1;
this.a.vy += -(dirVector.y * error * this.stiffness + dampingForce.y) * 1;
this.b.vx += (dirVector.x * error * this.stiffness + dampingForce.x) * 1;
this.b.vy += (dirVector.y * error * this.stiffness + dampingForce.y) * 1;
}
}
Constraint functions
function getConstraintImpulse(jacobian, velocities, masses, C, biasFactor, h) {
var bias = biasFactor / h * C;
var effectiveMass = getEffectiveMass(jacobian, masses);
var lambda = getLambda(effectiveMass, jacobian, velocities, bias);
var output = [];
for (var i = 0; i < jacobian.length; i++) {
output[i] = jacobian[i] * lambda;
}
return output;
}
function getLambda(effectiveMass, jacobian, velocities, bias) {
var sum = 0;
for (var i = 0; i < jacobian.length; i++) {
sum += jacobian[i] * velocities[i];
}
return -effectiveMass * (sum + bias);
}
function getEffectiveMass(jacobian, masses) {
var sum = 0;
for (var i = 0; i < jacobian.length; i++) {
sum += (jacobian[i] / masses[i]) * jacobian[i];
}
return 1 / sum;
}
Main Loop
function update(t, dt) {
graphics.clear();
for (var c = 0; c < 1; c += timeStep) {
// calculate "Volume"
var volume = 0;
for (var i = 0; i < springs.length; i++) {
var s = springs[i];
var normal = Vec2.normalizeVector({x: s.b.y - s.a.y, y: s.a.x - s.b.x});
volume += 0.5 * Math.abs(s.a.x - s.b.x) * Math.abs(normal.x) * Distance.getDistance(s.a.x, s.a.y, s.b.x, s.b.y);
}
for (var i = 0; i < springs.length; i++) {
var s = springs[i];
s.update(1 / 60 * timeStep);
}
for (var i = 0; i < springs.length; i++) {
var s = springs[i];
var pressure = Distance.getDistance(s.a.x, s.a.y, s.b.x, s.b.y) * Pressure * (1 / volume);
var normal = Vec2.normalizeVector({x: s.b.y - s.a.y, y: s.a.x - s.b.x});
var relativeVelocity = {x: s.a.vx - s.b.vx, y: s.a.vy - s.b.vy}
var dampingScalar = Vec2.dot(relativeVelocity, normal) * 0;
var dampingForce = {x: normal.x * dampingScalar, y: normal.y * dampingScalar };
s.a.vx += normal.x * pressure + dampingForce.x;
s.a.vy += normal.y * pressure + dampingForce.y;
s.b.vx += normal.x * pressure + dampingForce.x;
s.b.vy += normal.y * pressure + dampingForce.y;
}
for (var i = 0; i < points.length; i++) {
var p = points[i];
p.update(1 / 60 * timeStep);
}
}
for (var i = 0; i < points.length; i++) {
var p = points[i];
p.render();
}
}
var gc = new GameCanvas();
var gravity = {x: 0, y: -9.81};
var timeStep = 0.1;
var points = [];
var springs = [];
var Pressure = 0.2;
var PressureDamping = 0.1;
var Damping = 0.2;
var Stiffness = 1;
CreateBall(0, 0, 1, 30);
loop();
function loop() {
clearScreen();
for (var c = 0; c < 1; c += timeStep) {
var volume = 0;
for (var i = 0; i < springs.length; i++) {
var s = springs[i];
var normal = normalizeVector({
x: s.a.x - s.b.x,
y: s.a.y - s.b.y
});
normal = {x: -normal.y, y: normal.x};
volume += 0.5 Math.abs(s.a.x - s.b.x) Math.abs(normal.x) * getDistance(s.a.x, s.a.y, s.b.x, s.b.y);
}
for (var i = 0; i < springs.length; i++) {
var s = springs[i];
s.update(1 / 60 * timeStep);
}
for (var i = 0; i < springs.length; i++) {
var s = springs[i];
var pressure = getDistance(s.a.x, s.a.y, s.b.x, s.b.y) Pressure (1 / volume);
var normal = normalizeVector({
x: s.a.x - s.b.x,
y: s.a.y - s.b.y
});
normal = {x: -normal.y, y: normal.x};
var relativeVelocity = {
x: s.a.vx - s.b.vx,
y: s.a.vy - s.b.vy
}
var dampingScalar = dot(relativeVelocity, normal) * 0;
var dampingForce = {
x: normal.x * dampingScalar,
y: normal.y * dampingScalar
};
s.a.vx += normal.x * pressure + dampingForce.x;
s.a.vy += normal.y * pressure + dampingForce.y;
s.b.vx += normal.x * pressure + dampingForce.x;
s.b.vy += normal.y * pressure + dampingForce.y;
}
for (var i = 0; i < points.length; i++) {
var p = points[i];
p.update(1 / 60 * timeStep);
}
}
for (var i = 0; i < points.length; i++) {
var p = points[i];
p.render();
}
update();
requestAnimationFrame(loop);
}
Useful References
http://panoramx.ift.uni.wroc.pl/~maq/soft2d/howtosoftbody.html (this blog gives a step by step guide on implementing a soft body pressure model)
Comments