Inspired by this youtube video here
In P5!!! by kraftpy
class MassPoint {
constructor(x, y, mass, radius, frictionCoeff){
this.pos = createVector(x, y);
this.vel = createVector();
this.force = createVector();
this.mass = mass;
this.radius = radius;
this.frictionCoeff = frictionCoeff;
}
applyGravity(){
this.force.add(createVector(0, this.mass * g));
}
update(dt){
let dv = vec.mult(this.force, dt / this.mass);
this.vel.add(dv);
let dp = vec.mult(this.vel, dt);
this.pos.add(dp);
this.force = createVector();
}
handleCollision(polygons){
for(let polygon of polygons){
if(polygon.insideBoundingBox(this.pos)){
let {inside, normal, closestPoint} = polygon.insidePolygon(this.pos);
if(inside){
this.pos = closestPoint;
// friction
let proj = vec.dot(normal, this.vel);
let oppv = vec.sub(this.vel, vec.mult(normal, proj));
this.force.add( vec.mult(oppv, -this.frictionCoeff) );
this.vel = vec.sub(this.vel, vec.mult(normal, 2 * vec.dot(this.vel, normal)) );
break;
}
}
}
}
handleSelfCollision(massPoints){
for(let mp of massPoints){
if(mp !== this){
let d = dist(mp.pos.x, mp.pos.y, this.pos.x, this.pos.y);
if(d < this.radius * 2 && d > 0){
let norm = vec.sub(this.pos, mp.pos);
norm.normalize();
let offset = norm.copy();
offset.setMag(this.radius);
this.pos = vec.add(mp.pos, offset);
this.pos.add( vec.mult(norm, this.radius) );
this.vel = vec.sub(this.vel, vec.mult(norm, 2 * vec.dot(this.vel, norm)));
}
}
}
}
display(){
noStroke();
fill(255, 0, 0);
circle(this.pos.x, this.pos.y, this.radius * 2);
}
}
Spring
class Spring {
constructor(a, b, ks, restLength, kd){
this.a = a;
this.b = b;
this.ks = ks;
this.restLength = restLength;
this.kd = kd;
}
applyForces(){
let a = this.a;
let b = this.b;
let stiffnessForce = vec.sub(b.pos, a.pos).mag();
stiffnessForce = (stiffnessForce - this.restLength) * this.ks;
let dampForce = vec.sub(b.pos, a.pos);
if(dampForce.mag() > 0){
dampForce.normalize();
dampForce = vec.dot( dampForce, vec.sub(b.vel, a.vel) );
dampForce *= this.kd;
}
let force = stiffnessForce + dampForce;
let forceA = vec.sub(b.pos, a.pos);
forceA.setMag(force);
let forceB = forceA.copy();
forceB.mult(-1);
a.force.add(forceA);
b.force.add(forceB);
}
display(){
let a = this.a;
let b = this.b;
stroke(255);
strokeWeight(3);
line(a.pos.x, a.pos.y, b.pos.x, b.pos.y);
}
}
Polygon
class Polygon {
constructor(col){
this.points = [];
this.col = col;
this.boundingBox = {
minX: Infinity,
maxX:-Infinity,
minY: Infinity,
maxY:-Infinity
};
}
addPoint(x, y){
this.points.push(createVector(x, y));
}
calculateBoundingBox(){
for(let p of this.points){
this.boundingBox.minX = min(this.boundingBox.minX, p.x);
this.boundingBox.maxX = max(this.boundingBox.maxX, p.x);
this.boundingBox.minY = min(this.boundingBox.minY, p.y);
this.boundingBox.maxY = max(this.boundingBox.maxY, p.y);
}
}
insideBoundingBox(p){
let {minX, maxX, minY, maxY} = this.boundingBox;
return p.x >= minX && p.x <= maxX && p.y >= minY && p.y <= maxY;
}
insidePolygon(p){
const n = this.points.length;
let itscCount = 0;
let normal;
let closestPoint;
let smallestDist = Infinity;
for(let i = 0; i < n; i++){
let a = this.points[i];
let b = this.points[(i+1) % n];
// Test if inside polygon
if( (a.y >= p.y && b.y <= p.y) || (a.y <= p.y && b.y >= p.y) ) {
let s = a.x + (b.x - a.x) * (p.y - a.y) / (b.y - a.y);
if(p.x >= s){
itscCount++;
}
}
// Find closest point
let ab = vec.sub(b, a).normalize();
let u = vec.mult(ab, vec.dot( vec.sub(p, a), ab ));
let cp = vec.add(a, u);
let norm = vec.sub(vec.sub(p, a), u);
let d = norm.mag();
norm.normalize();
if(d < smallestDist){
smallestDist = d;
normal = norm;
closestPoint = cp;
}
}
return {inside: itscCount % 2 == 1,
normal: normal,
closestPoint: closestPoint
};
}
display(){
strokeWeight(3);
stroke(this.col);
noFill();
beginShape();
const n = this.points.length;
for(let i = 0; i < n + 1; i++){
let p = this.points[i % n];
vertex(p.x, p.y);
}
endShape();
}
}
Main Scene code
const vec = p5.Vector;
const g = 75;
const POINTS_SEP = 20;
const POINTS_MASS = 0.1;
const POINTS_RADIUS = 5;
const SPRING_STIFFNESS = 100;
const SPRING_REST_LENGTH = POINTS_SEP;
const SPRING_DAMP = 1.2;
const FRICTION_COEFF = 10;
let massPoints = [];
let springs = [];
let polygons = [];
function setup() {
createCanvas(600, 400);
polygons.push(new Polygon(color(255)));
polygons[0].addPoint(-width/2, height/2 - 50);
polygons[0].addPoint(-200, height/2 - 50);
polygons[0].addPoint(-180, 100);
polygons[0].addPoint(0, height/2 - 50);
polygons[0].addPoint(width/2, height/2 - 50);
polygons[0].addPoint(width/2, height/2);
polygons[0].addPoint(-width/2, height/2);
polygons.push(new Polygon(color(255)));
polygons[1].addPoint(100, -100);
polygons[1].addPoint(100, 100);
polygons[1].addPoint(150, 100);
polygons[1].addPoint(150, -100);
for(let polygon of polygons){
polygon.calculateBoundingBox();
}
createSoftBlock(6, 9,-100, -200);
}
Main Loop
function draw() {
background(0);
translate(width/2, height/2);
//const dt = frameRate() > 0 ? 1/frameRate() : 0;
const dt = 0.01;
for(let spring of springs){
spring.applyForces();
}
for(let mp of massPoints){
mp.applyGravity();
mp.handleCollision(polygons);
mp.handleSelfCollision(massPoints);
mp.update(dt);
}
for(let element of [...polygons, ...springs, ...massPoints]){
element.display();
}
}
Creating the blobby block
function createSoftBlock(sizeX, sizeY, posX, posY){
let matrix = [];
for(let i = 0; i < sizeY; i++){
matrix.push([]);
for(let j = 0; j < sizeX; j++){
matrix[i].push(new MassPoint(posX + j * POINTS_SEP,
posY + i * POINTS_SEP,
POINTS_MASS, POINTS_RADIUS, FRICTION_COEFF));
}
}
const smallLength = SPRING_REST_LENGTH;
const longLength = SPRING_REST_LENGTH * sqrt(2);
for(let i = 0; i < sizeY - 1; i++){
for(let j = 0; j < sizeX - 1; j++){
let matij = matrix[i][j];
let s1 = new Spring(matij, matrix[i][j+1], SPRING_STIFFNESS, smallLength, SPRING_DAMP);
let s2 = new Spring(matij, matrix[i+1][j], SPRING_STIFFNESS, smallLength, SPRING_DAMP);
let s3 = new Spring(matij, matrix[i+1][j+1], SPRING_STIFFNESS, longLength, SPRING_DAMP);
let s4 = new Spring(matrix[i+1][j], matrix[i][j+1],
SPRING_STIFFNESS, longLength, SPRING_DAMP);
springs.push(s1, s2, s3, s4);
}
}
for(let i = 0; i < sizeY - 1; i++){
let s = new Spring(matrix[i][sizeX - 1], matrix[i + 1][sizeX - 1],
SPRING_STIFFNESS, SPRING_REST_LENGTH, SPRING_DAMP);
springs.push(s);
}
for(let j = 0; j < sizeX - 1; j++){
let s = new Spring(matrix[sizeY - 1][j], matrix[sizeY - 1][j + 1],
SPRING_STIFFNESS, SPRING_REST_LENGTH, SPRING_DAMP);
springs.push(s);
}
for(let i = 0; i < sizeY; i++){
for(let j = 0; j < sizeX; j++){
massPoints.push(matrix[i][j]);
}
}
}
Useful References
Comments