Rope physics prototype 3D

Here is an example where a 3D rope can be controlled with the mouse. Press ‘w’ to toggle wind, ‘g’ to toggle gravity, space to pause and mouse wheel to increase/decrease wind strength. Boxes can be spawned by pressing ‘1’. I should also mention that none of the objects collide with each other.

ArrayList<Spring> springs;
PVector grab, grabVel;
PVector wind;
boolean selected=false;
boolean pause = false;
boolean windOn = false;
boolean gravityOn = true;
float windStrength = 10;
float num = 10;
Spring chain;
void setup() {
  size(400, 400, P3D);
  springs = new ArrayList<Spring>();
  chain = new Spring(new PVector(), new PVector());
  chain.len=6;
  springs.add(chain);
  for(int i = 0; i < 8; i++){
  Spring newChain = new Spring(springs.get(springs.size()-1).v2,new PVector());
  newChain.vel1=springs.get(springs.size()-1).vel2;
  newChain.d1=true;
  newChain.len=6;
  springs.add(newChain);
  }
  wind = new PVector();
}
void draw() {
  noCursor();
  //add random wind
  wind.mult(.99);
  PVector p = PVector.random3D();
  p.mult(.1);
  wind.add(p);
  background(255);
  lights();
  for (int i = 0; i < springs.size(); i++) {
    if(!pause){
    springs.get(i).update();
    }
  }
  for (int i = 0; i < springs.size(); i++) {
    springs.get(i).display();
  }
  chain.v1.x=mouseX;
  chain.v1.y=mouseY;
  chain.vel1.x+=mouseX-pmouseX;
  chain.vel1.y+=mouseY-pmouseY;
  chain.v1.z=0;
}
void keyReleased() {
  if (key==' ') {
    pause=!pause;
  }
  if (key=='1') {
    makeBox(new PVector(mouseX, mouseY, random(-100, 100)));
  }
  if (key=='g'||key=='G') {
    gravityOn=!gravityOn;
  }
  if (key=='w'||key=='W') {
    windOn=!windOn;
  }
}
void mouseWheel(MouseEvent event) {
  float e = event.getCount();
  windStrength-=e;
  if (windStrength<1) {
    windStrength=1;
  }
}
class Spring {
  PVector v1, v2;
  PVector vel1, vel2;
  float len = num*3;
  float damp = 1;
  float groundFriction = .34;
  float tightness = 1;
  boolean d1, d2;
  Spring(PVector V1, PVector V2) {
    v1 = V1;
    v2 = V2;
    vel1 = new PVector();
    vel2 = new PVector();
    d1 = false;
    d2 = false;
  }
  void update() {
    PVector w = wind.copy();
    w.mult((windStrength*4)/100);
    //since two springs can share the same PVector
    //we need to 'deactivate' one of them to prevent
    //double updating where we don't want it
    if (!d1) {
      if (gravityOn)vel1.y+=.34;
      if (windOn)vel1.add(w);
      vel1.mult(.9);
      v1.add(vel1);
    }

    if (!d2) {
      if (gravityOn)vel2.y+=.34;
      if (windOn)vel2.add(w);
      vel2.mult(.9);
      v2.add(vel2);
    }


    //spring math
    float ex = len-dist(v1.x, v1.y, v1.z, v2.x, v2.y, v2.z)*.5;
    PVector dir = v2.copy();
    dir.sub(v1);
    dir.normalize();
    dir.mult(ex);
    v1.sub(PVector.mult(dir, tightness));
    v2.add(PVector.mult(dir, tightness));
    vel1.sub(PVector.mult(dir, damp));
    vel2.add(PVector.mult(dir, damp));
  }
  void display() {
    if (v1.y>height-25) {
      v1.y=height-25;
      vel1.mult(groundFriction);
    }
    if (v2.y>height-25) {
      v2.y=height-25;
    }
    if (v1.x>width) {
      v1.x=width;
    }
    if (v2.x>width) {
      v2.x=width;
    }
    if (v1.x<0) {
      v1.x=0;
    }
    if (v2.x<0) {
      v2.x=0;
    }
    fill(255, 0, 0);
    noStroke();
    if (!d1) {
      pushMatrix();
      translate(v1.x, v1.y, v1.z);
      sphere(4);
      popMatrix();
    }
    if (!d2) {
      pushMatrix();
      translate(v2.x, v2.y, v2.z);
      sphere(4);
      popMatrix();
    }
    stroke(0);
    line(v1.x, v1.y, v1.z, v2.x, v2.y, v2.z);
  }
}
void connectSpring(PVector p1, PVector v1, PVector p2, PVector v2) {
  Spring spr = new Spring(p1, p2);
  spr.vel2=v2;
  spr.vel1=v1;
  spr.d1=true; //deactivates some properties
  spr.d2=true; //deactivates some properties
  springs.add(spr);
}
void connectSpring(PVector p, PVector v) {
  Spring spr = new Spring(new PVector(mouseX, mouseY), p);
  spr.vel2=v;
  spr.d2=true; //deactivates some properties
  springs.add(spr);
}
void makeBox(PVector pos) {
  num=10;
  PVector p1 = new PVector(pos.x-30, pos.y-30, pos.z-30);
  PVector p2 = new PVector(pos.x+30, pos.y-30, pos.z-30);
  PVector p3 = new PVector(pos.x-30, pos.y+30, pos.z-30);
  PVector p4 = new PVector(pos.x+30, pos.y+30, pos.z-30);
  PVector p5 = new PVector(pos.x-30, pos.y-30, pos.z+30);
  PVector p6 = new PVector(pos.x+30, pos.y-30, pos.z+30);
  PVector p7 = new PVector(pos.x-30, pos.y+30, pos.z+30);
  PVector p8 = new PVector(pos.x+30, pos.y+30, pos.z+30);
  //for edges
  Spring spr1 = new Spring(p1, p2);
  Spring spr2 = new Spring(p3, p4);
  Spring spr3 = new Spring(p5, p6);
  Spring spr4 = new Spring(p7, p8);
  springs.add(spr1);
  springs.add(spr2);
  springs.add(spr3);
  springs.add(spr4);
  connectSpring(p1, spr1.vel1, p5, spr3.vel1);
  connectSpring(p2, spr1.vel2, p6, spr3.vel2);
  connectSpring(p3, spr2.vel1, p7, spr4.vel1);
  connectSpring(p4, spr2.vel2, p8, spr4.vel2);
  connectSpring(p1, spr1.vel1, p3, spr2.vel1);
  connectSpring(p2, spr1.vel2, p4, spr2.vel2);
  connectSpring(p5, spr3.vel1, p7, spr4.vel1);
  connectSpring(p6, spr3.vel2, p8, spr4.vel2);
  num=sqrt(2)*10; 
  // for faces
  connectSpring(p1, spr1.vel1, p6, spr3.vel2);
  connectSpring(p2, spr1.vel2, p5, spr3.vel1);
  connectSpring(p3, spr2.vel1, p8, spr4.vel2);
  connectSpring(p4, spr2.vel2, p7, spr4.vel1);
  connectSpring(p1, spr1.vel1, p7, spr4.vel1);
  connectSpring(p5, spr3.vel1, p3, spr2.vel1);
  connectSpring(p2, spr1.vel2, p8, spr4.vel2);
  connectSpring(p6, spr4.vel2, p4, spr2.vel2);
  connectSpring(p1, spr1.vel1, p4, spr2.vel2);
  connectSpring(p2, spr1.vel2, p3, spr2.vel1);
  connectSpring(p5, spr3.vel1, p8, spr4.vel2);
  connectSpring(p6, spr3.vel2, p7, spr4.vel1);
  num=sqrt(3)*10;
  //for diagonal support
  connectSpring(p1, spr1.vel1, p8, spr4.vel2);
  connectSpring(p2, spr1.vel2, p7, spr4.vel1);
  connectSpring(p5, spr3.vel1, p4, spr2.vel2);
  connectSpring(p6, spr3.vel2, p3, spr2.vel1);
}

Here is the relevant bit of code for the rope physics where I’ve set damp equal to .24 and tightness equal to .46 and springLength can be any positive number.

//spring math
    float ex = springLength-dist(v1.x, v1.y, v1.z, v2.x, v2.y, v2.z)*.5;
    PVector dir = v2.copy();
    dir.sub(v1);
    dir.normalize();
    dir.mult(ex);
    v1.sub(PVector.mult(dir, .46));
    v2.add(PVector.mult(dir, .46));
    vel1.sub(PVector.mult(dir, .24));
    vel2.add(PVector.mult(dir, .24));

Edit: It’s also important to understand how connecting Springs work. For example:

Connecting Springs spr_1 and spr_2 would require setting spr_2.v1 = spr_1.v2 and similarly spr_2.vel1 = spr_1.vel2.

Make sure to ‘deactivate’ one of the connected springs. For example, if you connected spr_2.v1 and spr_1.v2, you should either set spr_2.d1 = true OR spr_1.d2 = true

The math isn’t spot on so any feedback would be appreciated!

2 Likes

Impressive.

when you use

      fill(255, 0, 0); 
      noStroke();

in both places for your spheres, they look better

Use lights(); after background() in draw()

Use ctrl-t to auto format your code (indents)

Thanks for sharing!

Chrisir

1 Like

Also when pressing ‘1’ the Z is missing.

I changed it to -1100

 if (key=='1') {
    makeBox(new PVector(mouseX, mouseY, random(-1100, 100)));
  }

and

removed 2 lines that were killing Z for me

  Spring(PVector V1, PVector V2) {
    v1 = V1;
    v2 = V2;
    //v1.z=random(-5, 5);
    //v2.z=random(-5, 5);
1 Like

Ah, I forgot to move that part. Works a lot nicer now. Thanks for the feedback, Chrisir

1 Like

Also when the cubes hit the floor they should be marked as unmoving so the wind does not affect them anymore.

Based on your suggestion I’ve updated the code so that there’s now a ground friction coefficient, which slows grounded cubes a bit but still allows movement. I’ve also made the initial rope length more adjustable and fixed it’s weird weight problems (no offense to the rope)

1 Like