• fullscreen
  • Body.pde
  • Circle.pde
  • EnvCircle.pde
  • Link.pde
  • PointMass.pde
  • World.pde
  • ragdollAquarium.pde
  • class Body {
      /*
         O
        /|\
       / | \
        / \
       |   |
      */
      // each pointmass will be a joint to the body.
      PointMass head;
      PointMass shoulder;
      PointMass elbowLeft;
      PointMass elbowRight;
      PointMass handLeft;
      PointMass handRight;
      PointMass pelvis;
      PointMass kneeLeft;
      PointMass kneeRight;
      PointMass footLeft;
      PointMass footRight;
      Circle headCircle;
      
      float headLength;
      Body (PVector position, float bodyHeight) {
        headLength = bodyHeight / 7.5;
    
        // PointMasses
        // Here, they're initialized with random positions. 
        head = new PointMass(new PVector(position.x + random(-5,5),position.y + random(-5,5)));
        shoulder = new PointMass(new PVector(position.x + random(-5,5),position.y + random(-5,5)));
        elbowLeft = new PointMass(new PVector(position.x + random(-5,5),position.y + random(-5,5)));
        elbowRight = new PointMass(new PVector(position.x + random(-5,5),position.y + random(-5,5)));
        handLeft = new PointMass(new PVector(position.x + random(-5,5),position.y + random(-5,5)));
        handRight = new PointMass(new PVector(position.x + random(-5,5),position.y + random(-5,5)));
        pelvis = new PointMass(new PVector(position.x + random(-5,5),position.y + random(-5,5)));
        kneeLeft = new PointMass(new PVector(position.x + random(-5,5),position.y + random(-5,5)));
        kneeRight = new PointMass(new PVector(position.x + random(-5,5),position.y + random(-5,5)));
        footLeft = new PointMass(new PVector(position.x + random(-5,5),position.y + random(-5,5)));
        footRight = new PointMass(new PVector(position.x + random(-5,5),position.y + random(-5,5)));
        
        // Masses
        // Uses data from http://www.humanics-es.com/ADA304353.pdf
        head.mass = 4;
        shoulder.mass = 26; // shoulder to torso
        elbowLeft.mass = 2; // upper arm mass
        elbowRight.mass = 2; 
        handLeft.mass = 2;
        handRight.mass = 2;
        pelvis.mass = 15; // pelvis to lower torso
        kneeLeft.mass = 10;
        kneeRight.mass = 10;
        footLeft.mass = 5; // calf + foot
        footRight.mass = 5;
        
        // Limbs
        // PointMasses are attached to each other here.
        // Proportions are mainly used from http://www.idrawdigital.com/2009/01/tutorial-anatomy-and-proportion/
        head.attachTo(shoulder, 5/4 * headLength, 1, true);
        elbowLeft.attachTo(shoulder, headLength*3/2, 1, true);
        elbowRight.attachTo(shoulder, headLength*3/2, 1, true);
        handLeft.attachTo(elbowLeft, headLength*2, 1, true);
        handRight.attachTo(elbowRight, headLength*2, 1, true);
        pelvis.attachTo(shoulder,headLength*3.5,0.8,true);
        kneeLeft.attachTo(pelvis, headLength*2, 1, true);
        kneeRight.attachTo(pelvis, headLength*2, 1, true);
        footLeft.attachTo(kneeLeft, headLength*2, 1, true);
        footRight.attachTo(kneeRight, headLength*2, 1, true);
        
        // Head
        headCircle = new Circle(head.position, headLength*0.75);
        headCircle.attachToPointMass(head);
        
        // Invisible Constraints. These add resistance to some limbs from pointing in odd directions.
        // this keeps the head from tilting in extremely uncomfortable positions
        pelvis.attachTo(head, headLength*4.75, 0.02, false);
        // these constraints resist flexing the legs too far up towards the body
        footLeft.attachTo(shoulder, headLength*7.5, 0.001, false);
        footRight.attachTo(shoulder, headLength*7.5, 0.001, false);
        
        // The PointMasses (and circle!) is added to the world
        world.addCircle(headCircle);
        world.addPointMass(head);
        world.addPointMass(shoulder);
        world.addPointMass(pelvis);
        world.addPointMass(elbowLeft);
        world.addPointMass(elbowRight);
        world.addPointMass(handLeft);
        world.addPointMass(handRight);
        world.addPointMass(kneeLeft);
        world.addPointMass(kneeRight);
        world.addPointMass(footLeft);
        world.addPointMass(footRight);
      }
      // This must be used if the body is ever deleted
      void removeFromWorld () {
        world.removeCircle(headCircle);
        world.removePointMass(head);
        world.removePointMass(shoulder);
        world.removePointMass(pelvis);
        world.removePointMass(elbowLeft);
        world.removePointMass(elbowRight);
        world.removePointMass(handLeft);
        world.removePointMass(handRight);
        world.removePointMass(kneeLeft);
        world.removePointMass(kneeRight);
        world.removePointMass(footLeft);
        world.removePointMass(footRight);
      }
    }
    
    // Could be called "Head" if we wanted, since it's basically all it's used for.
    class Circle {
      PVector position;
      float radius;
      
      // Most of the physics is done in the PointMass the Circle is attached to.
      boolean attachedToPointMass = false;
      PointMass attachedPointMass;
      
      Circle (PVector pos, float r) {
        position = pos.get();
        radius = r;
      }
      void solveConstraints () {
        // First move the circle to where its attached PointMass is.
        position = attachedPointMass.position.get();
        
        // Make sure it isn't outside of the screen
        if (position.y < radius)
          position.y = 2*(radius) - position.y;
        if (position.y > height-radius)
          position.y = 2 * (height - radius) - position.y;
        if (position.x > width-radius)
          position.x = 2 * (width - radius) - position.x;
        if (position.x < radius)
          position.x = 2*radius - position.x;
          
        // Move the PointMass to the corrected position.
        attachedPointMass.position = position.get();
      }
      void draw () {
        ellipse(position.x, position.y, radius*2, radius*2);
      }
      void attachToPointMass (PointMass p) {
        attachedPointMass = p;
      }
    }
    
    // The EnvCircle
    // These are the static circles floating around that the bodies collide with.
    class EnvCircle {
      PVector position;
      float radius;
      float radiusSquared;
      
      EnvCircle (PVector pos, float rad) {
        position = pos.get();
        radius = rad;
        radiusSquared = radius*radius;
      }
      
      // detect whether or not a point is colliding with circle, and correct it
      // The algorithm here is modified ball collision handling algorithm, which I wrote about here:
      // www.bluethen.com/wordpress/index.php/processing-app/do-you-like-balls/
      void solveCollision(PointMass pointM) {
        // first we see if the point is inside the circle.
        PVector delta = PVector.sub(pointM.position, position);  
        if (radiusSquared > (sq(delta.x) + sq(delta.y))) {
          float d = sqrt(delta.x * delta.x + delta.y * delta.y);
          
          // Instead of moving the point to the edge of the circle, 
          // we move it outside of the circle depending on how far inside it got.
          // This allows for a proper "bounce" to any collision with the circle.
          float difference = (radius - d) / d;
          pointM.position.add(PVector.mult(delta, difference));
          
          // We allow 3 frames for the bodies to position themselves in empty space before velocities are accounted for.
          if (frameCount < 3)
            pointM.lastPosition.set(pointM.position);
        }
      }
      void draw () {
        ellipse(position.x, position.y, radius * 2, radius * 2);  
      }
    }
    
    // The Link class is used for handling constraints between particles.
    class Link {
      float restingDistance;
      float stiffness;
      
      PointMass p1;
      PointMass p2;
      
      // the scalars are how much "tug" the particles have on each other
      // this takes into account masses and stiffness, and are set in the Link constructor
      float scalarP1;
      float scalarP2;
      
      // if you want this link to be invisible, set this to false
      boolean drawThis;
      
      Link (PointMass which1, PointMass which2, float restingDist, float stiff, boolean drawMe) {
        p1 = which1; // when you set one object to another, it's pretty much a reference.
        p2 = which2; // Anything that'll happen to p1 or p2 in here will happen to the paticles in our array
        
        restingDistance = restingDist;
        stiffness = stiff;
        
        // Masses are accounted for. If you remember the cloth simulator,
        // http://www.openprocessing.org/visuals/?visualID=20140
        // we added this ability in anticipation of future applets that might use mass
        float im1 = 1 / p1.mass; 
        float im2 = 1 / p2.mass;
        scalarP1 = (im1 / (im1 + im2)) * stiffness;
        scalarP2 = (im2 / (im1 + im2)) * stiffness;
        
        drawThis = drawMe;
      }
      
      void constraintSolve () {
        // calculate the distance between the two particles
        PVector delta = PVector.sub(p1.position, p2.position);  
        float d = sqrt(delta.x * delta.x + delta.y * delta.y);
        float difference = (restingDistance - d) / d;
        
        // Uncomment this if you want the ragdolls to be able to tear. 
        //if (d > 30) 
        //  p1.removeLink(this);
    
        // P1.position += delta * scalarP1 * difference
        // P2.position -= delta * scalarP2 * difference
        p1.position.add(PVector.mult(delta, scalarP1 * difference));
        p2.position.sub(PVector.mult(delta, scalarP2 * difference));
      }
    }
    
    // PointMass
    // This is pretty much the Particle class used in Curtain
    // http://www.openprocessing.org/visuals/?visualID=20140
    class PointMass {
      PVector lastPosition; // for calculating position change (velocity)
      PVector position;
    
      PVector acceleration; 
    
      float mass = 1;
      float damping = 20; // friction
    
      // An ArrayList for links, so we can have as many links as we want to this PointMass
      ArrayList links = new ArrayList();
    
      boolean pinned = false;
      PVector pinLocation = new PVector(0,0);
    
      // PointMass constructor
      PointMass (PVector pos) {
        position = pos.get();
        lastPosition = pos.get();
        acceleration = new PVector(0,0);
      }
      
      // The update function is used to update the physics of the particle.
      // motion is applied, and links are drawn here
      void updatePhysics (float timeStep) { 
        // gravity:
        // f(gravity) = m * g
        PVector fg = new PVector(0, mass * world.gravity, 0);
        this.applyForce(fg);
    
        /*
           We use Verlet Integration to simulate the physics
           In Verlet Integration, the rule is simple: any change in position will result in a change of velocity
           Therefore, things in motion will stay in motion. If you want to push a PointMass towards a direction,
           just move its position and it'll continue going that way.   
        */
        // velocity = position - lastPosition
        PVector velocity = PVector.sub(position, lastPosition);
        // apply damping: acceleration -= velocity * (damping/mass)
        acceleration.sub(PVector.mult(velocity,damping/mass)); 
        // newPosition = position + velocity + 0.5 * acceleration * deltaTime * deltaTime
        PVector nextPos = PVector.add(PVector.add(position, velocity), PVector.mult(PVector.mult(acceleration, 0.5), timeStep * timeStep));
        // reset variables
        lastPosition.set(position);
        position.set(nextPos);
        acceleration.set(0,0,0);
    
        // make sure the particle stays in its place if it's pinned
        // (This isn't used for this simulation, but it's there anyways.)
        if (pinned)
          position.set(pinLocation);
      } 
      void updateInteractions () {
        // this is where our interaction comes in.
        if (mousePressed) {
          float distanceSquared = sq(mouseX - position.x) + sq(mouseY - position.y);
          if (mouseButton == LEFT) {
            if (distanceSquared < mouseInfluenceSize) { // remember mouseInfluenceSize was squared in setup()
              // move particles towards where the mouse is moving
              // amount to add onto the particle position:
              position.x += (mouseX - pmouseX) * 0.1 * (sqrt(mouseInfluenceSize) - sqrt(distanceSquared)) / sqrt(mouseInfluenceSize);
              position.y += (mouseY - pmouseY) * 0.1 * (sqrt(mouseInfluenceSize) - sqrt(distanceSquared)) / sqrt(mouseInfluenceSize);
            }
          }
          else { // if the right mouse button is clicking, we tear the cloth by removing links
            if (distanceSquared < mouseTearSize) 
              links.clear();
          }
        }
      }
    
      void draw () {
        // draw the links and points
        stroke(0);
        if (links.size() > 0) {
          for (int i = 0; i < links.size(); i++) {
            Link currentLink = (Link) links.get(i);
            if (currentLink.drawThis) // some links are invisible
              line(position.x, position.y, currentLink.p2.position.x, currentLink.p2.position.y);
          }
        }
        else
          point(position.x, position.y);
      }
      // here we tell each Link to solve constraints
      void solveConstraints () {
        for (int i = 0; i < links.size(); i++) {
          Link currentLink = (Link) links.get(i);
          currentLink.constraintSolve();
        }
        for (int i = 0; i < circles.size(); i++) {
          EnvCircle circle = (EnvCircle) circles.get(i);
          circle.solveCollision(this);
        }
        
        // These if statements keep the particles within the screen
        if (position.y < 1)
          position.y = 2 - position.y;
        if (position.y > height-1)
          position.y = 2 * (height - 1) - position.y;
        if (position.x > width-1)
          position.x = 2 * (width - 1) - position.x;
        if (position.x < 1)
          position.x = 2 - position.x;
      }
    
      // attachTo can be used to create links between this particle and other particles
      void attachTo (PointMass P, float restingDist, float stiff, boolean drawThis) {
        Link lnk = new Link(this, P, restingDist, stiff, drawThis);
        links.add(lnk);
      }
      void removeLink (Link lnk) {
        links.remove(lnk);
      }  
      void removeLink (PointMass P) {
        for (int i = 0; i < links.size(); i++) {
          Link lnk = (Link) links.get(i);
          if ((lnk.p1 == P) || (lnk.p2 == P))
            links.remove(i);
        }
      }
    
      void applyForce (PVector f) {
        // acceleration = (1/mass) * force
        // or
        // acceleration = force / mass
        acceleration.add(PVector.div(PVector.mult(f,1), mass));
      }
    
      void pinTo (PVector location) {
        pinned = true;
        pinLocation.set(location);
      }
    }
    
    
    // World
    // All physics and objects, as well as the time step stuff, are handled here.
    class World {
      // All of our objects
      ArrayList pointMasses = new ArrayList(); 
      ArrayList circles = new ArrayList();
      
      // Psh. Who needs gravity!
      float gravity = 0;
      
      // These variables are used to keep track of how much time is elapsed between each frame
      // they're used in the physics to maintain a certain level of accuracy and consistency
      // this program should run the at the same rate whether it's running at 30 FPS or 300,000 FPS
      long previousTime;
      long currentTime;
      // Delta means change. It's actually a triangular symbol, to label variables in equations
      // some programmers like to call it elapsedTime, or changeInTime. It's all a matter of preference
      // To keep the simulation accurate, we use a fixed time step
      int fixedDeltaTime;
      float fixedDeltaTimeSeconds;
      // the leftOverDeltaTime carries over change in time that isn't accounted for over to the next frame
      int leftOverDeltaTime;
    
      // How many times constraints are solved each frame
      int constraintAccuracy;
      
      World (int deltaTimeLength, int constraintAcc) {
        fixedDeltaTime = deltaTimeLength;
        fixedDeltaTimeSeconds = (float)fixedDeltaTime / 1000;
        previousTime = millis();
        currentTime = previousTime;  
        constraintAccuracy = constraintAcc;
      }
      
      void update () {
        /* Time related stuff */
        currentTime = millis();
        // deltaTimeMS: change in time in milliseconds since last frame
        long deltaTimeMS = currentTime - previousTime;
        // Reset previousTime.
        previousTime = currentTime;
        // timeStepAmt will be how many of our fixedDeltaTime's can fit in the physics for this frame.
        int timeStepAmt = (int)((float)(deltaTimeMS + leftOverDeltaTime) / (float)fixedDeltaTime);
        // reset leftOverDeltaTime.
        leftOverDeltaTime = (int)deltaTimeMS - (timeStepAmt * fixedDeltaTime); 
        float fixedDeltaTimeSeconds = (float)fixedDeltaTime / 1000;
        
        /* Physics */
        for (int iteration = 1; iteration <= timeStepAmt; iteration++) {
          // update each PointMass's position
          for (int i = 0; i < pointMasses.size(); i++) {
            PointMass p = (PointMass) pointMasses.get(i);
            p.updatePhysics(fixedDeltaTimeSeconds);
          }
          // solve the constraints
          for (int x = 0; x < constraintAccuracy; x++) {
            for (int i = 0; i < pointMasses.size(); i++) {
              PointMass p = (PointMass) pointMasses.get(i);
              p.solveConstraints();
            }
            for (int i = 0; i < circles.size(); i++) {
              Circle c = (Circle) circles.get(i);
              c.solveConstraints();  
            }
          }
        }
        
        // we use a separate loop for drawing so points and their links don't get drawn more than once
        // (rendering can be a major resource hog if not done efficiently)
        // also, interactions (mouse dragging) is applied
        for (int i = 0; i < pointMasses.size(); i++) {
          PointMass p = (PointMass) pointMasses.get(i);
          p.updateInteractions();
          p.draw();
        }
        for (int i = 0; i < circles.size(); i++) {
          Circle c = (Circle) circles.get(i);
          c.draw();  
        }
      }
      // Functions for adding PointMasses and Circles.
      void addCircle (Circle c) {
        circles.add(c);  
      }
      void removeCircle (Circle c) {
        circles.remove(c);  
      }
      void addPointMass (PointMass p) {
        pointMasses.add(p);
      }
      void removePointMass (PointMass p) {
        pointMasses.remove(p);
      }
    }
    
    /* 
      Ragdoll Aquarium
      Made by BlueThen on February 21, 2011
      Optimized, refined, etc on March 6, 2011.
      www.bluethen.com
      
      'R' to reset.
      Click to interact
    */
    
    // This arraylist will keep track of the circles all of the bodies will collide with.
    ArrayList circles = new ArrayList();
    // See: World class
    // All of the physics and objects are handled inside the "World"
    World world;
    
    // Distance from each PointMass where interaction is done from the cursor.
    // We keep tear size from the cloth simulator for the fun of it.
    // The cloth simulator can be found at http://www.openprocessing.org/visuals/?visualID=20140
    float mouseInfluenceSize = 100; 
    float mouseTearSize = 20;
    
    int bodyCount = 300;
    int circleCount = 3;
    // How tall is everyone? (Pixels)
    int bodyHeights = 20;
    // How big are the circles? (Radius)
    int circleMin = 50;
    int circleMax = 100;
    
    // Setup is called whenever the program starts
    void setup () {
      // 640x480.
      // P2D is the renderer used.
      size(640,480, P2D);
      
      // The "sizes" are squared here, so they don't need to be when compared in the PointMass class
      mouseInfluenceSize *= mouseInfluenceSize;
      mouseTearSize *= mouseTearSize;
      
      // Reset function initializes World, the bodies, and the circles in our environment.
      reset();
    }
    // Draw () is called over and over. It's our "frame" of the animation.
    void draw () {
      // Color everything white so we can draw on top of it again.
      background(255);
      
      // Update physics, draw bodies, etc.
      world.update();
      
      // Draw the environment
      for (int i = 0; i < circles.size(); i++) {
        EnvCircle circle = (EnvCircle) circles.get(i);
        circle.draw();
      }
      
      if (frameCount % 60 == 0)
        println("Frame rate is " + frameRate);
    }
    
    // The reset function randomly places bodies and circles around the screen.
    void reset () {
      // World class is constructed with 2 parameters: (int deltaTimeLength, int constraintAcc)
      // deltaTimeLength is the timestep used. Smaller would be more accurate.
      // constraintAcc is how many times constraints are solved per timestep.
      world = new World(15, 5);
      
      // Clear the cirlces just in case reset() has already been called
      circles.clear();
      
      // Create the Bodies. in the Body constructor, everything is added to the World automatically.
      for (int i = 0; i < bodyCount; i++) {
        Body body = new Body(new PVector(random(width), random(height)), bodyHeights);
      }  
      // Add circles to the environment
      for (int i = 0; i < circleCount; i++) {
        EnvCircle circle = new EnvCircle(new PVector(random(width), random(height)), random(circleMin,circleMax));
        circles.add(circle);
      }  
      // set frameCount to 0
      // The first constraint solve can cause ragdolls to go a little crazy
      // (bodies spawned inside circles will fly out)
      // So in EnvCircle, when bodies are pushed away, their velocities are kept to 0 if the frameCount is too low
      frameCount = 0;
    }
    
    // Allow the user to reset everything when they press 'R'.
    void keyPressed() {
      if ((key == 'r') || (key == 'R'))
        reset();
    }
    

    code

    tweaks (0)

    about this sketch

    This sketch is running as Java applet, exported from Processing.

    license

    advertisement

    Jared Counts

    Ragdoll Aquarium

    Add to Faves Me Likey@! 37
    You must login/register to add this sketch to your favorites.

    In this applet, we make use of the Verlet Integration method from Curtain, and create little ragdolls.

    I did my best to comment the code for others to use.

    Please visit http://www.bluethen.com for more details on this applet, it's formulas, and other applets as well. Thank you.

    'R' to reset
    Click and drag to interact.

    Nom Bot
    24 Apr 2011
    I think this is fun and very cool.
    Fantastic! Very well done. I'll be watching your work.
    ale plus+
    19 Nov 2011
    It´s a pity the thumbnail isn´t as attractive as the sketch. This one is really funny!
    ;-)
    Jared Counts
    26 Nov 2011
    Ale: thanks for bringing that up. This one's kind of challenging for making an attractive thumbnail.
    Hieronymus Bosch would enjoy this a lot ) ..I can't help myself, that image of heaping bodies activates some decent sickness inside.. Nicely done.
    Irene Calangian
    26 May 2012
    hi, i really like this!. do you mind if i use some of your codes for my school project? i promise i will put you on my references.. i will even give you the link to my final project.. hope you'll respond! thanks! :)
    Irene Calangian
    26 May 2012
    hi, i really like this!. do you mind if i use some of your codes for my school project? i promise i will put you on my references.. i will even give you the link to my final project.. hope you'll respond! thanks! :)
    Jared Counts
    31 May 2012
    Irene: Go ahead! If you have any issues, please ask.
    adila faruk plus+
    28 Apr 2013
    this is so cool!
    Blyk
    30 May 2014
    this is crazy XD
    NICOLE SHI
    16 Dec 2014
    this is perfect
    You need to login/register to comment.