top of page
Search
cedarcantab

Spring Model Soft body Physics

Updated: Mar 29



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




3 views0 comments

Recent Posts

See All

p2 naive broadphase

var Broadphase = require('../collision/Broadphase'); module.exports = NaiveBroadphase; /** * Naive broadphase implementation. Does N^2...

sopiro motor constranit

import { Matrix2, Vector2 } from "./math.js"; import { RigidBody } from "./rigidbody.js"; import { Settings } from "./settings.js";...

Comments


記事: Blog2_Post
bottom of page