More Springs

Please see Golan’s Notes on Springs. This page is full of great examples using Particle and Spring objects.

This page has some additional notes on designing software, using springs as an example.

The question is: How do we represent springs?

There is no one answer.

How would you implement springs?

Let’s look at several different ways. All are “correct,” but you may find some methods more attractive than others. We’ll use the triangle example.

Springs as Code

Let’s start by simply “hard coding” the spring forces using code. Every time we run draw(), we’ll have draw() run some code that explicitly calculates spring forces and applies them to particles.

In this approach, the springs are implemented in code, and we can say even represented in code — there’s no variable or object that can say “I am the spring!” No, the springs are in the code.

First, let’s remind ourselves how this code behaves:
springs-triangle-code

// springs-triangle-code.js -- springs as code

var myParticles = [];

var springConstant = 0.1;
var restLength = 100;

// The index in the particle array, of the one the user has clicked.
var whichParticleIsGrabbed = -1;

//-------------------------
function setup() {
    createCanvas(600, 400);
    createParticles(); 
}

//-------------------------
function createParticles(){
    var particle0 = new Particle(); 
    var particle1 = new Particle(); 
    var particle2 = new Particle(); 
    
    particle0.set(250,200);
    particle1.set(350,200);
    particle2.set(300,286);
    
    particle0.bHardBoundaries = true; 
    particle1.bHardBoundaries = true; 
    particle2.bHardBoundaries = true; 
    
    myParticles.push(particle0);
    myParticles.push(particle1);
    myParticles.push(particle2);
}


function mousePressed() {
    // If the mouse is pressed, 
    // find the closest particle, and store its index.
    whichParticleIsGrabbed = -1;
    var maxDist = 9999;
    for (var i=0; i<myParticles.length; i++) {
        var dx = mouseX - myParticles[i].px;
        var dy = mouseY - myParticles[i].py;
        var dh = sqrt(dx*dx + dy*dy);
        if (dh < maxDist) {
            maxDist = dh;
            whichParticleIsGrabbed = i;
        }
    }
}
 
 
function draw() {
    background (0);
 
    for (var i=0; i<myParticles.length; i++) {
        myParticles[i].addForce(0, 0.1); // gravity!
        myParticles[i].update(); // update all locations
    }
 
    if (mouseIsPressed && (whichParticleIsGrabbed > -1)) {
        // If the user is grabbing a particle, peg it to the mouse.
        myParticles[whichParticleIsGrabbed].px = mouseX;
        myParticles[whichParticleIsGrabbed].py = mouseY;
    }
    
    // Springs and their forces are represented here by the code
    // itself.
    // Advantage: You can read exactly what computation is done.
    // Disadvantage: Irregular forces and connections could require
    //    a lot of special cases in the code.
 
    // Spring connections are from every p[i] to p[(i+1) % n]
    for (var i = 0; i < myParticles.length; i++) {
        var p = myParticles[i];
        var q = myParticles[(i + 1) % myParticles.length];
        springCalculateDraw(p, q);
    }

    for (var i=0; i<myParticles.length; i++) {
        myParticles[i].render(); // render all particles
    }
    
    fill(255); 
    noStroke(); 
    text("Grab a point", 5,20); 
}


function springCalculateDraw(p, q) {
    var dx = p.px - q.px;
    var dy = p.py - q.py;
    var dh = sqrt(dx * dx + dy * dy);
    if (dh > 1) {
        var distention = dh - restLength;
        var restorativeForce = springConstant * distention; // F = -kx
        var fx = (dx / dh) * restorativeForce;
        var fy = (dy / dh) * restorativeForce;
        p.addForce(-fx, -fy);
        q.addForce(fx, fy);
    }
    // now draw the spring
    stroke(255);
    line(p.px, p.py, q.px, q.py);
}


//==========================================================
var Particle = function Particle() {
    this.px = 0;
    this.py = 0;
    this.vx = 0;
    this.vy = 0;
    this.mass = 1.0;
    this.damping = 0.96;
    
    this.bFixed = false;
    this.bLimitVelocities = true;
    this.bPeriodicBoundaries = false;
    this.bHardBoundaries = false;
    
    
    // Initializer for the Particle
    this.set = function(x, y) {
        this.px = x;
        this.py = y;
        this.vx = 0;
        this.vy = 0;
        this.damping = 0.96;
        this.mass = 1.0;
    };

    // Add a force in. One step of Euler integration.
    this.addForce = function(fx, fy) {
        var ax = fx / this.mass;
        var ay = fy / this.mass;
        this.vx += ax;
        this.vy += ay;
    };

    // Update the position. Another step of Euler integration.
    this.update = function() {
        if (this.bFixed === false){
            this.vx *= this.damping;
            this.vy *= this.damping;
    
            this.limitVelocities();
            this.handleBoundaries();
            this.px += this.vx;
            this.py += this.vy;
        }
    };

    this.limitVelocities = function() {
        if (this.bLimitVelocities) {
            var speed = sqrt(this.vx * this.vx + this.vy * this.vy);
            var maxSpeed = 10;
            if (speed > maxSpeed) {
                this.vx *= maxSpeed / speed;
                this.vy *= maxSpeed / speed;
            }
        }
    };

    this.handleBoundaries = function() {
        if (this.bPeriodicBoundaries) {
            if (this.px > width) this.px -= width;
            if (this.px < 0) this.px += width;
            if (this.py > height) this.py -= height;
            if (this.py < 0) this.py += height;
        } else if (this.bHardBoundaries) {
            if (this.px >= width){
                this.vx = abs(this.vx)*-1;
            }
            if (this.px <= 0){
                this.vx = abs(this.vx);
            }
            if (this.py >= height){
                this.vy = abs(this.vy)*-1;
            }
            if (this.py <= 0){
                this.vy = abs(this.vy);
            }
        }
    };

    this.render = function() {
        fill(255);
        ellipse(this.px, this.py, 9, 9);
    };
}


Implementation Details

Let me call your attention to code in the middle of draw():

1
2
3
4
5
6
7
8
9
10
11
12
    // Springs and their forces are represented here by the code
    // itself.
    // Advantage: You can read exactly what computation is done.
    // Disadvantage: Irregular forces and connections could require
    //    a lot of special cases in the code.
 
    // Spring connections are from every p[i] to p[(i+1) % n]
    for (var i = 0; i < myParticles.length; i++) {
        var p = myParticles[i];
        var q = myParticles[(i + 1) % myParticles.length];
        springCalculateDraw(p, q);
    }

As you can see, the loop in line 8 finds pairs of adjacent particles in myParticles and applies spring force by calling springCalculateDraw(p, q), which also draws the spring. Notice the use of the modulo operator (%) so that the “next” particle wraps around to index 0 when we get to the last particle.

Here is springCalculateDraw(p, q). It calculates and applies forces between particles p and q. Note that all springs have the same length and spring constant. If this were not the case, we’d have to calculate or look these up these in the code somehow and pass them in as additional parameters:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function springCalculateDraw(p, q) {
    var dx = p.px - q.px;
    var dy = p.py - q.py;
    var dh = sqrt(dx * dx + dy * dy);
    if (dh > 1) {
        var distention = dh - restLength;
        var restorativeForce = springConstant * distention; // F = -kx
        var fx = (dx / dh) * restorativeForce;
        var fy = (dy / dh) * restorativeForce;
        p.addForce(-fx, -fy);
        q.addForce(fx, fy);
    }
    // now draw the spring
    stroke(255);
    line(p.px, p.py, q.px, q.py);
}

Springs as Properties

Another way to represent springs is particle-centric. What if the particles “know” what springs they are attached to? Then, when we update the particles, each particle can calculate spring forces for each spring it is attached to.

After a little thought, it seems simpler to attach each spring to one particle rather than both particles. The particle that “owns” the spring will be responsible for calculating the force at both ends of the spring. Thus we can use the same springCalculateDraw() function shown above.

If we were to associate each spring to both particles that it attaches to, we’d have a representation that seems like a more faithful model of the overall system, but then we’d have to calculate each spring force twice — once for each particle — or we’d have to somehow prevent the second particle from recomputing the spring forces applied by the first particle.

Implementation Details

Here’s an implementation where springs are represented as properties of particles. Each particle has a property called connections, which stores an array of other particles. In the triangle example, each connections array will have just one other particle representing a spring between this particle and the other particle.

Here’s the modification to make the connections. (We could have used the “modulo” trick and a loop, but in this case just made the connections separately.)

1
2
3
4
5
6
7
8
9
10
function createSpringMeshConnectingParticles() {
    // Stitch the particles together by springs.
    var p0 = myParticles[0];
    var p1 = myParticles[1];
    var p2 = myParticles[2];
 
    p0.connect(p1);
    p1.connect(p2);
    p2.connect(p0);
}

And here’s the implementation of the connect() method:

1
2
3
4
5
6
7
var Particle = function Particle() {
    ...
    this.connections = [];
    ...
    this.connect = function(p) {
        this.connections.push(p);
    }

Finally, the render() method of Particle is modified to calculate spring forces. Note the iteration over all the connections in line 4:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    this.render = function() {
        fill(255);
        ellipse(this.px, this.py, 9, 9);
        for (var i = 0; i < this.connections.length; i++) {
            var p = this.connections[i];
            var dx = this.px - p.px;
            var dy = this.py - p.py;
            var dh = sqrt(dx * dx + dy * dy);
            if (dh > 1) {
                var distention = dh - restLength;
                var restorativeForce = springConstant * distention; // F = -kx
                var fx = (dx / dh) * restorativeForce;
                var fy = (dy / dh) * restorativeForce;
                this.addForce(-fx, -fy);
                p.addForce(fx, fy);
            }
            stroke(255);
            line(this.px, this.py, p.px, p.py);
        }
    };

The complete code for springs as properties is here.

Springs as Objects

Finally, we come to the approach shown in Golan’s Springs page, where springs are represented explicitly as objects. You can go to that page to see the code. The main things to look for are:

  1. There is a new object type: the Spring
  2. There must be a list to keep track of springs as well as particles
  3. There must be a loop to apply each spring’s forces to connected particles at each frame
  4. There must be code to create each spring object and “attach” it to particles

Summary

There you have it. Three ways to represent springs: as code, as properties, as objects.

Code is creative and definitely has an esthetic component. Which method is simplest? Which seems right? Which is most beautiful? Which is most understandable? Which is most flexible?

These are all important attributes of code. Designing good code that balances all these concerns is as much an art as it is science or engineering.