This page is a continuation of notes beginning with “Week 9“
REMARK: Examples on this page will run faster when just one sketch is running in the browser! All examples can be downloaded from this zip file.
The universe can be modeled from springs. Nearly every solid material can be thought of as a series of particles which are attached to each other by elastic forces.
From Isaac Newton (1686) we know his second law of motion, that F = ma.
This states that in general, force is proportional to mass times acceleration.
From Robert Hooke (1678) we know the spring law, F = -kx.
This states that the force applied by a spring, is proportional (by some constant k) to its distention x (amount of stretch or compression). The minus sign tells us that the spring’s “restorative force” happens in the opposite direction.
To take an example, if you stretch a spring (which is rigidly fixed at one end) one centimeter to the east, it will apply a restoring force towards the west. If you stretch it two centimeters eastward, it will apply a westerly force which is twice as strong.
Because F = ma and F = -kx, we can derive that ma = -kx. Interestingly, this is actually a “second-order differential equation” — which describes a relationship between a particle’s position, x, and its acceleration (or the rate of change of the rate of change of its position), a. The solution to such equations always take the form of an “exponentially damped cosinusoid”, in what is called damped harmonic motion:
Pleasingly, this exactly captures the movement of a spring over time: it wiggles back and forth, gradually losing energy due to friction. Depending on the amount of damping, the spring may be very wiggly, or not. We use terms like overdamped and underdamped to describe this:
The constant k is the “spring constant” which is unique for each material. When the value of k is high, the material is stiff, snappy or brittle. When k is low, the material is soft, flexible, stretchy. (High values of k will expose the limits of Euler integration, our current numerical method for solving springs here; if k is too high, the simulation will “explode”. We’ll let you discover that…). You can find the value of k in the code samples below. Good values for our solver are in the range of 0.01 to 0.5; try using 0.1 to get started.
There is also a damping factor in our particles which describes how much energy they retain after each frame of animation. A value of 0.96 is fine to get started (meaning, they lose 4% of their energy each frame). Under no circumstances should you ever set this higher than 1.0! Lower values (e.g. 0.7, 0.5, etc.) will cause overdamping.
Examples
Springs 1 (Simple)
Here, two particles are connected to each other by a spring. Pulling one away from the other, causes the spring to exert an elastic restorative force on each.
var myParticles = [];
var mySprings = [];
// The index in the particle array, of the one the user has clicked.
var whichParticleIsGrabbed = -1;
// ========== PARTICLE METHODS =============
// Update the position based on force and velocity
function particleUpdate() {
if (this.bFixed == false) {
this.vx *= this.damping;
this.vy *= this.damping;
this.limitVelocities();
this.handleBoundaries();
this.px += this.vx;
this.py += this.vy;
}
}
// Prevent particle velocity from exceeding maxSpeed
function particleLimitVelocities() {
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;
}
}
}
// do boundary processing if enabled
function particleHandleBoundaries() {
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);
}
if (this.px <= 0) {
this.vx = abs(this.vx);
}
if (this.py >= height) {
this.vy = -abs(this.vy);
}
if (this.py <= 0) {
this.vy = abs(this.vy);
}
}
}
// draw the particle as a white circle
function particleDraw() {
fill(255);
ellipse(this.px, this.py, 9, 9);
}
// add a force to the particle using F = mA
function particleAddForce(fx, fy) {
var ax = fx / this.mass;
var ay = fy / this.mass;
this.vx += ax;
this.vy += ay;
}
// make a new particle
function makeParticle(x, y, dx, dy) {
var p = {px: x, py: y, vx: dx, vy: dy,
mass: 1.0, damping: 0.96,
bFixed: false,
bLimitVelocities: false,
bPeriodicBoundaries: false,
bHardBoundaries: false,
addForce: particleAddForce,
update: particleUpdate,
limitVelocities: particleLimitVelocities,
handleBoundaries: particleHandleBoundaries,
draw: particleDraw
}
return p;
}
// ============ SPRING METHODS =============
// update particles with spring force
function springUpdate() {
var dx = this.p.px - this.q.px;
var dy = this.p.py - this.q.py;
var dh = dist(this.p.px, this.p.py, this.q.px, this.q.py);
if (dh > 1) {
var distention = dh - this.restLength;
// computer F = -kx:
var restorativeForce = this.springConstant * distention;
var fx = (dx / dh) * restorativeForce;
var fy = (dy / dh) * restorativeForce;
this.p.addForce(-fx, -fy);
this.q.addForce( fx, fy);
}
}
// draw spring as a white line
function springDraw() {
stroke(255);
line(this.p.px, this.p.py, this.q.px, this.q.py);
}
// make a new spring object
function makeSpring(p1, p2, k) {
var s = {p: p1, q: p2,
restLength: dist(p1.px, p1.py, p2.px, p2.py),
springConstant: k,
update: springUpdate,
draw: springDraw
}
return s;
}
// ========== THE MAIN PROGRAM ============
// two particles connected by a spring
function setup() {
createCanvas(600, 400);
createParticles();
createSpringMeshConnectingParticles();
frameRate(10);
}
function createParticles(){
var particle0 = makeParticle(250, 200, 0, 0);
var particle1 = makeParticle(350, 200, 0, 0);
myParticles.push(particle0);
myParticles.push(particle1);
}
function createSpringMeshConnectingParticles() {
// The spring constant.
var K = 0.1;
// Stitch the particles together by a spring.
var p = myParticles[0];
var q = myParticles[1];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
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 dh = dist(mouseX, mouseY,
myParticles[i].px, myParticles[i].py)
if (dh < maxDist) {
maxDist = dh;
whichParticleIsGrabbed = i;
}
}
}
function draw() {
background(0);
for (var i = 0; i < myParticles.length; i++) {
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;
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].update(); // draw all springs
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].draw(); // draw all springs
}
for (var i = 0; i < myParticles.length; i++) {
myParticles[i].draw(); // draw all particles
}
fill(255);
noStroke();
text("Grab a point", 5, 20);
}
When particles are connected by rigid elements in a triangle, we have the fundamental element of truss structures. All architects know this. (The following simulation also has a gravity force added in to the particles, so that they are affected both by gravity and by spring forces.)
springs-triangle.js
var myParticles = [];
var mySprings = [];
// The index in the particle array, of the one the user has clicked.
var whichParticleIsGrabbed = -1;
// ========== PARTICLE METHODS =============
// Update the position based on force and velocity
function particleUpdate() {
if (this.bFixed == false) {
this.vx *= this.damping;
this.vy *= this.damping;
this.limitVelocities();
this.handleBoundaries();
this.px += this.vx;
this.py += this.vy;
}
}
// Prevent particle velocity from exceeding maxSpeed
function particleLimitVelocities() {
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;
}
}
}
// do boundary processing if enabled
function particleHandleBoundaries() {
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);
}
if (this.px <= 0) {
this.vx = abs(this.vx);
}
if (this.py >= height) {
this.vy = -abs(this.vy);
}
if (this.py <= 0) {
this.vy = abs(this.vy);
}
}
}
// draw the particle as a white circle
function particleDraw() {
fill(255);
ellipse(this.px, this.py, 9, 9);
}
// add a force to the particle using F = mA
function particleAddForce(fx, fy) {
var ax = fx / this.mass;
var ay = fy / this.mass;
this.vx += ax;
this.vy += ay;
}
// make a new particle
function makeParticle(x, y, dx, dy) {
var p = {px: x, py: y, vx: dx, vy: dy,
mass: 1.0, damping: 0.96,
bFixed: false,
bLimitVelocities: false,
bPeriodicBoundaries: false,
bHardBoundaries: false,
addForce: particleAddForce,
update: particleUpdate,
limitVelocities: particleLimitVelocities,
handleBoundaries: particleHandleBoundaries,
draw: particleDraw
}
return p;
}
// ============ SPRING METHODS =============
// update particles with spring force
function springUpdate() {
var dx = this.p.px - this.q.px;
var dy = this.p.py - this.q.py;
var dh = dist(this.p.px, this.p.py, this.q.px, this.q.py);
if (dh > 1) {
var distention = dh - this.restLength;
// computer F = -kx:
var restorativeForce = this.springConstant * distention;
var fx = (dx / dh) * restorativeForce;
var fy = (dy / dh) * restorativeForce;
this.p.addForce(-fx, -fy);
this.q.addForce( fx, fy);
}
}
// draw spring as a white line
function springDraw() {
stroke(255);
line(this.p.px, this.p.py, this.q.px, this.q.py);
}
// make a new spring object
function makeSpring(p1, p2, k) {
var s = {p: p1, q: p2,
restLength: dist(p1.px, p1.py, p2.px, p2.py),
springConstant: k,
update: springUpdate,
draw: springDraw
}
return s;
}
// ========== THE MAIN PROGRAM ============
// two particles connected by a spring
function setup() {
createCanvas(600, 400);
createParticles();
createSpringMeshConnectingParticles();
frameRate(10);
}
function createParticles(){
var particle0 = makeParticle(250, 200, 0, 0);
var particle1 = makeParticle(350, 200, 0, 0);
var particle2 = makeParticle(300, 286, 0, 0);
// set boundary behavior
particle0.bHardBoundaries = true;
particle1.bHardBoundaries = true;
particle2.bHardBoundaries = true;
myParticles.push(particle0);
myParticles.push(particle1);
myParticles.push(particle2);
}
function createSpringMeshConnectingParticles() {
// The spring constant.
var K = 0.1;
// Stitch the particles together by a spring.
var p = myParticles[0];
var q = myParticles[1];
var r = myParticles[2];
var aSpring0 = makeSpring(p, q, K);
mySprings.push(aSpring0);
var aSpring1 = makeSpring(q, r, K);
mySprings.push(aSpring1);
var aSpring2 = makeSpring(p, r, K);
mySprings.push(aSpring2);
}
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 dh = dist(mouseX, mouseY,
myParticles[i].px, myParticles[i].py)
if (dh < maxDist) {
maxDist = dh;
whichParticleIsGrabbed = i;
}
}
}
function draw() {
background(0);
for (var i = 0; i < myParticles.length; i++) {
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;
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].update(); // draw all springs
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].draw(); // draw all springs
}
for (var i = 0; i < myParticles.length; i++) {
myParticles[i].draw(); // draw all particles
}
fill(255);
noStroke();
text("Grab a point", 5, 20);
}
Fixing a Spring
It’s possible to “fix” one or more particles to a rigid location. Notice the statement particle1.bFixed = true;
in the function createParticles
. It is as if one particle were attached to something too large to move.
var myParticles = [];
var mySprings = [];
// The index in the particle array, of the one the user has clicked.
var whichParticleIsGrabbed = -1;
// ========== PARTICLE METHODS =============
// Update the position based on force and velocity
function particleUpdate() {
if (this.bFixed == false) {
this.vx *= this.damping;
this.vy *= this.damping;
this.limitVelocities();
this.handleBoundaries();
this.px += this.vx;
this.py += this.vy;
}
}
// Prevent particle velocity from exceeding maxSpeed
function particleLimitVelocities() {
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;
}
}
}
// do boundary processing if enabled
function particleHandleBoundaries() {
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);
}
if (this.px <= 0) {
this.vx = abs(this.vx);
}
if (this.py >= height) {
this.vy = -abs(this.vy);
}
if (this.py <= 0) {
this.vy = abs(this.vy);
}
}
}
// draw the particle as a white circle
function particleDraw() {
fill(255);
ellipse(this.px, this.py, 9, 9);
}
// add a force to the particle using F = mA
function particleAddForce(fx, fy) {
var ax = fx / this.mass;
var ay = fy / this.mass;
this.vx += ax;
this.vy += ay;
}
// make a new particle
function makeParticle(x, y, dx, dy) {
var p = {px: x, py: y, vx: dx, vy: dy,
mass: 1.0, damping: 0.96,
bFixed: false,
bLimitVelocities: false,
bPeriodicBoundaries: false,
bHardBoundaries: false,
addForce: particleAddForce,
update: particleUpdate,
limitVelocities: particleLimitVelocities,
handleBoundaries: particleHandleBoundaries,
draw: particleDraw
}
return p;
}
// ============ SPRING METHODS =============
// update particles with spring force
function springUpdate() {
var dx = this.p.px - this.q.px;
var dy = this.p.py - this.q.py;
var dh = dist(this.p.px, this.p.py, this.q.px, this.q.py);
if (dh > 1) {
var distention = dh - this.restLength;
// computer F = -kx:
var restorativeForce = this.springConstant * distention;
var fx = (dx / dh) * restorativeForce;
var fy = (dy / dh) * restorativeForce;
this.p.addForce(-fx, -fy);
this.q.addForce( fx, fy);
}
}
// draw spring as a white line
function springDraw() {
stroke(255);
line(this.p.px, this.p.py, this.q.px, this.q.py);
}
// make a new spring object
function makeSpring(p1, p2, k) {
var s = {p: p1, q: p2,
restLength: dist(p1.px, p1.py, p2.px, p2.py),
springConstant: k,
update: springUpdate,
draw: springDraw
}
return s;
}
// ========== THE MAIN PROGRAM ============
// two particles connected by a spring
function setup() {
createCanvas(600, 400);
createParticles();
createSpringMeshConnectingParticles();
frameRate(10);
}
function createParticles(){
var particle0 = makeParticle(250, 200, 0, 0);
var particle1 = makeParticle(350, 200, 0, 0);
particle1.bFixed = true;
myParticles.push(particle0);
myParticles.push(particle1);
}
function createSpringMeshConnectingParticles() {
// The spring constant.
var K = 0.1;
// Stitch the particles together by a spring.
var p = myParticles[0];
var q = myParticles[1];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
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 dh = dist(mouseX, mouseY,
myParticles[i].px, myParticles[i].py)
if (dh < maxDist) {
maxDist = dh;
whichParticleIsGrabbed = i;
}
}
}
function draw() {
background(0);
for (var i = 0; i < myParticles.length; i++) {
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;
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].update(); // draw all springs
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].draw(); // draw all springs
}
for (var i = 0; i < myParticles.length; i++) {
myParticles[i].draw(); // draw all particles
}
fill(255);
noStroke();
text("Grab a point", 5, 20);
}
Springs 2 (Rope, with and without Gravity)
We can connect multiple particles into a rope. A for() loop has been used to connect each particle to the next one. Here we see the basic code structure we will be using: an array of particles, and an array of springs which (selectively) connects certain ones among them:
var myParticles = [];
var mySprings = [];
var nPoints = 15;
// The index in the particle array, of the one the user has clicked.
var whichParticleIsGrabbed = -1;
// ========== PARTICLE METHODS =============
// Update the position based on force and velocity
function particleUpdate() {
if (this.bFixed == false) {
this.vx *= this.damping;
this.vy *= this.damping;
this.limitVelocities();
this.handleBoundaries();
this.px += this.vx;
this.py += this.vy;
}
}
// Prevent particle velocity from exceeding maxSpeed
function particleLimitVelocities() {
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;
}
}
}
// do boundary processing if enabled
function particleHandleBoundaries() {
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);
}
if (this.px <= 0) {
this.vx = abs(this.vx);
}
if (this.py >= height) {
this.vy = -abs(this.vy);
}
if (this.py <= 0) {
this.vy = abs(this.vy);
}
}
}
// draw the particle as a white circle
function particleDraw() {
fill(255);
ellipse(this.px, this.py, 9, 9);
}
// add a force to the particle using F = mA
function particleAddForce(fx, fy) {
var ax = fx / this.mass;
var ay = fy / this.mass;
this.vx += ax;
this.vy += ay;
}
// make a new particle
function makeParticle(x, y, dx, dy) {
var p = {px: x, py: y, vx: dx, vy: dy,
mass: 1.0, damping: 0.96,
bFixed: false,
bLimitVelocities: false,
bPeriodicBoundaries: false,
bHardBoundaries: false,
addForce: particleAddForce,
update: particleUpdate,
limitVelocities: particleLimitVelocities,
handleBoundaries: particleHandleBoundaries,
draw: particleDraw
}
return p;
}
// ============ SPRING METHODS =============
// update particles with spring force
function springUpdate() {
var dx = this.p.px - this.q.px;
var dy = this.p.py - this.q.py;
var dh = dist(this.p.px, this.p.py, this.q.px, this.q.py);
if (dh > 1) {
var distention = dh - this.restLength;
// computer F = -kx:
var restorativeForce = this.springConstant * distention;
var fx = (dx / dh) * restorativeForce;
var fy = (dy / dh) * restorativeForce;
this.p.addForce(-fx, -fy);
this.q.addForce( fx, fy);
}
}
// draw spring as a white line
function springDraw() {
stroke(255);
line(this.p.px, this.p.py, this.q.px, this.q.py);
}
// make a new spring object
function makeSpring(p1, p2, k) {
var s = {p: p1, q: p2,
restLength: dist(p1.px, p1.py, p2.px, p2.py),
springConstant: k,
update: springUpdate,
draw: springDraw
}
return s;
}
// ========== THE MAIN PROGRAM ============
// two particles connected by a spring
function setup() {
createCanvas(600, 400);
createParticles();
createSpringMeshConnectingParticles();
frameRate(10);
}
function createParticles() {
for (var i = 0; i < nPoints; i++) {
var rx = map(i, 0, nPoints, 100, 500);
var ry = 100;
var particle = makeParticle(rx, ry, 0, 0);
particle.bHardBoundaries = true;
myParticles.push(particle);
}
}
function createSpringMeshConnectingParticles() {
// Stitch the particles together into a mesh by
// connecting neighbors with a spring.
// The spring constant.
var K = 0.1;
for (var i = 0; i < nPoints - 1; i++) {
var p = myParticles[i];
var q = myParticles[i + 1];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
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 dh = dist(mouseX, mouseY,
myParticles[i].px, myParticles[i].py)
if (dh < maxDist) {
maxDist = dh;
whichParticleIsGrabbed = i;
}
}
}
function draw() {
background(0);
for (var i = 0; i < myParticles.length; i++) {
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;
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].update(); // draw all springs
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].draw(); // draw all springs
}
for (var i = 0; i < myParticles.length; i++) {
myParticles[i].draw(); // draw all particles
}
fill(255);
noStroke();
text("Grab a point", 5, 20);
}
Here is the same example, but with a gravity force added in. Architects will readily recognize that the simulation is able to achieve the well-known catenary curve.
var myParticles = [];
var mySprings = [];
var nPoints = 15;
// The index in the particle array, of the one the user has clicked.
var whichParticleIsGrabbed = -1;
// ========== PARTICLE METHODS =============
// Update the position based on force and velocity
function particleUpdate() {
if (this.bFixed == false) {
this.vx *= this.damping;
this.vy *= this.damping;
this.limitVelocities();
this.handleBoundaries();
this.px += this.vx;
this.py += this.vy;
}
}
// Prevent particle velocity from exceeding maxSpeed
function particleLimitVelocities() {
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;
}
}
}
// do boundary processing if enabled
function particleHandleBoundaries() {
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);
}
if (this.px <= 0) {
this.vx = abs(this.vx);
}
if (this.py >= height) {
this.vy = -abs(this.vy);
}
if (this.py <= 0) {
this.vy = abs(this.vy);
}
}
}
// draw the particle as a white circle
function particleDraw() {
fill(255);
ellipse(this.px, this.py, 9, 9);
}
// add a force to the particle using F = mA
function particleAddForce(fx, fy) {
var ax = fx / this.mass;
var ay = fy / this.mass;
this.vx += ax;
this.vy += ay;
}
// make a new particle
function makeParticle(x, y, dx, dy) {
var p = {px: x, py: y, vx: dx, vy: dy,
mass: 1.0, damping: 0.96,
bFixed: false,
bLimitVelocities: false,
bPeriodicBoundaries: false,
bHardBoundaries: false,
addForce: particleAddForce,
update: particleUpdate,
limitVelocities: particleLimitVelocities,
handleBoundaries: particleHandleBoundaries,
draw: particleDraw
}
return p;
}
// ============ SPRING METHODS =============
// update particles with spring force
function springUpdate() {
var dx = this.p.px - this.q.px;
var dy = this.p.py - this.q.py;
var dh = dist(this.p.px, this.p.py, this.q.px, this.q.py);
if (dh > 1) {
var distention = dh - this.restLength;
// computer F = -kx:
var restorativeForce = this.springConstant * distention;
var fx = (dx / dh) * restorativeForce;
var fy = (dy / dh) * restorativeForce;
this.p.addForce(-fx, -fy);
this.q.addForce( fx, fy);
}
}
// draw spring as a white line
function springDraw() {
stroke(255);
line(this.p.px, this.p.py, this.q.px, this.q.py);
}
// make a new spring object
function makeSpring(p1, p2, k) {
var s = {p: p1, q: p2,
restLength: dist(p1.px, p1.py, p2.px, p2.py),
springConstant: k,
update: springUpdate,
draw: springDraw
}
return s;
}
// ========== THE MAIN PROGRAM ============
// two particles connected by a spring
function setup() {
createCanvas(600, 400);
createParticles();
createSpringMeshConnectingParticles();
frameRate(10);
}
function createParticles() {
for (var i = 0; i < nPoints; i++) {
var rx = map(i, 0, nPoints, 100, 500);
var ry = 100;
var particle = makeParticle(rx, ry, 0, 0);
particle.bHardBoundaries = true;
myParticles.push(particle);
}
myParticles[0].bFixed = true;
}
function createSpringMeshConnectingParticles() {
// Stitch the particles together into a mesh by
// connecting neighbors with a spring.
// The spring constant.
var K = 0.1;
for (var i = 0; i < nPoints - 1; i++) {
var p = myParticles[i];
var q = myParticles[i + 1];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
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 dh = dist(mouseX, mouseY,
myParticles[i].px, myParticles[i].py)
if (dh < maxDist) {
maxDist = dh;
whichParticleIsGrabbed = i;
}
}
}
function draw() {
background(0);
for (var i = 0; i < myParticles.length; i++) {
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;
}
var gravityForceX = 0;
var gravityForceY = 0.1;
for (var i = 0; i < myParticles.length; i++) {
myParticles[i].addForce(gravityForceX, gravityForceY);
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].update(); // draw all springs
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].draw(); // draw all springs
}
for (var i = 0; i < myParticles.length; i++) {
myParticles[i].draw(); // draw all particles
}
fill(255);
noStroke();
text("Grab a point", 5, 20);
}
Springs 3 (Blob)
In this example, particles are placed around the perimeter of a circle. Then springs are used to connect each one to its neighbor. All of particles are also connected by a spring to a single central particle, like spokes of a wheel. Notice that this leads to some tricky loop start and endpoints: the first element of myParticles
is the hub. This example has a different damping factor and spring constant than the previous one. You can experiment with these to get “bouncier” and “stiffer” animations. I have taken the liberty of decorating this set of particles with a closed Bezier curve that stitches through them all:
var myParticles = [];
var mySprings = [];
// The index in the particle array, of the one the user has clicked.
var whichParticleIsGrabbed = -1;
// ========== PARTICLE METHODS =============
// Update the position based on force and velocity
function particleUpdate() {
if (this.bFixed == false) {
this.vx *= this.damping;
this.vy *= this.damping;
this.limitVelocities();
this.handleBoundaries();
this.px += this.vx;
this.py += this.vy;
}
}
// Prevent particle velocity from exceeding maxSpeed
function particleLimitVelocities() {
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;
}
}
}
// do boundary processing if enabled
function particleHandleBoundaries() {
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);
}
if (this.px <= 0) {
this.vx = abs(this.vx);
}
if (this.py >= height) {
this.vy = -abs(this.vy);
}
if (this.py <= 0) {
this.vy = abs(this.vy);
}
}
}
// draw the particle as a white circle
function particleDraw() {
fill(255);
ellipse(this.px, this.py, 9, 9);
}
// add a force to the particle using F = mA
function particleAddForce(fx, fy) {
var ax = fx / this.mass;
var ay = fy / this.mass;
this.vx += ax;
this.vy += ay;
}
// make a new particle
function makeParticle(x, y, dx, dy) {
var p = {px: x, py: y, vx: dx, vy: dy,
mass: 1.0, damping: 0.8,
bFixed: false,
bLimitVelocities: false,
bPeriodicBoundaries: false,
bHardBoundaries: false,
addForce: particleAddForce,
update: particleUpdate,
limitVelocities: particleLimitVelocities,
handleBoundaries: particleHandleBoundaries,
draw: particleDraw
}
return p;
}
// ============ SPRING METHODS =============
// update particles with spring force
function springUpdate() {
var dx = this.p.px - this.q.px;
var dy = this.p.py - this.q.py;
var dh = dist(this.p.px, this.p.py, this.q.px, this.q.py);
if (dh > 1) {
var distention = dh - this.restLength;
// computer F = -kx:
var restorativeForce = this.springConstant * distention;
var fx = (dx / dh) * restorativeForce;
var fy = (dy / dh) * restorativeForce;
this.p.addForce(-fx, -fy);
this.q.addForce( fx, fy);
}
}
// draw spring as a white line
function springDraw() {
stroke(255);
line(this.p.px, this.p.py, this.q.px, this.q.py);
}
// make a new spring object
function makeSpring(p1, p2, k) {
var s = {p: p1, q: p2,
restLength: dist(p1.px, p1.py, p2.px, p2.py),
springConstant: k,
update: springUpdate,
draw: springDraw
}
return s;
}
// ========== THE MAIN PROGRAM ============
// two particles connected by a spring
function setup() {
createCanvas(600, 400);
createParticles();
createSpringMeshConnectingParticles();
frameRate(10);
}
function createParticles() {
var radius = 100;
var hub = makeParticle(width / 2, height / 2, 0, 0);
myParticles.push(hub);
for (var i = 0; i < 9; i++) {
var angle = map(i, 0, 9, 0, TWO_PI);
var rx = width / 2 + radius * cos(angle);
var ry = height / 2 + radius * sin(angle);
var particle = makeParticle(rx, ry, 0, 0);
particle.bHardBoundaries = true;
myParticles.push(particle);
}
}
function createSpringMeshConnectingParticles() {
// Stitch the particles together into a mesh by
// connecting neighbors with springs.
// The spring constant.
var K = 0.2;
// start with i = 1 to skip hub
for (var i = 1; i < myParticles.length; i++) {
var p = myParticles[i];
var q = myParticles[i + 1];
if (i == (myParticles.length - 1)) {
q = myParticles[1]; // complete the circle
}
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
// connect all the particles to the hub
var p = myParticles[0];
for (var i = 1; i < myParticles.length; i++) {
var q = myParticles[i];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
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 dh = dist(mouseX, mouseY,
myParticles[i].px, myParticles[i].py)
if (dh < maxDist) {
maxDist = dh;
whichParticleIsGrabbed = i;
}
}
}
function draw() {
background(0);
for (var i = 0; i < myParticles.length; i++) {
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;
}
var gravityForceX = 0;
var gravityForceY = 0.1;
for (var i = 0; i < myParticles.length; i++) {
myParticles[i].addForce(gravityForceX, gravityForceY);
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].update(); // draw all springs
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].draw(); // draw all springs
}
for (var i = 0; i < myParticles.length; i++) {
myParticles[i].draw(); // draw all particles
}
// draw a blob
// Some of the complexity here comes from rendering it with bezier.
fill(255, 200, 200, 225);
noStroke();
beginShape();
curveVertex(myParticles[9].px, myParticles[9].py);
for (var i = 1; i < myParticles.length; i++) {
curveVertex(myParticles[i].px, myParticles[i].py);
}
curveVertex(myParticles[1].px, myParticles[1].py);
endShape(CLOSE);
fill(255);
noStroke();
text("Grab a point", 5, 20);
}
In this next example, particles are placed around the perimeter of a circle. Then springs are used to connect (some of) them. Specifically, particles are connected to their neighbors, and some of their other nearby neighbors as well. The result is a kind of bouncy bag:
var myParticles = [];
var mySprings = [];
var nPoints;
// The index in the particle array, of the one the user has clicked.
var whichParticleIsGrabbed = -1;
// ========== PARTICLE METHODS =============
// Update the position based on force and velocity
function particleUpdate() {
if (this.bFixed == false) {
this.vx *= this.damping;
this.vy *= this.damping;
this.limitVelocities();
this.handleBoundaries();
this.px += this.vx;
this.py += this.vy;
}
}
// Prevent particle velocity from exceeding maxSpeed
function particleLimitVelocities() {
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;
}
}
}
// do boundary processing if enabled
function particleHandleBoundaries() {
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);
}
if (this.px <= 0) {
this.vx = abs(this.vx);
}
if (this.py >= height) {
this.vy = -abs(this.vy);
}
if (this.py <= 0) {
this.vy = abs(this.vy);
}
}
}
// draw the particle as a white circle
function particleDraw() {
fill(255);
ellipse(this.px, this.py, 9, 9);
}
// add a force to the particle using F = mA
function particleAddForce(fx, fy) {
var ax = fx / this.mass;
var ay = fy / this.mass;
this.vx += ax;
this.vy += ay;
}
// make a new particle
function makeParticle(x, y, dx, dy) {
var p = {px: x, py: y, vx: dx, vy: dy,
mass: 1.0, damping: 0.96,
bFixed: false,
bLimitVelocities: false,
bPeriodicBoundaries: false,
bHardBoundaries: false,
addForce: particleAddForce,
update: particleUpdate,
limitVelocities: particleLimitVelocities,
handleBoundaries: particleHandleBoundaries,
draw: particleDraw
}
return p;
}
// ============ SPRING METHODS =============
// update particles with spring force
function springUpdate() {
var dx = this.p.px - this.q.px;
var dy = this.p.py - this.q.py;
var dh = dist(this.p.px, this.p.py, this.q.px, this.q.py);
if (dh > 1) {
var distention = dh - this.restLength;
// computer F = -kx:
var restorativeForce = this.springConstant * distention;
var fx = (dx / dh) * restorativeForce;
var fy = (dy / dh) * restorativeForce;
this.p.addForce(-fx, -fy);
this.q.addForce( fx, fy);
}
}
// draw spring as a white line
function springDraw() {
stroke(255);
line(this.p.px, this.p.py, this.q.px, this.q.py);
}
// make a new spring object
function makeSpring(p1, p2, k) {
var s = {p: p1, q: p2,
restLength: dist(p1.px, p1.py, p2.px, p2.py),
springConstant: k,
update: springUpdate,
draw: springDraw
}
return s;
}
// ========== THE MAIN PROGRAM ============
// two particles connected by a spring
function setup() {
createCanvas(600, 400);
createParticles();
createSpringMeshConnectingParticles();
frameRate(10);
}
function createParticles() {
nPoints = 47;
var radius = 100;
for (var i = 0; i < nPoints; i++) {
var angle = map(i, 0, nPoints, 0, TWO_PI);
var rx = width / 2 + radius * cos(angle);
var ry = height / 2 + radius * sin(angle);
var particle = makeParticle(rx, ry, 0, 0);
particle.bHardBoundaries = true;
myParticles.push(particle);
}
}
function createSpringMeshConnectingParticles() {
// Stitch the particles together into a mesh by
// connecting neighbors with springs.
// The spring constant.
var K = 0.1;
// stitch the particles together into a blob
for (var i = 0; i < (nPoints / 2); i++) {
var p = myParticles[i];
var q = myParticles[(i + 1) % nPoints];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
// add extra connections
var connections = [2, 3, 5, 7, 11];
for (var j = 1; j < connections.length; j++) {
for (var i = 0; i < nPoints; i++) {
var p = myParticles[i];
var q = myParticles[(i + connections[j]) % nPoints];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
}
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 dh = dist(mouseX, mouseY,
myParticles[i].px, myParticles[i].py)
if (dh < maxDist) {
maxDist = dh;
whichParticleIsGrabbed = i;
}
}
}
function draw() {
background(0);
for (var i = 0; i < myParticles.length; i++) {
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;
}
var gravityForceX = 0;
var gravityForceY = 0.1;
for (var i = 0; i < myParticles.length; i++) {
myParticles[i].addForce(gravityForceX, gravityForceY);
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].update(); // draw all springs
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].draw(); // draw all springs
}
for (var i = 0; i < myParticles.length; i++) {
myParticles[i].draw(); // draw all particles
}
fill(255);
noStroke();
text("Grab a point", 5, 20);
}
Springs 4 (Big Square Mesh)
Here is a classic mesh. Just as in real life, for proper rigidity, we have to connect elements diagonally, or else the structure will not have integrity and will collapse. The code for doing so is (slightly) tricky.
var myParticles = [];
var mySprings = [];
// The number of particles in the grid of springs, in x and y
var nX = 9;
var nY = 9;
// The index in the particle array, of the one the user has clicked.
var whichParticleIsGrabbed = -1;
// ========== PARTICLE METHODS =============
// Update the position based on force and velocity
function particleUpdate() {
if (this.bFixed == false) {
this.vx *= this.damping;
this.vy *= this.damping;
this.limitVelocities();
this.handleBoundaries();
this.px += this.vx;
this.py += this.vy;
}
}
// Prevent particle velocity from exceeding maxSpeed
function particleLimitVelocities() {
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;
}
}
}
// do boundary processing if enabled
function particleHandleBoundaries() {
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);
}
if (this.px <= 0) {
this.vx = abs(this.vx);
}
if (this.py >= height) {
this.vy = -abs(this.vy);
}
if (this.py <= 0) {
this.vy = abs(this.vy);
}
}
}
// draw the particle as a white circle
function particleDraw() {
fill(255);
ellipse(this.px, this.py, 9, 9);
}
// add a force to the particle using F = mA
function particleAddForce(fx, fy) {
var ax = fx / this.mass;
var ay = fy / this.mass;
this.vx += ax;
this.vy += ay;
}
// make a new particle
function makeParticle(x, y, dx, dy) {
var p = {px: x, py: y, vx: dx, vy: dy,
mass: 1.0, damping: 0.96,
bFixed: false,
bLimitVelocities: false,
bPeriodicBoundaries: false,
bHardBoundaries: false,
addForce: particleAddForce,
update: particleUpdate,
limitVelocities: particleLimitVelocities,
handleBoundaries: particleHandleBoundaries,
draw: particleDraw
}
return p;
}
// ============ SPRING METHODS =============
// update particles with spring force
function springUpdate() {
var dx = this.p.px - this.q.px;
var dy = this.p.py - this.q.py;
var dh = dist(this.p.px, this.p.py, this.q.px, this.q.py);
if (dh > 1) {
var distention = dh - this.restLength;
// computer F = -kx:
var restorativeForce = this.springConstant * distention;
var fx = (dx / dh) * restorativeForce;
var fy = (dy / dh) * restorativeForce;
this.p.addForce(-fx, -fy);
this.q.addForce( fx, fy);
}
}
// draw spring as a white line
function springDraw() {
stroke(255);
line(this.p.px, this.p.py, this.q.px, this.q.py);
}
// make a new spring object
function makeSpring(p1, p2, k) {
var s = {p: p1, q: p2,
restLength: dist(p1.px, p1.py, p2.px, p2.py),
springConstant: k,
update: springUpdate,
draw: springDraw
}
return s;
}
// ========== THE MAIN PROGRAM ============
// two particles connected by a spring
function setup() {
createCanvas(600, 400);
createParticles();
createSpringMeshConnectingParticles();
frameRate(10);
}
function createParticles() {
// Create a bunch of (nX*nY) particles,
// which happen to be positioned on a grid
for (var y = 0; y < nY; y++) {
for (var x = 0; x < nX; x++) {
var rx = 100 + x * 50;
var ry = 100 + y * 50;
var p = makeParticle(rx, ry, 0, 0);
p.bHardBoundaries = true;
myParticles.push(p);
}
}
}
function createSpringMeshConnectingParticles() {
// Stitch the particles together into a mesh,
// by connecting them to their neighbors with springs.
// The spring constant.
var K = 0.1;
// Make springs between each particle and the one to its right.
for (var y = 0; y < nY; y++) {
for (var x = 0; x < nX - 1; x++) {
var p = myParticles[y * nX + x];
var q = myParticles[y * nX + x + 1];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
// Make springs between each particle and the one below it.
for (var y = 0; y < nY - 1; y++) {
for (var x = 0; x < nX; x++) {
var p = myParticles[y * nX + x];
var q = myParticles[(y + 1) * nX + x];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
// Diagonal springs: connect each particle to the one below right.
for (var y = 0; y < nY - 1; y++) {
for (var x = 0; x < nX - 1; x++) {
var p = myParticles[y * nX + x];
var q = myParticles[(y + 1) * nX + x + 1];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
// Diagonal springs: connect each particle to the one below left.
for (var y = 0; y < nY - 1; y++) {
for (var x = 1; x < nX; x++) {
var p = myParticles[y * nX + x];
var q = myParticles[(y + 1) * nX + x - 1];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
}
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 dh = dist(mouseX, mouseY,
myParticles[i].px, myParticles[i].py)
if (dh < maxDist) {
maxDist = dh;
whichParticleIsGrabbed = i;
}
}
}
function draw() {
background(0);
for (var i = 0; i < myParticles.length; i++) {
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;
}
var gravityForceX = 0;
var gravityForceY = 0.1;
for (var i = 0; i < myParticles.length; i++) {
myParticles[i].addForce(gravityForceX, gravityForceY);
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].update(); // draw all springs
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].draw(); // draw all springs
}
for (var i = 0; i < myParticles.length; i++) {
myParticles[i].draw(); // draw all particles
}
fill(255);
noStroke();
text("Grab a point", 5, 20);
}
Springs 5 (Auto-Bouncy Mesh)
We can grab one of the particles and move it automatically. Here one particle is being moved by a combination of sinusoidal and Perlin-noise forces. The rest of the mesh accommodates. (Note that this example is not interactive.)
var myParticles = [];
var mySprings = [];
// The number of particles in the grid of springs, in x and y
var nX = 5;
var nY = 5;
var indexOfParticleToWiggle = 2;
// ========== PARTICLE METHODS =============
// Update the position based on force and velocity
function particleUpdate() {
if (this.bFixed == false) {
this.vx *= this.damping;
this.vy *= this.damping;
this.limitVelocities();
this.handleBoundaries();
this.px += this.vx;
this.py += this.vy;
}
}
// Prevent particle velocity from exceeding maxSpeed
function particleLimitVelocities() {
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;
}
}
}
// do boundary processing if enabled
function particleHandleBoundaries() {
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);
}
if (this.px <= 0) {
this.vx = abs(this.vx);
}
if (this.py >= height) {
this.vy = -abs(this.vy);
}
if (this.py <= 0) {
this.vy = abs(this.vy);
}
}
}
// draw the particle as a white circle
function particleDraw() {
fill(255);
ellipse(this.px, this.py, 9, 9);
}
// add a force to the particle using F = mA
function particleAddForce(fx, fy) {
var ax = fx / this.mass;
var ay = fy / this.mass;
this.vx += ax;
this.vy += ay;
}
// make a new particle
function makeParticle(x, y, dx, dy) {
var p = {px: x, py: y, vx: dx, vy: dy,
mass: 1.0, damping: 0.96,
bFixed: false,
bLimitVelocities: false,
bPeriodicBoundaries: false,
bHardBoundaries: false,
addForce: particleAddForce,
update: particleUpdate,
limitVelocities: particleLimitVelocities,
handleBoundaries: particleHandleBoundaries,
draw: particleDraw
}
return p;
}
// ============ SPRING METHODS =============
// update particles with spring force
function springUpdate() {
var dx = this.p.px - this.q.px;
var dy = this.p.py - this.q.py;
var dh = dist(this.p.px, this.p.py, this.q.px, this.q.py);
if (dh > 1) {
var distention = dh - this.restLength;
// computer F = -kx:
var restorativeForce = this.springConstant * distention;
var fx = (dx / dh) * restorativeForce;
var fy = (dy / dh) * restorativeForce;
this.p.addForce(-fx, -fy);
this.q.addForce( fx, fy);
}
}
// draw spring as a white line
function springDraw() {
stroke(255);
line(this.p.px, this.p.py, this.q.px, this.q.py);
}
// make a new spring object
function makeSpring(p1, p2, k) {
var s = {p: p1, q: p2,
restLength: dist(p1.px, p1.py, p2.px, p2.py),
springConstant: k,
update: springUpdate,
draw: springDraw
}
return s;
}
// ========== THE MAIN PROGRAM ============
// two particles connected by a spring
function setup() {
createCanvas(600, 400);
createParticles();
createSpringMeshConnectingParticles();
frameRate(10);
}
function createParticles() {
// Create a bunch of (nX*nY) particles,
// which happen to be positioned on a grid
for (var y = 0; y < nY; y++) {
for (var x = 0; x < nX; x++) {
var rx = 100 + x * 50;
var ry = 100 + y * 50;
var p = makeParticle(rx, ry, 0, 0);
p.bHardBoundaries = true;
// Set the bottom row of particles to be "fixed".
// They won't be affected by forces.
if (y == nY - 1) {
p.bFixed = true;
}
myParticles.push(p);
}
}
}
function createSpringMeshConnectingParticles() {
// Stitch the particles together into a mesh,
// by connecting them to their neighbors with springs.
// The spring constant.
var K = 0.1;
// Make springs between each particle and the one to its right.
for (var y = 0; y < nY; y++) {
for (var x = 0; x < nX - 1; x++) {
var p = myParticles[y * nX + x];
var q = myParticles[y * nX + x + 1];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
// Make springs between each particle and the one below it.
for (var y = 0; y < nY - 1; y++) {
for (var x = 0; x < nX; x++) {
var p = myParticles[y * nX + x];
var q = myParticles[(y + 1) * nX + x];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
// Diagonal springs: connect each particle to the one below right.
for (var y = 0; y < nY - 1; y++) {
for (var x = 0; x < nX - 1; x++) {
var p = myParticles[y * nX + x];
var q = myParticles[(y + 1) * nX + x + 1];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
// Diagonal springs: connect each particle to the one below left.
for (var y = 0; y < nY - 1; y++) {
for (var x = 1; x < nX; x++) {
var p = myParticles[y * nX + x];
var q = myParticles[(y + 1) * nX + x - 1];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
}
function draw() {
background(0);
for (var i = 0; i < myParticles.length; i++) {
if (i == indexOfParticleToWiggle) {
// Take one special particle, move it around
var tx = millis() / 1000.0;
var ty = millis() / 400.0;
var xWiggle = 80 * (noise(tx) - 0.5);
var yWiggle = 50 * sin(ty);
myParticles[i].px = 200 + xWiggle;
myParticles[i].py = 100 + yWiggle;
} else {
// Update all other particles.
myParticles[i].update(); // update all locations
}
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].update(); // draw all springs
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].draw(); // draw all springs
}
for (var i = 0; i < myParticles.length; i++) {
myParticles[i].draw(); // draw all particles
}
fill(255);
noStroke();
text("Grab a point", 5, 20);
}
Springs 6 (Narrow Truss)
Here is a narrow truss which imitates a bottom-feeding worm. Notice that the spring constant can be changed by moving the mouse left (spring constant = 0.001) to right (spring constant = 0.3):
var myParticles = [];
var mySprings = [];
// The number of particles in the grid of springs, in x and y
var nX = 13;
var nY = 3;
var xSpacing = 20;
var ySpacing = 15;
var whichParticleIsGrabbed = -1;
// ========== PARTICLE METHODS =============
// Update the position based on force and velocity
function particleUpdate() {
if (this.bFixed == false) {
this.vx *= this.damping;
this.vy *= this.damping;
this.limitVelocities();
this.handleBoundaries();
this.px += this.vx;
this.py += this.vy;
}
}
// Prevent particle velocity from exceeding maxSpeed
function particleLimitVelocities() {
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;
}
}
}
// do boundary processing if enabled
function particleHandleBoundaries() {
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);
}
if (this.px <= 0) {
this.vx = abs(this.vx);
}
if (this.py >= height) {
this.vy = -abs(this.vy);
}
if (this.py <= 0) {
this.vy = abs(this.vy);
}
}
}
// draw the particle as a white circle
function particleDraw() {
fill(255);
ellipse(this.px, this.py, 9, 9);
}
// add a force to the particle using F = mA
function particleAddForce(fx, fy) {
var ax = fx / this.mass;
var ay = fy / this.mass;
this.vx += ax;
this.vy += ay;
}
// make a new particle
function makeParticle(x, y, dx, dy) {
var p = {px: x, py: y, vx: dx, vy: dy,
mass: 1.0, damping: 0.9,
bFixed: false,
bLimitVelocities: false,
bPeriodicBoundaries: false,
bHardBoundaries: false,
addForce: particleAddForce,
update: particleUpdate,
limitVelocities: particleLimitVelocities,
handleBoundaries: particleHandleBoundaries,
draw: particleDraw
}
return p;
}
// ============ SPRING METHODS =============
// update particles with spring force
function springUpdate() {
var dx = this.p.px - this.q.px;
var dy = this.p.py - this.q.py;
var dh = dist(this.p.px, this.p.py, this.q.px, this.q.py);
if (dh > 1) {
var distention = dh - this.restLength;
// computer F = -kx:
var restorativeForce = this.springConstant * distention;
var fx = (dx / dh) * restorativeForce;
var fy = (dy / dh) * restorativeForce;
this.p.addForce(-fx, -fy);
this.q.addForce( fx, fy);
}
}
// draw spring as a white line
function springDraw() {
stroke(255);
line(this.p.px, this.p.py, this.q.px, this.q.py);
}
// make a new spring object
function makeSpring(p1, p2, k) {
var s = {p: p1, q: p2,
restLength: dist(p1.px, p1.py, p2.px, p2.py),
springConstant: k,
update: springUpdate,
draw: springDraw
}
return s;
}
// ========== THE MAIN PROGRAM ============
// two particles connected by a spring
function setup() {
createCanvas(600, 400);
createParticles();
createSpringMeshConnectingParticles();
frameRate(10);
}
function createParticles() {
// Create a bunch of (nX*nY) particles,
// which happen to be positioned on a grid
for (var y = 0; y < nY; y++) {
for (var x = 0; x < nX; x++) {
var rx = 100 + x * xSpacing;
var ry = 100 + y * ySpacing;
var p = makeParticle(rx, ry, 0, 0);
p.bHardBoundaries = true;
myParticles.push(p);
}
}
}
function createSpringMeshConnectingParticles() {
// Stitch the particles together into a mesh,
// by connecting them to their neighbors with springs.
// The spring constant.
var K = 0.35;
// Make springs between each particle and the one to its right.
for (var y = 0; y < nY; y++) {
for (var x = 0; x < nX - 1; x++) {
var p = myParticles[y * nX + x];
var q = myParticles[y * nX + x + 1];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
// Make springs between each particle and the one 2 to its right
for (var y = 0; y < nY; y++) {
for (var x = 0; x < nX - 2; x++) {
var p = myParticles[y * nX + x];
var q = myParticles[y * nX + x +2];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
// Make springs between each particle and the one 1 to its right
for (var y = 0; y < nY - 2; y++) {
for (var x = 0; x < nX - 1; x++) {
var p = myParticles[y * nX + x];
var q = myParticles[(y + 2) * nX + x + 1];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
// Makek springs between each particle and the one below it.
for (var y = 0; y < nY - 2; y++) {
for (var x = 1; x < nX; x++) {
var p = myParticles[y * nX + x];
var q = myParticles[(y + 2) * nX + x];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
// Make springs between each particle and the one below it.
for (var y = 0; y < nY - 1; y++) {
for (var x = 0; x < nX; x++) {
var p = myParticles[y * nX + x];
var q = myParticles[(y + 1) * nX + x];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
// Diagonal springs: connect each particle to the one below right.
for (var y = 0; y < nY - 1; y++) {
for (var x = 0; x < nX - 1; x++) {
var p = myParticles[y * nX + x];
var q = myParticles[(y + 1) * nX + x + 1];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
// Diagonal springs: connect each particle to the one below left.
for (var y = 0; y < nY - 1; y++) {
for (var x = 1; x < nX; x++) {
var p = myParticles[y * nX + x];
var q = myParticles[(y + 1) * nX + x - 1];
var aSpring = makeSpring(p, q, K);
mySprings.push(aSpring);
}
}
}
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 dh = dist(mouseX, mouseY,
myParticles[i].px, myParticles[i].py)
if (dh < maxDist) {
maxDist = dh;
whichParticleIsGrabbed = i;
}
}
}
function draw() {
background(0);
for (var i = 0; i < myParticles.length; i++) {
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;
}
var mx = map(noise(millis() / 3000.0), 0, 1, 0.1, 0.9);
var my = map(noise(millis() / 2773.0), 0, 1, 0.8, 0.9);
myParticles[nX].px = width * mx;
myParticles[nX].py = height * my;
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].update(); // update all springs
}
for (var i = 0; i < mySprings.length; i++) {
mySprings[i].draw(); // draw all springs
mySprings[i].springConstant = map(mouseX, 0, width, 0.001, 0.3);
}
for (var i = 0; i < myParticles.length; i++) {
myParticles[i].draw(); // draw all particles
}
fill(255);
noStroke();
text("Grab a point", 5, 20);
}