COS426 Assignment 5 - Cloth Simulation (Instructions)



Overview of the Starter Code

Before you start, here's some basic information on the structure of the starter code.


Part 1: Particles and Springs

In computer graphics, many different cloth models have been developed for simulation purposes. Of these, one of the simplest models uses a system of particles ("point masses") and springs. Given a sheet of cloth with some desired dimensions and parameters, we can divide the cloth up into evenly spaced particles and then connect nearby particles with springs. The springs hold the particles together and the different types of springs affect how the cloth behaves.

Before writing any code, look through js/cloth.js and js/particle.js and get an understanding of the constructors for the Cloth, Particle, and Constraint objects.

initial cloth held by its corners

(1.5) Let's build a cloth!

For this part, we will be working with the Cloth constructor in js/cloth.js. The image above shows the wireframe of the cloth, but the underlying structure of particles and springs does not exist yet, which we will need if we want to be able to simulate the cloth's physics and then animate it.

The starter code in the constructor creates the list of Particle objects that make up the cloth and stores it into this.particles in row-major order.

Next, we need to create the springs that bind the cloth's particles togther. In this assignment, our springs are represented by Constraint objects. Each Constraint object requires 3 things: the two Particles that it constrains, and the desired rest distance between them.

The starter code handles the necessary structural constraints along the edges of the square cloth as an example (hooray, the edge cases are handled already!).

In addition to these structural constraints along the edges, you will need to create springs to apply structural, shearing, and bending constraints to the particles.

  1. Structural constraints exist between a particle and the particle to its right as well as the particle below it. A structural constraint's desired rest distance is simply the global variable restDistance (the distance between two adjacent particles in the same row or column in the uniform square grid of particles).
  2. Shearing constraints exist between a particle and the particle to its diagonal bottom left as well as the particle to its diagonal bottom right. A shearing constraint's desired rest distance is also restDistance, scaled by restDistanceS (see the constants at the top js/cloth.js). Make sure you understand why this is the case!
  3. Bending constraints exist between a particle and the particle two away to its right as well as the particle two below it. A bending constraint's desired rest distance is restDistance, scaled by restDistanceB. See above.

image borrowed from UC Irvine's CS114

Implementation advice

Big Picture Note: Unfortunately, you won't be able to tell if your code is correct just yet, but you can toggle the existance of the types of springs in the GUI and see if there are any syntax errors in the Console.

(1.5) Enforce the constraints

Recall that each Constraint object contains 2 Particle objects, which are stored in this.p1 and this.p2. In addition, it stores a desired rest distance in this.distance.

In this part, we will approximate the effects of the spring forces acting on their particles and avoid directly using Hooke's law. To do so, we will enforce a constraint on its two particles by computing the vector \(v_{12}\) from one particle to the other. Then, we compute the correction vector as:

\[ v_{corr} = \frac{||v_{12}|| - d}{||v_{12}||} * v_{12}\]

where \(d\) is the value stored in this.distance.

Step 1: Complete the Constraint.prototype.enforce function in js/cloth.js. Apply half of this correction to one particle, and half to the other particle, to bring them closer to each other along the line that connects them. Be careful to bring the particles closer together, rather than apart.

Step 2: Next, complete the Cloth.prototype.enforceConstraints function in js/cloth.js. In this function, loop over the cloth's constraints and enforce each one by calling the function you just wrote above.

Big Picture Note: Recall that this function is called every timestep in the simulate() loop. Unfortunately, at this point, we still can't observe anything interesting just yet.


Part 2: Simulation via Numerical Integration

Now that we have set up the cloth model as a system of particles and springs (constraints), we need to integrate the physical equations of motion in order to apply the forces on our cloth's particles to compute how they move from one timestep to the next.

(1.0) Let there be gravity!

To start off, let's make it so that each particle in the cloth experiences the effects of gravity.

Step 1: Add support for a particle to accumulate forces acting on it by filling in Particle.prototype.addForce in js/particle.js. This should be a one-liner.

Step 2: Next, complete the Cloth.prototype.applyGravity function in js/cloth.js so that it applies a force due to gravity to each of its particle.

Implementation advice

Big Picture Note: In each call to simulate(), cloth.applyForces() is called, which then calls applyGravity(). It also calls applyWind(), for which a simple implementation has been given. At this point, we are able to accumulate forces into each of our cloth's particles... But nothing is happening in the scene yet, because the particles don't know what to do with the forces acting on them!

(1.5) Verlet integration

Now, we will perform numerical integration to compute each particle's change in position. There are many ways of integrating our equations of motion, which include Euler's method (explicit and implicit), Verlet algorithm, Runge-Kutta method, and more. In this assignment, we will use Verlet integration, an explicit integrator that is fairly accurate and relatively easy to implement.

Verlet integration computes a point mass's new position at time \(t + dt\), \(x_{t+dt}\) as follows:

\[x_{t+dt} = x_t + (1 - D) * v_t * dt + a_t * dt^2\]

where \(x_t\) is the current position, \(v_t\) is the current velocity, \(a_t\) is the current total acceleration from all accumulated forces acting on the particle, \(D\) is a damping coefficient DAMPING, and \(dt\) is the global constant TIMESTEP.

In Verlet integration, we approximate \(v_t * dt = x_t - x_{t - dt}\), where \(x_{t - dt}\) is the particle's previous position from the last timestep.

\[x_{t+dt} = x_t + (1 - D) * (x_t - x_{t - dt}) + a_t * dt^2\]

Step 1: Implement the Particle.prototype.integrate function in js/particle.js so that it performs Verlet integration on the particle with the provided deltaT parameter. (i.e. deltaT is the \(dt\) in the equations above). See the comments for more details.

Step 2: Now, go back to js/cloth.js and fill in the code for the Cloth.prototype.update function, so that each of its particles performs Verlet integration to compute its new position for the current timestep.

Part 2.5: Experiment with your Simulation

At long last, we can now observe our cloth falling to the floor due to gravity! However, note that we have not yet implemented any collision detection, so our cloth will clip through the floor in some pinning configurations (such as OneEdge).

Try the following to sanity check your work so far:

gravity force (default wireframe, corners pinned)

gravity force (default cloth, corners pinned)

provided simple wind force (default wireframe, corners pinned)

provided simple wind force (default cloth, corners pinned)


Part 3: Collisions

In this part, we will add support for collisions with other objects for more interesting results.

(0.5) Handling floor collisions

First, let's handle collisions with the floor in our scene. The floor is a x-z plane with y-coordinate equal to GROUND_Y. For this part, assume that the floor is an infinite plane and that objects in our scene are guaranteed to start initially above this plane. As a result, it's possible that forces such as gravity could cause objects to collide with the plane from above.

Step 1: Fill in the Particle.prototype.handleFloorCollision function in js/particle.js.

If the particle's y-coordinate is less than GROUND_Y, simply force the position's y-coordinate back to GROUND_Y. At a macro scale with a relatively small timestep, this is a simple, yet effective method of simulating floor collisions.

Step 2: Now, go back to js/cloth.js and fill in the code for Cloth.prototype.handleCollisions function, so that it calls handleFloorCollision on each of its particles. You should be able to observe your cloth properly interacting with the floor in pinning configurations such as "OneEdge", or by simply increasing the size of your cloth.

Side Note: In Part 4, you may extend this to work for any general plane with particle collisions coming from either side of the plane, and also account for more realistic factors such as friction.

floor collision (default wireframe, corners pinned)

floor collision (default cloth, corners pinned)

(1.5) Handling sphere collisions

In our scene, there is a global variable called sphere maintained by the simulation program.

You can toggle its existance in the scene by changing the object's dropdown menu to Sphere.

In addition, you can have the ball move around in the scene in a repeated loop by checking moving sphere. However, the sphere is invisible, and you will not be able to notice its existance until you have written some code.

Step 1: Fill in the Particle.prototype.handleSphereCollision function in js/particle.js.

  1. First check if the particle is inside the sphere at all. If not, return and do nothing.
  2. If so, compute a position posNoFriction, which is the projection of the particle's current position to the nearest point on the sphere's surface.
  3. If the particle was outside of the sphere in the last timestep, then take corrective action while accounting for friction. To do so, compute a position posFriction, which is the particle's previous position, adjusted by the sphere's movement in the last timestep. If the sphere is not moving, then this adjustment should be zero. This adjustment simulates perfect friction (i.e. friction = 1). However, to generalize it to any friction parameter, let the particle's new position be the weighted sum of posFriction and posNoFriction, weighted by friction and 1 - friction, respectively.
  4. If the particle was instead inside of the sphere in the last timestep, project it back onto the surface of the sphere directly at posNoFriction. You do not have to account for friction in this case.

Step 2: Now, go back to js/cloth.js and edit the code for Cloth.prototype.handleCollisions function, so that it also calls handleSphereCollision on each of its particles. In the simulation, change the object to be "Sphere", and you should be able to observe your cloth properly interacting with it.

Implementation advice

sphere collision (default wireframe, corners pinned)

sphere collision (default cloth, corners pinned)

moving sphere collision - your results may vary depending on how the ball moves and when your cloth drops (default cloth, corners pinned)

(1.5) Handling box collisions

In addition to the sphere, there is also a global variable called box maintained by the simulation program.

You can toggle its existance in the scene by changing the object's dropdown menu to Box.

Unlike the sphere, this box does not move and is static in the scene. However, the box is also invisible, and you will not be able to notice its existance until you have written some code.

Step 1: Fill in the Particle.prototype.handleBoxCollision function in js/particle.js.

  1. Check if the particle is inside the box. If not, do nothing.
  2. If so, compute a position posNoFriction, which is the projection of the particle's current position to the nearest point on the box surface. You may assume that the box is axis-aligned, similar to the one you worked with in Assignment 3's raytracer.
  3. If the particle was outside of the box in the last timestep, then take corrective action while accounting for friction. Let the particle's new position be the weighted sum of posFriction and posNoFriction, weighted by friction and 1 - friction, respectively. The box does not move, so you do not have to worry about "following" the box movements.
  4. Otherwise, if the particle was in the box in the last timestep, have it cling onto the surface of the box directly at posNoFriction. You do not have to account for friction in this case.

Step 2: Now, go back to js/cloth.js and edit the code for Cloth.prototype.handleCollisions function, so that it also calls handleBoxCollision on each of its particles. In the simulation, change the object to be "Box", and you should be able to observe your cloth properly interacting with it.

Implementation advice

box collision (default wireframe, corners pinned)

box collision (default cloth, corners pinned)


Part 4: Extensions

Implement one or more extension(s) of your choice to the cloth simulation! You need to complete at least 1 point in order to meet the full 10 points for this assignment. However, you are welcome to tackle more if you have time and are interested, but with diminishing returns on each point past the first 1 (see the Scoring section of the main assignment page for more details).

Here are a few ideas and possible points for each: