You can grab and drag any particle with the mouse. F1 resets the cloth, F2 toggles a fan that blows from the side and F3 toggles colour.
I'm pretty sure I could have done this faster and easier with love.physics but I wanted to try my hand at implementing it from scratch.
Code: Select all
-- Disable output buffer so debug messages print in real time
io.stdout:setvbuf("no")
function love.load()
love.window.setTitle("Verlet Physics")
particles = {}
sticks = {}
screenWidth = love.graphics.getWidth()
screenHeight = love.graphics.getHeight()
gravity = 900
drag = 0.03
heldParticle = -1
fan = 0
colourMode = true
createGrid()
end
-- simple hsv to rgb converter
function hsv2rgb(h, s, v)
h = h / 60
local c = s * v
local x = c * (1 - math.abs(h % 2 - 1))
if h < 1 then
return {c, x, 0}
elseif h < 2 then
return {x, c, 0}
elseif h < 3 then
return {0, c, x}
elseif h < 4 then
return {0, x, c}
elseif h < 5 then
return {x, 0, c}
else
return {c, 0, x}
end
end
-- function to create the initial sheet of particles and springs
function createGrid()
-- clear any existing particles
particles = {}
sticks = {}
local fixed, col
local dx = 0
for x = 100, 700, 10 do
dx = 0
-- set colour just based on x
col = hsv2rgb(((x - 100) / 600) * 360, 0.8, 0.8)
for y = 50, 90 do
fixed = false
-- fix a few particles in the top row
if y == 50 and x % 50 == 0 then
fixed = true
end
addParticle(x + dx, y, fixed, col)
-- small dx so the particles fall slightly offest
dx = dx + 1
-- add spring above
if y > 50 then
addStick(particles[#particles], particles[#particles - 1], 10, true, col)
end
-- add spring to left
if x > 100 then
addStick(particles[#particles], particles[#particles - 41], 10, true, col)
end
end
end
end
-- keypressed (f1 resets the cloth, f2 toggles the "fan", f3 toggles colour)
function love.keypressed(key)
if key == "f1" then
createGrid()
end
if key == "f2" then
if fan ~= 0 then
fan = 0
else
fan = math.random(50, 200)
end
end
if key == "f3" then
colourMode = not colourMode
end
end
-- mousepressed, get a close particle and grab it
function love.mousepressed(x, y, button)
if button == 1 then
local dx, dy
for i, p in ipairs(particles) do
dx = math.abs(x - p.x)
dy = math.abs(y - p.y)
if dx < 6 and dy < 6 then
p.fixed = true
heldParticle = i
break
end
end
end
end
-- mouse released, drop a particle if we have one
function love.mousereleased(x, y, button)
if button == 1 and heldParticle ~= -1 then
particles[heldParticle].fixed = false
heldParticle = -1
end
end
-- mouse moved, set pos and old pos of held particle
function love.mousemoved(x, y)
if heldParticle ~= -1 then
particles[heldParticle].x = x
particles[heldParticle].y = y
particles[heldParticle].ox = x
particles[heldParticle].oy = y
end
end
function addStick(p1, p2, length, spring, col)
local s = {p1 = p1, p2 = p2, length = length, spring = spring, col = col}
table.insert(sticks, s)
return s
end
function addParticle(x, y, fixed, col)
local p = {x = x, y = y, ox = x, oy = y, fixed = fixed, col = col}
table.insert(particles, p)
return p
end
function updateParticle(p, dt)
-- don't move fixed points
if p.fixed then return end
-- apply force from fan and gravity
ax = fan
ay = gravity
-- calculate velocity based on last position and acceleration
local vx = (p.x - p.ox) * (1 - drag) + ax * dt * dt
local vy = (p.y - p.oy) * (1 - drag) + ay * dt * dt
-- move
p.ox, p.x = p.x, p.x + vx
p.oy, p.y = p.y, p.y + vy
end
function particleMovePos(p, x, y)
if p.fixed then return end
p.x = p.x + x
p.y = p.y + y
end
function checkBounds(p)
-- check bounds
if p.x > screenWidth then
p.x = screenWidth
elseif p.x < 0 then
p.x = 0
end
if p.y > screenHeight then
p.y = screenHeight
elseif p.y < 0 then
p.y = 0
end
end
function updateStick(s)
-- check distance between particles
local dx = s.p1.x - s.p2.x
local dy = s.p1.y - s.p2.y
local dist = math.sqrt(dx * dx + dy * dy)
-- work out difference and correction amount
local diff = (s.length - dist) / dist * 0.5
-- springs only do anything if dist > length
if s.spring and diff > 0 then return end
-- apply to particles
particleMovePos(s.p1, dx * diff, dy * diff)
particleMovePos(s.p2, -dx * diff, -dy * diff)
end
function drawParticle(p)
if colourMode then love.graphics.setColor(p.col) end
love.graphics.circle("fill", p.x, p.y, 3)
end
function drawStick (s)
if colourMode then love.graphics.setColor(s.col) end
love.graphics.line(s.p1.x, s.p1.y, s.p2.x, s.p2.y)
end
function love.update(dt)
-- if the fan is on, randomize it a bit
if fan ~=0 then
fan = fan + math.random(100) - 50
end
-- update particles
for _, p in ipairs(particles) do
updateParticle(p, dt)
end
-- update sticks (do multiple times to make smoother)
for i = 1, 4 do
for _, s in ipairs(sticks) do
updateStick(s)
end
end
-- check bounds
for _, p in ipairs(particles) do
checkBounds(p)
end
end
function love.draw()
love.graphics.setColor(1, 1, 1)
-- draw particles
for _, p in ipairs(particles) do
drawParticle(p)
end
-- draw sticks
for _, s in ipairs(sticks) do
drawStick(s)
end
end