In this project, I implemented real-time cloth simulation with the ability to detect cloth-primitive collision and cloth self-collision. Cloth modeling is a notoriously difficult problem that researchers today are still working on. This project uses a particle-based model of the cloth where a particle is represented as a PointMass
within the cloth. For extra credit, I created different shaders for the cloth as well as implemented wind as a time and spatially varying force.
In order to have a cloth simulation, we must figure out a way to represent the cloth. The purpose of this section was to find a way to represent the cloth as a series of PointMasses
and Springs
.
Each cloth has an input width
and height
as well as the number of PointMasses
needed to represent the cloth, num_width_points
x num_height_points
. of points. To create the points that represent the cloth, I looped through num_width_points
x num_height_points
, creating all the necessary points. If the cloth is horizontal (same y
value), I set the point's position's y - value to 1.0 so the points all have uniform height. If the cloth is vertical, I set the point's position's z - value to a random offset between 1/1000 and -1/1000. If the point is pinned (meaning the point should not be moved during simulation steps), I set the point's pinned
value to true
. All other points' pinned
values are set to false
. All points are added to the cloth's vector of PointMasses
.
With the collection of points, we need to connect them together. Connections between points are called springs. There exist 3 types of springs:
springs
vector so the cloth is able to reference it for updates when needed.
![]() |
![]() |
![]() |
The goal of this section is to simulate the cloth's motion over time. In our cloth sim, objects are affected by two kinds of forces: external forces which exert uniform force to all objects in the scene and spring correction forces which affect each object differently depending on the spring constraint type. To simulate movement, I update each PointMass
with the sum of all the forces. Next, I use Verlet Integration to calculate the PointMass's
change in position. Finally, I correct positions for PointMasses
with abnormally high deformation. The simulation cloth should now behave similarly to a cloth in real life.
Each PointMass
object has a force vector that represents the sum total of forces on the PointMass
at the current step of the simulation. I loop through all the Springs
and add the external_accelerations
(like gravity, wind, etc.). Then, I calculate the spring correction forces using Hooke's Law:
F_s = k_s * (||p_a - p_b|| - l)where k_s is the spring constant which represents a spring's "stiffness" and l represents the resting length of the spring.
Next, I looped through all the PointMasses
. If the point is not pinned (meaning the point is allowed to move), I calculate its new position, taking into account the forces.
For the final step of that time step's simulation, I loop through all the springs to correct positions of points that may have strayed too far, causing the springs between points to exceed 10% of their resting length. If the distance between the two points of a spring is more than 10% greater than the spring's resting length, I adjust the position points. If neither of the spring's points are pinned, I correct both point positions (by half the amount I would if one point was pinned). I adjust each point by the current distance between the points multiplied by the difference between our maximally allowed distance (1.10 x distance).
ks is the spring constant in Hooke's Law. It measures a spring's stiffness. This constant is uniformly applied to all springs in the cloth so low ks may indicate "stretchier" springs while high ks may indicate "tighter" springs.
![]() |
![]() |
![]() |
![]() |
PointMasses
of the springs are not able to move as far away as they could with a low ks value.
Density is used to calculate the mass of each point in the cloth. It measures the mass per unit area of the cloth. At low densities, external forces have a lesser effect overall (for F = ma, if we keep a constant, and decrease m, F will decrease). Therefore, the low density cloth looks lighter and has a slighter curvature to the top of the cloth. Conversely, high density indicates that each PointMass
takes up significant mass. This leads to the top of the cloth appearing to be very curved and the folds to be much more pronounced in shape as the external forces are pushing the PointMasses
further down with each step of the simulation.
![]() |
![]() |
![]() |
![]() |
Damping value is used to simulate energy loss. The higher the damping value, the faster the cloth stops swaying since the energy loss per timestep of the simulation is greater. For the following pictures, I initially set my damping value to 0.0% which caused the simulation to run (seemingly) forever as the energy was not being lost. With high damping values, the cloth stopped after one swing (on the way down from its horizontal position).
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
In order to accurately simulate the real world, we need to implement collisions with other objects. In this section, I implemented the plane's collision with spheres and planes.
In each step of the simulation, I loop through all my points and check their positions. If the position is within the surface of the sphere or on the sphere's surface, I update their position so that the PointMass
now lies on top of the sphere at its tangent point. Then, I compute the PointMass's
correction vector which is the difference between the tangent point and the PointMass's
position at its last time step. This vector is multiplied against the friction value (so that the PointMass
can lie slightly above the surface) and added to the PointMass's
position at its last timestep.
![]() |
![]() |
![]() |
For the plane collision, I used the ray-plane equation to calculate the tangent point of the PointMass
and the plane. After finding the tangent point, I compare the distance between the tangent point and the PointMass's
last position with the distance between the position and the PointMass's
last position. The goal is to find out if this timestep led to a point crossing past a plane. If the distance between the PointMass's
current position is greater than the last position and the tangent point (that is on the plane), the PointMass
must have intersected the plane. Given this is the case, I calculate the correction vector which moves the PointMass
to a small surface offset above the plane (in the direction of the plane's normal). I set the PointMass's
position to the sum of the PointMass's
last position and the correction vector.
![]() |
At this stage of the simulation, the cloth is able to detect collision with other objects. However, it will still clip into itself as it cannot detect its own points. The purpose of this section is to implement self-collision where the cloth will fold and the majority of PointMasses
will adjust such that clipping is minimal.
In order to implement this, I first divided the cloth's maximum space into bounding boxes, eaching containing some number of PointMasses
. All points within the same bounding box, are then hashed to the same bucket. By hashing every point in the cloth, I can build a spatial map that groups together the closest points within a section. The buckets are used to check points against their closest neighbors (other points within the same box) for clipping. If the difference between any pair of points within each bucket is less than 2 x thickness
(represents the cloth's thickness), then I adjust the initial point's position to be a total of 2 x thickness
distance away from that particular neighbor. The initial point's final position is the average of all the corrected positions divided by the number of simulation steps the cloth sim is running. Less correction is needed if more simulation steps are done.
An issue with this particular method is that it does not check for points < (2 x thickness
) away when PointMasses
are within different buckets. This can lead to clipping (seen in some screenshots below). However, over time, the clipping issue disappeared as the cloth flattened out. The number of buckets are also expected to decrease as the simulation runs.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Low Density @ 1g/cm^2: Note that the cloth is relatively smooth with any folds that occur being large in size. There are no small wrinkles and the simulation ran quickly.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
High Density @ 100g/cm^2: Note that the cloth has many wrinkles throughout the simulation and the folds vary in size (large and small). The simulation took longer to complete and clipping occurred.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Low ks @ 1 N/m: Note that the cloth is insanely wrinkled. The small spring constant value means that the springs are extremely elastic. This simulation took a while for the cloth to flatten out but no significant clipping occurred.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
High ks @ 100,000 N/m: Note that the cloth only contained large folds. The relative stiffness of the springs meant that each individual spring's PointMasses
could not undergo large changes in position. As such, the collisions were isolated to larger areas.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
I implemented a couple different shaders in addition to the current shader:
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Wind is a spatially and time-varying force. My intuition behind this section was to change the position of each point by some small random number (fractional) in each step of Verlet Integration during simulation. I generate 6 random numbers: 3 are used to determine the sign of the fraction and the other 3 represent the numerator of the fraction added to position.x, position.y, position.z
. I multiply this wind-vector by delta_t
since wind varies with time.
The current line is commented out in the code so feel free to adjust to test (Line 207 in cloth.cpp).
![]() |
![]() |