Skip to content

Instantly share code, notes, and snippets.

@stormouse
Last active January 1, 2021 23:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stormouse/90a8d22582ad4e9bc6c91c4a5f80e842 to your computer and use it in GitHub Desktop.
Save stormouse/90a8d22582ad4e9bc6c91c4a5f80e842 to your computer and use it in GitHub Desktop.
pbd-distance-constraint-example.js
// you can try it out on https://editor.p5js.org/
class Constraint {
constructor(constraintType, updateFunc, stiffness) {
this.ctype = constraintType;
this.f = updateFunc;
this.stiffness = stiffness;
}
project(verts) {
this.f(verts);
}
clone() {
return new Constraint(this.ctype, this.f);
}
}
class Vertex {
constructor(x, v, w, f) {
this.x = x;
this.v = v;
this.w = w;
this.f = f;
this.p = [x[0], x[1]];
}
setForce(f) {
this.f = f;
}
}
function applyForce(verts, dt) {
for(let i = 0; i < verts.length; i++) {
for(let d = 0; d < verts[i].v.length; d++) {
verts[i].v[d] = verts[i].v[d] + dt * verts[i].w * verts[i].f[d];
}
}
}
function dampVelocity(verts) {
for(let i = 0; i < verts.length; i++) {
for(let d = 0; d < verts[i].v.length; d++) {
verts[i].v[d] *= 0.99;
}
}
}
function makeProposedPositions(verts, dt) {
for(let i = 0; i < verts.length; i++) {
for(let d = 0; d < 2; d++) {
verts[i].p[d] = verts[i].x[d] + dt * verts[i].v[d];
}
}
}
function generateCollisionConstraints(verts) {
return []; // not implemented
}
function projectConstraints(verts, constraints, nSteps) {
for(let step = 0; step < nSteps; step++) {
for(let i = 0; i < constraints.length; i++) {
constraints[i].project(verts);
}
}
}
function finalizeVertices(verts, dt) {
for(let i = 0; i < verts.length; i++) {
for(let d = 0; d < 2; d++) {
verts[i].v[d] = (verts[i].p[d] - verts[i].x[d]) / dt;
verts[i].x[d] = verts[i].p[d];
}
}
}
function PBDUpdate(vertices, constraints, deltaTime, nSteps=4) {
applyForce(vertices, deltaTime);
dampVelocity(vertices);
makeProposedPositions(vertices, deltaTime);
collisionConstraints = generateCollisionConstraints(vertices);
projectConstraints(vertices, constraints.concat(collisionConstraints), nSteps);
finalizeVertices(vertices, deltaTime);
}
// -------------- helpers ------------------ //
function updateDistanceConstraintPosition(v1, v2, d, k) {
function mod(p1, p2) {
return Math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]));
}
let mag = mod(v1.p, v2.p);
let s1 = 1.0 * (-v1.w) / (v1.w + v2.w) * (mag - d) / mag;
let s2 = 1.0 * v2.w / (v1.w + v2.w) * (mag - d) / mag;
v1.p[0] += s1 * (v1.p[0] - v2.p[0]) * k;
v1.p[1] += s1 * (v1.p[1] - v2.p[1]) * k;
v2.p[0] += s2 * (v1.p[0] - v2.p[0]) * k;
v2.p[1] += s2 * (v1.p[1] - v2.p[1]) * k;
}
function makeTwoVertexDistanceConstraint(v1, v2, d, stiffness) {
return new Constraint(
"distance",
(verts) => updateDistanceConstraintPosition(verts[v1], verts[v2], d, stiffness),
stiffness);
}
// --------------- application --------------- //
const verts = [];
const constraints = [];
const edges = []; // only visual
let isInitialized = false;
function initialize()
{
// creates a vertex matrix
const N = 3;
const D = 50.0;
const NStep = 4;
const K = 0.9;
let index = (x, y) => y * N + x;
for(let y = 0; y < N; y++) {
for(let x = 0; x < N; x++) {
verts.push(new Vertex([x * D, y * D], [0.0, 0.0], 1.0, [0.0, 0.0]));
if (y > 0) {
constraints.push(makeTwoVertexDistanceConstraint(index(x, y-1), index(x, y), D, K));
edges.push([index(x, y-1), index(x, y), K]);
}
if (x > 0) {
constraints.push(makeTwoVertexDistanceConstraint(index(x-1, y), index(x, y), D, K));
edges.push([index(x-1, y), index(x, y), K]);
}
if (x > 0 && y > 0) {
constraints.push(makeTwoVertexDistanceConstraint(index(x-1, y-1), index(x, y), D * 1.414, K * 0.05 / NStep));
edges.push([index(x-1, y-1), index(x, y), K * 0.05 / NStep]);
}
if (x < N - 1 && y > 0) {
constraints.push(makeTwoVertexDistanceConstraint(index(x+1, y-1), index(x, y), D * 1.414, K * 0.05 / NStep));
edges.push([index(x+1, y-1), index(x, y), K * 0.05 / NStep]);
}
}
}
isInitialized = true;
}
const dotRadius = 2;
let dragIndex = -1;
let lastUpdateTime;
function setup() {
createCanvas(800, 800);
lastUpdateTime = 0;
initialize();
dragIndex = verts.length - 1; //Math.floor(verts.length / 2);
}
function draw() {
clear();
let deltaTime = millis() - lastUpdateTime;
lastUpdateTime += deltaTime;
PBDUpdate(verts, constraints, deltaTime * 0.001, 4);
for(let i = 0; i < verts.length; i++) {
ellipse(verts[i].x[0], verts[i].x[1], dotRadius * 2, dotRadius * 2);
}
for(let i = 0; i < edges.length; i++) {
const v1 = verts[edges[i][0]];
const v2 = verts[edges[i][1]];
stroke(225 * (1.0 - edges[i][2]));
line(
v1.x[0],
v1.x[1],
v2.x[0],
v2.x[1]);
}
if(dragIndex != -1)
{
stroke(200);
line(mouseX, mouseY, verts[dragIndex].x[0], verts[dragIndex].x[1]);
}
}
function mouseMoved() {
if(isInitialized)
{
let index = dragIndex;
verts[index].setForce([
(1.0 * mouseX - verts[index].x[0]) * 10.0,
(1.0 * mouseY - verts[index].x[1]) * 10.0
]);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment