Final Artifact – Cloth Simulator
Due: Monday, December 8th @ 11:00pm
This assignment was originally created by Jack Lowry with the help of Jason Kim for Summer 2025. It was recently updated by Jason Kim with the help of Julia Lundblad and Bowei Chen for Autumn 2025. The bug model asset used in this project was created and designed by Julia Lundblad.
Recommended reading: Xavier Provot, "Deformation Constraints in a Mass–Spring Model to Describe Rigid Cloth Behavior" (1995) .
Table of Contents
- Overview
- Getting Started
- Background
- Requirements (5 TODOs)
- Provided Code
- Scenes & Expected Behavior
- Turn In
Overview
In this final artifact, you will implement a real-time cloth simulation in Unity using a mass–spring system with Verlet integration. You will model a 2D rectangular cloth as a grid of particles connected by springs, and simulate how it deforms under gravity, wind, and collisions with scene geometry.
By the end of the project, you will have:
- A cloth that hangs, swings, and settles realistically under gravity.
- Configurable pinning patterns (fixed corners) for multiple test scenes.
- An overextension constraint to keep springs from stretching too far.
- Interactive wind control in a sail-like scene.
- A freefall scene where the cloth falls and drapes over a collider (the CSE 457 bug model).
Getting Started
Clone the repository using the button above or via command line:
git clone https://gitlab.cs.washington.edu/kimjason/cloth-simulator.git
All of your implementation will take place in
Assets/Scripts/ClothObject.cs.
Every place you need to write code is marked with
// TODO comments.
The project is built to work on Unity 6 (v. 6000.0.42f1). Please make sure you are using this version (or later in the same major line) so that the project opens correctly.
Tip: Start with Scene_FourCornersFixed to test basic behavior,
then progress to the other scenes. Cloth simulation can be subtle to debug—start early!
Background
Mass–Spring System
Cloth is modeled as a grid of particles connected by springs. Each particle has mass, position, and velocity. Springs connect particles and exert forces based on how much they are stretched or compressed relative to their rest length.
We use three types of springs to capture different cloth behaviors:
- Structural springs: Connect immediate horizontal and vertical neighbors. These resist stretching.
- Shear springs: Connect diagonal neighbors. These resist shearing (the cloth turning into a parallelogram).
- Flexion springs: Connect particles two steps apart. These resist bending.
Verlet Integration
Instead of explicit Euler integration, we use Verlet integration, which is more stable for stiff spring systems. Verlet integration computes the new position using the current position, previous position, and acceleration—without explicitly storing velocity:
\[ \mathbf{x}_{n+1} = 2\mathbf{x}_n - \mathbf{x}_{n-1} + \mathbf{a}_n \Delta t^2 \]
Overextension Constraint
Real cloth can stretch, but only to a limited extent. If springs behave like perfectly elastic rubber bands, the cloth will stretch unrealistically and may become numerically unstable.
To address this, we implement an overextension constraint that pulls particles back together when a spring stretches beyond a critical threshold.
Requirements (5 TODOs)
Your implementation consists of 5 TODOs in ClothObject.cs:
TODO Summary
- GetForce() – Implement Hooke's Law for spring forces
- CalculateDynamicInverseConstraint() – Implement overextension constraint
- checkFixedPoint() – Determine which particles are pinned
- Spring connection arrays – Define structural, shear, and flexion offsets
- ComputeMovement() – Implement the three-phase physics loop
TODO 1: Spring Force (Hooke's Law)
In the SpringForce class, implement GetForce() using Hooke's Law:
\[ \mathbf{F} = k (d - d_\text{rest}) \hat{\mathbf{d}} \]
Where:
- \(k\) is the spring constant (
_k) - \(d\) is the current distance between the two particles
- \(d_\text{rest}\) is the rest length (
_restDist) - \(\hat{\mathbf{d}}\) is the unit direction vector from
_p1toward_p2
The force should pull _p1 toward _p2 when stretched, and push away when compressed.
TODO 2: Overextension Constraint
In CalculateDynamicInverseConstraint(), implement the constraint that prevents springs
from stretching too far beyond their rest length.
Your implementation should:
- Check if the spring is overextended (stretched beyond
_criticalDisplacementRatioof rest length) - If overextended, calculate how much to move
_p1to bring the spring back within limits:- If
_p2is fixed:_p1moves the full correction - If neither is fixed:
_p1moves half (the symmetric spring handles_p2)
- If
- Accumulate the correction into
_p1.inverseConstraintAccumulator
TODO 3: Fixed Point Detection
Implement checkFixedPoint(int x, int y) to determine if a particle should be pinned
(immovable). The function should handle three configurations based on boolean flags:
| Flag | Fixed Corners |
|---|---|
fourCornersFixed |
(0,0), (0, yParticles-1), (xParticles-1, 0), (xParticles-1, yParticles-1) |
threeCornersFixed |
(0,0), (0, yParticles-1), (xParticles-1, 0) |
twoCornersFixed |
(0,0), (0, yParticles-1) |
The cloth grid coordinate system:
(0,0) -------- (0,9) ← top edge (x=0)
| |
| |
| |
(9,0) -------- (9,9) ← bottom edge (x=xParticles-1)
TODO 4: Spring Connection Offsets
In generateParticles(), define the offset arrays that specify how particles connect
to their neighbors. Each offset is {i_offset, j_offset} from the current particle.
Define arrays for all three spring types:
- Structural: Connect to immediate horizontal and vertical neighbors (4 connections)
- Shear: Connect to diagonal neighbors (4 connections)
- Flexion: Connect to particles 2 steps away in each cardinal direction (4 connections)
Example: An offset of {-1, 0} connects to the particle one step "left" in the i direction.
TODO 5: Physics Loop
Implement ComputeMovement(), the main physics update that runs each frame. The simulation
happens in three phases, each requiring a nested loop over all particles:
Phase 1: Force Accumulation
For each non-fixed particle, sum all forces acting on it:
- Spring forces (via
p.GetForces()) - Global forces like gravity and drag (loop through
_globalForces) - Wind forces if enabled (via
CalculateParticleNormalandGetWindAtPoint)
Add the total to p.forceAccumulator.
Phase 2: Verlet Integration + Collisions + Constraints
For each non-fixed particle:
- Compute acceleration from accumulated forces (F = ma)
- Clear the force accumulator for next frame
- Apply Verlet integration:
newPos = 2*current - last + accel*dt²
Important: SavelastPositionBEFORE updatingPosition! - Update velocity for next frame's drag calculation
- Handle collisions with
HandleCollisions(p) - Calculate constraints with
p.CalculateDynamicInverseConstraint()
Phase 3: Apply Constraints + Update Mesh
For each non-fixed particle:
- Apply constraint corrections with
p.ApplyDynamicInverseConstraint() - Copy particle position to mesh vertices (both front and back faces)
After the particle loop, transform all vertices to local space using _transform.InverseTransformPoint().
Provided Code
The following functionality is already implemented for you:
HandleCollisions(Particle p)
Handles collision response between a particle and scene colliders. When a particle penetrates a collider, it is pushed out to the surface and its velocity is adjusted based on restitution (bounciness) and friction.
CalculateParticleNormal(int i, int j)
Computes an approximate surface normal at a particle position by averaging cross products of edges from adjacent triangles. Used for wind calculations—wind only affects surfaces facing into it.
GetWindAtPoint(Vector3 position, Vector3 normal)
Calculates the wind force at a particle based on position and surface normal. The wind model includes Perlin noise-based turbulence for natural-looking gusts, and a dot product term so wind only pushes on surfaces facing into it (like a sail).
HandleWindControls()
Processes keyboard input to control wind in real-time:
- WASD / Arrow keys: Change wind direction
- Q / E: Decrease / Increase wind strength
- Space: Toggle wind on/off
Spring Creation Loops
The loops that iterate through your connection offset arrays and create SpringForce
objects are provided. You only need to define the offset arrays themselves.
Scenes & Expected Behavior
The Unity project includes several scenes that test different aspects of your simulator:
Scene – FourCornersFixed
All four corners pinned. The cloth should sag naturally under gravity and settle into a stable hammock shape. Start here to test your basic implementation.
Scene – ThreeCornersFixed
Three corners pinned (bottom-right free). The cloth should hang asymmetrically with the free corner drooping down.
Scene – TwoCornersFixed
Only the top edge pinned. The cloth should hang like a curtain, swinging freely.
Scene – Sail
Wind interaction scene. Use the keyboard controls to change wind direction and strength. The cloth should billow and respond dynamically to wind changes. Note, you may need to greatly incr
Scene – Freefall
The cloth starts above the CSE 457 bug model and falls under gravity. With collisions enabled, it should drape over the bug model without tunneling through.
NOTE: All the parameters in the scene have been defined for you. You can experiment with these values by selecting the Cloth GameObject in the Hierarchy and modifying the values in the Inspector panel. It should be under "Cloth Object (Script)".
Turn In
Files to Submit
-
Unity Script:
ClothObject.cs -
Video Demo: A recording named
ClothSimulationDemoshowing all five scenes:- FourCornersFixed
- ThreeCornersFixed
- TwoCornersFixed
- Sail (demonstrate wind controls)
- Freefall (cloth draping over bug model)
Submission Format
- Create a folder named with your UW NetID (e.g.,
zoran) - Place your
ClothObject.csand video inside this folder - Zip the folder and upload to Canvas under Final Artifact – Cloth Simulator
Congratulations on making it to the end of CSE 457! We hope this project gave you a sense of how physics, numerical methods, and graphics come together to create compelling visual effects.