Wiki
Clone wikiCore / ClothSimulation
Back to Codea's Example Projects
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