# #Fabric

The goal here is to create an interactive fabric simulation. The link to this demo is found here.

The source code is found on the Github repo.

# Setup

Before we get to writing any actual runnable code, let's create some helper functions that will make things much easier.

## Drawing

We're going to define two simple drawing fucntions. The first draws a circle, the second draws a line. These both use the current stroke/fill color set by the canvas drawing context. This is all pretty basic, I'm just putting it here in case you see these functions in later code and don't recognize them. For more information, see my canvas pages.

```
// draw a circle at (x,y) of radius r
function drawCircle(x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, 2*Math.PI);
ctx.fill();
};
// draw a line from start = {x:_,y:_} to end = {x:_,y:_}
function drawLine(start, end) {
ctx.beginPath();
ctx.moveTo(start.x, start.y);
ctx.lineTo(end.x, end.y);
ctx.stroke();
};
```

## Vectors

We're going to define four simple functions for vector math. For concision I used ES6 arrow notation, but I'll also explain them using traditional function notation.

The functions are: `dist`

for euclidean distance, `normVec`

for normalization, `scale`

for scalar multiplication, and `add`

for componentwise addition.

```
const dist = (point1,point2) => Math.pow(Math.pow(point1.x-point2.x,2)+Math.pow(point1.y-point2.y,2), 0.5);
const normVec = (vec) => {let mag = dist(vec, VEC_ZERO); return {x:vec.x/mag, y:vec.y/mag}};
const scale = (vec, scalar) => ({x: vec.x*scalar, y:vec.y*scalar});
const add = (vec1, vec2) => ({x:vec1.x+vec2.x, y:vec1.y+vec2.y});
```

**Distance**

The distance between two vectors $\vec{v_1} = (x_1, y_1)$ and $\vec{v_2} = (x_2, y_2)$ is defined using the Pythagorean Theorem:

$$d(\vec{v_1}-\vec{v_2}) = \sqrt{(x_1-x_2)^2+(y_1-y_2)^2}$$

The resulting function is a direct application of ES6 arrow notation.

```
const dist = (point1,point2) => Math.pow(Math.pow(point1.x-point2.x,2)+Math.pow(point1.y-point2.y,2), 0.5);
//equivalent syntax
function dist(point1, point2) {
return Math.pow(Math.pow(point1.x-point2.x,2)+Math.pow(point1.y-point2.y,2), 0.5);
}
```

**Normalization**

In order to normalize a vector, we divide it by its magnitude. For some vector `$\vec{v} = (x, y)$`

, where we have a magnitude `$|\vec{v}|=d(\vec{v}-(0,0))$`

we have:

`$$\text{norm}(\vec{v})=(x/|\vec{v}|,y/|\vec{v}|)$$`

Again, we use arrow notation. However, we use the `return`

syntax so that we do not calculate the magnitude twice.

```
//earlier on
const VEC_ZERO = {x:0, y:0};
...
const normVec = (vec) => {let mag = dist(vec, VEC_ZERO); return {x:vec.x/mag, y:vec.y/mag}};
//equivalent syntax
function normVec(vec) {
let mag = dist(vec, VEC_ZERO);
return {x:vec.x/mag, y:vec.y/mag};
}
//functional, but inefficient syntax
const normVec = (vec) => {x:vec.x/dist(vec, VEC_ZERO), y:vec.y/dist(vec, VEC_ZERO)};
```

**Scalar Multiplication**

We use the standard definition of scalar multiplication on a vector.

`$$c(x,y) = (cx, cy)$$`

The resulting function:

```
const scale = (vec, scalar) => ({x: vec.x*scalar, y:vec.y*scalar});
// equivalent syntax
function scale(vec, scalar) {
return {x: vec.x*scalar, y:vec.y*scalar};
}
```

**Addition**

We use the standard definition of addition on two vectors.

`$$(x_1, y_1) + (x_2, y_2) = (x_1+x_2, y_1+y_2)$$`

The resulting function:

```
const add = (vec1, vec2) => ({x:vec1.x+vec2.x, y:vec1.y+vec2.y});
// equivalent syntax
function add(vec1, vec2) {
return {x:vec1.x+vec2.x, y:vec1.y+vec2.y};
}
```

# Fundamental Physics

We need to start off by modeling our desired effect using basic physics. We're going to model the fabric as a matrix of point masses with springs between each mass. As a system, this collection of masses and springs should exhibit fabric-like behavior.

## Calculating Movement (Calculus)

As with most physics simulations, the key here is in understanding movement. Since our system is really just a system of particles, all we need to do is calculate the net force on each particle for each frame. We use this force to calculate acceleration, velocity, and position.

In code, I add this method to each point object:

```
getForce: function() {
return this.forces.reduce((acc, curr) => ({x:acc.x+curr.x, y:acc.y+curr.y}),{x:0, y:0});
}
```

`forces`

is an array which keeps track of all the forces on each point. In our case, the only forces are gravity and spring forces.

Here, we use ES6 arrow notation to return a vector obtained by component-wise adding up the values of each force by accumulating values over the starting vector `{x:0, y:0}`

.

To get realistic movement, we use a naive integration function on the time step. All this means is that we directly use the definitions of velocity and acceleration to calculate position. Velocity is defined as the instantaneous change in position (derivative of position with respect to time), which means we can calculate the position of the next frame by adding the velocity to the position. Similarly, acceleration is the instantaneous change in velocity (derivative of velocity with respect to time), we can just add the acceleration fo the velocity. (If you don't understand the derivative stuff, don't worry too much about it.)

In order to make the movement more natural, we also add fluid damping. This just means that we're going to account for fluid friction (like air resistance) by introducing a slowing factor proportional to the velocity. Think about trying to swim through water. The faster you move your hand, the more you feel the water push back.

```
for(let i=0; i<points.length; i++) {
// calculate forces
points[i].forces.push({x:0, y:0.2}); //apply gravity
let netForce = points[i].getForce(); // calculate resulting force
points[i].forces = []; // reset forces
points[i].ax = netForce.x;
points[i].ay = netForce.y;
// simple integration
points[i].vx+=points[i].ax;
points[i].vy+=points[i].ay;
points[i].x+=points[i].vx;
points[i].y+=points[i].vy;
...
// fluid damping
points[i].ax -= 0.4 * points[i].vx;
points[i].ay -= 0.4 * points[i].vy;
}
```

At the line `let netForce = points[i].getForce();`

we see the `getForce()`

function that we defined earlier. We use a `forces`

array to manage our forces: at the start of each frame, we clear the array. Then, (somewhere else) we populate it with each spring force. Then, we push a gravity vector. We use our `getForce`

to merge this list before we clear it.

## Springs

The force produced by each spring follows Hooke's law, where the force is proportional to the displacement. See this page for more information.

We're going to model our springs using Hooke's law. Let's take a look at our complete `Constraint`

prototype, where a `Constraint`

is a spring:

```
// create a constraint b/t two points
function Constraint(point1, point2) {
// create internal references (pointers) to points that are in the global
// this is so that we can access them later internally
this.start = point1;
this.end = point2;
this.length = dist(point1,point2); // resting length
this.k = 0.02; // spring constant
this.applyForce = function() {
let delx = dist(this.start,this.end)-this.length; // displacement
const mypoints = [this.start, this.end];
for(let i=0; i<mypoints.length; i++) { // do it for both points
const thispoint = mypoints[i];
const otherpoint = mypoints[(i+1)%2]; // this is a fun hack
// find the direction of the Hooke's law force
// normalize the difference vector
let normalized = normVec({
x:otherpoint.x-thispoint.x,
y:otherpoint.y-thispoint.y
});
// now scale the normalized vector by magnitude (k del x)
let force = scale(normalized, this.k*delx);
// finally, add the force to the forces array of each point
thispoint.forces.push(force);
}
}
}
```

The structure of this prototype is pretty straightforward. We only have four attributes. `start`

and `end`

are just pointers that allow us to internally access the global array of vertices. `k`

and `length`

are attributes of the spring representing its strength and resting length, respectively.

Let's look a bit more in-depth at the `applyForce`

function. First, we find the displacement in order to use it for the Hooke's law calculation.

```
this.applyForce = function() {
let delx = dist(this.start,this.end)-this.length; // calculate displacement
const mypoints = [this.start, this.end];
for(let i=0; i<mypoints.length; i++) {
const thispoint = mypoints[i];
const otherpoint = mypoints[(i+1)%2];
let normalized = normVec({
x:otherpoint.x-thispoint.x,
y:otherpoint.y-thispoint.y
});
let force = scale(normalized, this.k*delx);
thispoint.forces.push(force);
}
}
```

# Program Flow

With everything set up, we can finally go to creating the running executable code of the program. We can model this code in three component: initialization, animation loop, and action listeners. This is pretty standard architecture for any kind of interactive code.

## Initialization

In the initialization (sometimes called `init`

) phase, we run all the code that runs before the user is able to interact with the canvas. This includes basic canvas functions, such as setting up a drawing context, and populating the document with our objects (vertices and springs).

First, we set up our basic canvas stuff:

```
// basic canvas stuff
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext('2d');
// this is exclusively so we can use jQuery to handle the resize event later on
let canv = $("canvas");
canv.attr('width',$('body').width());
canv.attr('height',$('body').height());
```

We use the `canv`

object as a wrapper just so that we can use jQuery to handle dimension stuff. Technically, this is less efficient, as it requires loading the additional resource of jQuery, but it makes coding a lot easier so we'll just do it this way.

We have to run `canv.attr(...)`

for both height and width so that we can use those dimensions in the following calculation:

```
let dist_between = 50; // dist b/t each vertex
// calculate maximum # of vertices across and down
let max_across = Math.floor(canvas.width/dist_between)-4;
let max_down = Math.floor(canvas.height/dist_between)-5;
// margins b/t fabric and sides of document
let offset_x = (canvas.width-dist_between*(max_across-1))/2; //left margin
let offset_y = dist_between; // top margin
```

`dist_between`

is the number of pixels, vertically or horizontally, between each vertex. `max_across`

and `max_down`

are the number of rows and columns in our fabric. We calculate the largest number of vertices that can fit (`canvas.width/dist_between`

), floor it to the closest integer, and then subtract a few so that we have a margin.

At the end, we calculate our margins. We just use a `dist_between`

vertical margin. Our horizontal margin is the difference between the canvas width `canvas.width`

and the fabric width `dist_between*(max_across-1)`

(note that we subtract 1 because we create a vertex at both ends) divided by 2.

Now, we populate an array of vertices.

```
let points = []; // contains all the vertices
// add all the vertices to the points array
for(let i=0; i<max_across; i++) { // iterate across and down
for(let j=0; j<max_down; j++) {
points.push({
x:offset_x+i*dist_between, // position
y:offset_y+j*dist_between,
vx: 0, // velocity
vy: 0,
ax: 0, // acceleration
ay: 0,
static: j==0, // if it's the top row, it can't move
forces: [], // list of forces
getForce: function() { // accumulator function for the forces
return this.forces.reduce((acc, curr) => ({x:acc.x+curr.x, y:acc.y+curr.y}),{x:0, y:0});
},
clicking:false // is it being held down by the mouse?
});
}
}
```

We go across all `max_across`

columns and `max_down`

rows and populate the fabric vertex by vertex by filling a `points`

array. The position parameter is calculated by takin the between-vertex spacing and multiplying it by the column/row number, offset by the margin. The velocity and acceleration are both initialized at 0. `static`

is a boolean variable which signals if a point is on the top row or not - if so, it cannot move. (We'll deal with this in the draw function.) `forces`

and `getForce`

are used to handle movement, as mentioned in the Calculating Movement section. `clicking`

is used to handle click events, as we'll see later.

Finally, we'll populate the document with constraints, using the `Constraint`

prototype described earlier:

```
let constraints = []; // array of all springs
// make constraints across
for(var i=0; i<max_across; i++) {
for(var j=0; j<max_down-1; j++) {
constraints.push(new Constraint(points[i*max_down+j],points[i*max_down+j+1]));
}
}
//make constraints down
for(var i=0; i<max_down; i++) {
for(var j=0; j<max_across-1; j++) {
constraints.push(new Constraint(points[j*max_down+i],points[(j+1)*max_down+i]));
}
}
//make constraints diagonally
for(var i=0; i<max_across-1; i++) {
for(var j=0; j<max_down-1; j++) {
constraints.push(new Constraint(points[i*max_down+j],points[(i+1)*max_down+j+1]));
}
}
```

This is all fairly straightforward. First, we make all the horizontal springs, then all the vertical ones, then all the diagonal ones.

## Animation Loop

The animation loop is a function that runs every frame. Here's what it looks like in full:

```
function reDraw() { // animation step
//reset
canv.attr('width',$('body').width());
canv.attr('height',$('body').height());
ctx.fillStyle = "#222244";
ctx.fillRect(0, 0, canvas.width, canvas.height);
//update constraints
ctx.strokeStyle="#FFFFFF";
for(let i=0; i<constraints.length; i++) {
constraints[i].applyForce();
drawLine(constraints[i].start, constraints[i].end);
}
//redraw + update points
for(let i=0; i<points.length; i++) {
if(!points[i].static) {
points[i].x+=points[i].vx;
points[i].y+=points[i].vy;
if(points[i].clicking) {
points[i].x += click.vx;
points[i].y += click.vy;
points[i].vx = 0; // comment these 4 line out to make the points snap back
points[i].vy = 0;
points[i].ax = 0;
points[i].ay = 0;
} else {
points[i].vx+=points[i].ax;
points[i].vy+=points[i].ay;
points[i].forces.push({x:0, y:0.2}); //apply gravity
let netForce = points[i].getForce();
points[i].forces = [];
points[i].ax = netForce.x;
points[i].ay = netForce.y;
points[i].ax -= 0.4 * points[i].vx;
points[i].ay -= 0.4 * points[i].vy;
}
}
ctx.fillStyle="white";
drawCircle(points[i].x,points[i].y,5);
}
window.requestAnimationFrame(reDraw);
}
window.requestAnimationFrame(reDraw);
```

Let's go through this component by component. First of all, we use `window.requestAnimationFrame`

to set up the animation loop.

At the start of every frame, we reset the canvas.

```
// set dimensions to fill the screen - this is just to make things look nice when the window is moved around
canv.attr('width',$('body').width());
canv.attr('height',$('body').height());
// redraw the background
ctx.fillStyle = "#222244";
ctx.fillRect(0, 0, canvas.width, canvas.height);
```

Next, we iterate through all the constraints and handle the forces.

```
ctx.strokeStyle="#FFFFFF"; // set the canvas draw color
for(let i=0; i<constraints.length; i++) {
constraints[i].applyForce(); // apply the force to the vertices at either end
drawLine(constraints[i].start, constraints[i].end); // draw the constraint
}
```

Finally, we iterate through the particles:

```
for(let i=0; i<points.length; i++) {
if(!points[i].static) { // if it's static don't do anything
points[i].x+=points[i].vx; // integrate velocity
points[i].y+=points[i].vy;
if(points[i].clicking) { // we'll get to this later, just ignore it for now
points[i].x += click.vx;
points[i].y += click.vy;
points[i].vx = 0;
points[i].vy = 0;
points[i].ax = 0;
points[i].ay = 0;
} else {
points[i].vx+=points[i].ax; // integrate acceleration
points[i].vy+=points[i].ay;
points[i].forces.push({x:0, y:0.2}); //apply gravity
// merge all forces into a single value, and reset the forces array
let netForce = points[i].getForce();
points[i].forces = [];
// newton's law: f = ma (where m = 1)
points[i].ax = netForce.x;
points[i].ay = netForce.y;
// fluid damping
points[i].ax -= 0.4 * points[i].vx;
points[i].ay -= 0.4 * points[i].vy;
}
}
// draw the new position of each vertex
ctx.fillStyle="white";
drawCircle(points[i].x,points[i].y,5);
}
```

## Action Listeners

We want to make it possible to play with and move around the fabric. The objective is to make it feel natural and like real fabric. When you click down, you take a portion of the fabric with you, and when you release the click, you release the fabric at that point.

Here's the click handler:

```
// this click stores mouse information
let click = {x: 0, y:0, vx:0, vy:0};
// update position of mouse on mousemove
$(canvas).mousemove(function(e) {
// update the position of the mouse relative to the canvas
let newx = e.pageX - $(canvas).offset().left;
let newy = e.pageY - $(canvas).offset().top;
click.vx = newx-click.x;
click.vy = newy-click.y;
click.x = newx;
click.y = newy;
}).mousedown(function() { // grab all points in a 100 px radius on mousedown
for(let i=0; i<points.length; i++) {
if(dist(click, points[i]) < 100) {
points[i].clicking = true;
}
}
}).mouseup(function() { // release all points on mouseup
for(let i=0; i<points.length; i++) {
points[i].clicking = false;
}
});
```

First, we create an object `click = {x: 0, y:0, vx:0, vy:0}`

which can be accessed from anywhere in the code for mouse data.

In the `mousemove`

portion, the `let newx = ..., let newy = ...`

lines update the stored position of the mouse relative to the canvas. This is so that we can use an `<iframe>`

or something and our positioning won't be thrown off. Then, we calculate a mouse position `click.x`

,`click.y`

and a mouse velocity `click.vx`

, `click.vy`

.

On `mousedown`

, we set `point.clicking`

in each point within a 100 pixel radius to be `true`

, so that we can handle the behavior in the animation loop.

On `mouseup`

, we release all points by setting `point.clicking`

to false.

Finally, let's look at the click handling of the animation loop:

```
reDraw() {
...
if(points[i].clicking) { // we'll get to this later, just ignore it for now
points[i].x += click.vx;
points[i].y += click.vy;
points[i].vx = 0;
points[i].vy = 0;
points[i].ax = 0;
points[i].ay = 0;
}
...
}
```

If a point is being held (`point.clicking==true`

), we suspend normal movement calculations (`vx, vy, ax, ay = 0`

) and handle them separately using the click. To do this, we simply make the points move with the mouse by integrating the mouse velocity onto the point position.

# Final Thoughts

There are a still a lot of ways to improve this demo:

- The clicking works a little bit wonky because the damping isn't properly tuned, so if a patch of fabric is dragged far out it shakes violently.
- Gravity feels kind of unresponsive.
- Could add the ability to remove static vertices so it's just a floating piece of fabric
- Also could add the ability to have multiple fabrics
- Can we make the fabric interact with other objects like cubes?