Wiki

Clone wiki

Core / ClothSimulation

Back to Codea's Example Projects


ProjectCloth-Small.png Cloth Simulation by Jason Boettcher

Introduction


Cloth Simulation is one of the example projects supplied with Codea. It uses verlet integration to simulate cloth. The user can tilt his or her iPad to control the direction of the gravity and see the cloth move.

The project runs in display mode STANDARD and supports any landscape orientation.

Tabs in the project


All of the code is contained in the Main tab.

Main tab


The Main tab implements two callback functions (which are explained further later in this section):

setup() -- Called once
draw()  -- Called up to 60 times a second

The Main tab also implements the following functions:

integrate(dt)
satisfyconstraints()
applyglobalforce(fx, fy)
insertparticle(_x, _y)
insertconstraint(_p1, _p2)
partdistance(p1, p2)

Finally, the Main tab includes the following code outside of functions:

supportedOrientations(LANDSCAPE_ANY)

-- Global variables
part_list = {}  -- A table (will be an array) for the particles
const_list = {} -- A table (will be an array) for constraints acting between parts
time = 0        -- Keeps track of time (not used, in the example)
num_parts = 0   -- A counter, updated by insertparticle()
num_const = 0   -- A counter, updated by insertconstraint()

setup()

The setup() function prints an introductory message ("Tilt you device to make the cloth move"). It then inserts particles into the part_list array and constraints into the const_list array, using functions insertparticle() and insertconstraint().

The particles are inserted, row by row, from top to bottom, on an 11 x 11 grid (0 to 10 from left to right and 0 to 10 from top to bottom), with horizontal and vertical spacing of 40. The particle in the top-left corner is placed at 120, 600. A counter i keeps track of the index in part_list[], with the first particle located at part_list[0].

The constraints are inserted, row by row, from top to bottom (0 to 9 from left to right and 0 to 9 from top to bottom) as follows: between a particle and the one to its right, between a particle and the one below and between a particle and the one diagonally to its bottom-right.

Constraints are also inserted along the bottom row (row 10) (between a particle and the one to its right) and the rightmost edge (column 10) (between a particle and the one below).

A counter i keeps track of the index in const_list[], with the first constraint located at const_list[0].

insertparticle(_x, _y)

Each particle is represented by a table with the fields below. The insertparticle() function also updates num_parts to count the number of particles inserted:

function insertparticle(_x,_y)
    num_parts = num_parts + 1
    return {
        x   = _x, -- The position of the particle
        y   = _y,
        ox  = _x, -- The old position of the particle
        oy  = _y,
        fx  = 0,  -- The force on the particle
        fy  = 0,
        orx = _x, -- Used for some (rigid) particles that never move
        ory = _y
    }
end

insertconstraint(_p1, _p2)

Each constraint is represented by a table with the fields below. The insertconstraint() function also updates num_const to count the number of constraints inserted:

function insertconstraint(_p1, _p2)
    num_const = num_const + 1
    return {
        p1 = _p1,                   -- The index of the first particle
        p2 = _p2,                   -- The index of the second particle
        d  = partdistance(_p1, _p2) -- The distance between the two particles when the
                                    -- constraint was created
    }
end

partdistance(p1, p2)

The function partdistance() returns the distance between the two particles, given the position recorded in their x and y fields:

function partdistance(p1, p2)
    x = part_list[p2].x - part_list[p1].x -- Horizontal distance between particles
    y = part_list[p2].y - part_list[p1].y -- Vertical distance between particles
    return math.sqrt(x * x + y * y)       -- Square root of the hypotenuse
end

Although not defined as local variables, x and y in the function are used like local variables.

draw()

The draw() function is called by Codea up to 60 times a second, and it does two things: (1) update the position of the particles; and (2) draw lines in the viewer between particles for each constraint.

The update is implemented as follows:

function draw()
    ...
    dt = DeltaTime   -- The time in seconds since draw() was last called by Codea
    ...
    applyglobalforce(Gravity.x * 10, Gravity.y * 10) -- Gravity is updated by the iPad's
                                                     -- accelerometer
    integrate(dt)
    satisfyconstraints()
    ...
end

(The following code is not actually used in the update:

time = time + dt                 -- Keep track of time
fx = math.sin(time) * 4          -- A horizontal force that varies with time
fy = math.cos(time * 1.5) * 2    -- A vertical force that varies with time
-- applyglobalforce(fx, -6 + fy) -- Commented out; the force is not applied

The drawing of the lines is implemented as follows. The constraints identify the two particles by their index numbers in part_list[].

noSmooth()               -- Turn smoothing off (for speed)
background(10, 10, 20)   -- A dark background 
fill(255, 0, 0)          -- Colour is bright red (not used)
stroke(218, 27, 27, 255) -- Colour is redish
strokeWidth(1)           -- Width is 1 (thin)
...
for i=0, num_const - 1 do
    line(part_list[const_list[i].p1].x, part_list[const_list[i].p1].y,
        part_list[const_list[i].p2].x, part_list[const_list[i].p2].y)
end

applyglobalforce(fx, fx)

This function applies the same force (fx, fy) to each particle:

function applyglobalforce(fx, fy)
    for i=0, num_parts-1 do  -- Iterate through the particles
        part_list[i].fx = fx -- Set the x-dimension of the force
        part_list[i].fy = fy -- Set the y-dimension of the force
    end
end

integrate(dt)

For each particle, the x and y position is updated based on its velocity (vx and vy) and the force acting on it (fx and fy) for time dt. For example (for the x-dimension):

x = part_list[i].x                                          -- Preserve 'x'
vx = part_list[i].x - part_list[i].ox                       -- Calculate velocity (x - ox)
part_list[i].x = part_list[i].x + vx + part_list[i].fx * dt -- Update 'x'
part_list[i].ox = x                                         -- Old 'x' ('ox') is preserved 'x'
part_list[i].fx = 0                                         -- Reset force to 0

In the case of the first row of 11 particles, they are 'rigid' and do not move from their initial position (preserved in the orx and ory fields):

for x=0, 10 do
    part_list[x].x = part_list[x].orx
    part_list[x].y = part_list[x].ory
end

satisfyconstraints()

The first part of this function can be ignored, as it duplicates an action that has been performed by the integrate() function (the resetting of the 'rigid' particles to their original position).

For each constraint, the reciprocal of the current distance (1/distance) between the particles to which it relates is calculated (as l):

dx = part_list[const_list[i].p2].x - part_list[const_list[i].p1].x
dy = part_list[const_list[i].p2].y - part_list[const_list[i].p1].y
l = rsqrt(dx*dx + dy*dy) -- rsqrt() uses a 'fast' algorithm

The difference between the current distance between the particles and the distance when the constraint was created is calculated as a proportion of the current distance (as diff):

diff = (1/l - const_list[i].d) * l -- Recall that l is 1/distance

The two particles (indexed by .p1 and .p2) are then each moved towards each other by that proportion:

part_list[const_list[i].p1].x = part_list[const_list[i].p1].x + (dx * 0.5) * diff
part_list[const_list[i].p1].y = part_list[const_list[i].p1].y + (dy * 0.5) * diff
part_list[const_list[i].p2].x = part_list[const_list[i].p2].x - (dx * 0.5) * diff
part_list[const_list[i].p2].y = part_list[const_list[i].p2].y - (dy * 0.5) * diff

Finally, in the case of the first row of 11 'rigid' particles, they are moved back to their initial position (preserved in the orx and ory fields):

for x=0, 10 do
    part_list[x].x = part_list[x].orx
    part_list[x].y = part_list[x].ory
end

Although not defined as local variables, dx, dy, l and diff in the function are used like local variables.

Updated