# Fixed-area Bubble: an experiment with 2D soft bodies

A first experiment with 2D soft bodies:

``````
--
-- Fixed-Area Bubble in Lua for Codea
--
-- Based on E W Jordan's algorithm implemented in Processing
-- http://www.ewjordan.com/processing/VolumeBlob/ConstantAreaBlob.pde
-- Licence: Unknown
--

supportedOrientations(LANDSCAPE_ANY)
displayMode(FULLSCREEN)
function setup()
twoPi   = 2 * math.pi
epsilon = .0001
g       = 9.8 * 3 -- Gravity

l = 10            -- Left edge
r = WIDTH - 10    -- Right edge
b = 10            -- Bottom edge
t = HEIGHT - 10   -- Top edge

tRadius = 50      -- Size of touch circle
tX = WIDTH/4      -- Position of touch circle
tY = HEIGHT/4
tvX = 0           -- Velocity of touch circle
tvY = 0
k = 3             -- Spring strength
drag = 2          -- Drag on movement
destX = tX        -- Destination of touch circle
destY = tY

jForce = 200      -- Strength of jump, if double-tapped

n           = 40  -- Number of points in bubble
bRadius     = 100 -- Size of bubble
nIters  = 5
relax   = 0.9

setupBubble()     -- Create the bubble
textMode(CORNER)
end

function draw()
respondToEvents()
integrate(DeltaTime)
constrainEdges()
collideWithWalls()
collideWithTouch()
updateTouch(DeltaTime)

background(0)
fill(255)
text("Fixed-area Bubble (with acknowledgements to E W Jordan)",
10 , HEIGHT - 25)
drawBubble()
drawTouch()
end

function touched(touch)
if isInsideBubble(touch.x, touch.y) then
if touch.tapCount == 2 and touch.state == ENDED and
hitFloor then
jump = true
end
else
destX = touch.x
destY = touch.y
end
end

function isInsideBubble(pX, pY)
for i = 1, n do
local c = (pY - y[i]) * (x[i % n + 1] - x[i]) -
(pX - x[i]) * (y[i % n + 1] - y[i])
if c > 0 then return false end
end
return true
end

function updateTouch(dt)
local fX = (destX - tX) * k - tvX * drag
local fY = (destY - tY) * k - tvY * drag
tvX = tvX + fX * dt
tvY = tvY + fY * dt
tX = tX + tvX * dt
tY = tY + tvY * dt
end

function respondToEvents()
if jump and hitFloor then
local cmx = 0
local cmy = 0
for i = 1, n do
cmx = cmx + x[i]
cmy = cmy + y[i]
end
cmx = cmx / n
cmy = cmy / n
for i = 1, n do
ax[i] = ax[i] - (x[i] - cmx) * jForce
end
jump = false
end
end

function drawTouch()
stroke(0, 240, 255)
strokeWidth(5)
noFill()
end

function drawBubble()
fill(255)
stroke(255)
strokeWidth(5)
for i = 1, n do
line(x[i], y[i], x[i % n + 1], y[i % n + 1])
end
end

function setupBubble()
x = {}
y = {}
xLast = {}
yLast = {}
ax = {}
ay = {}

local cx = WIDTH/2
local cy = HEIGHT/2

for i = 1, n do
local a = (i - 1)/n * twoPi
x[i] = cx + math.sin(a) * bRadius
y[i] = cy + math.cos(a) * bRadius
xLast[i] = x[i]
yLast[i] = y[i]
ax[i] = 0
ay[i] = 0
end
local dx = x[2] - x[1]
local dy = y[2] - y[1]
len = math.sqrt(dx * dx + dy * dy)
bubbleAreaTarget = bubbleArea()
end

function fixEdge()
local dx = {}
local dy = {}
for i = 1, n do
dx[i] = 0
dy[i] = 0
end
for count = 1, nIters do
for i = 1, n do
local j = i % n + 1
local eX = x[j] - x[i]
local eY = y[j] - y[i]
local d = math.sqrt(eX * eX + eY * eY)
if d < epsilon then d = 1 end
local ratio = 1 - len / d
dx[i] = dx[i] + relax * eX * ratio / 2
dy[i] = dy[i] + relax * eY * ratio / 2
dx[j] = dx[j] - relax * eX * ratio / 2
dy[j] = dy[j] - relax * eY * ratio / 2
end
for i = 1, n do
x[i] = x[i] + dx[i]
y[i] = y[i] + dy[i]
dx[i] = 0
dy[i] = 0
end
end
end

function constrainEdges()
fixEdge()
local edge = 0
local nx = {}
local ny = {}
for i = 1, n do
local j = i % n + 1
local dx = x[j] - x[i]
local dy = y[j] - y[i]
local d = math.sqrt(dx * dx + dy * dy)
if d < epsilon then d = 1 end
nx[i] =  dy / d
ny[i] = -dx / d
edge = edge + d
end
local dArea = bubbleAreaTarget - bubbleArea()
local dH = 0.5 * dArea / edge
for i = 1, n do
local j = i % n + 1
x[j] = x[j] + dH * (nx[i] + nx[j])
y[j] = y[j] + dH * (ny[i] + ny[j])
end
end

function bubbleArea()
local area = 0
for i= 1, n do
area = area + x[i] * y[i % n + 1] - x[i % n + 1] * y[i]
end
area = area/2
return area
end

function integrate(dt)
local dtSqr = dt * dt
for i = 1, n do
local tempX = x[i]
local tempY = y[i]
x[i] = 2 * x[i] - xLast[i] + ax[i] * dtSqr
y[i] = 2 * y[i] - yLast[i] + ay[i] * dtSqr - g * dtSqr
xLast[i] = tempX
yLast[i] = tempY
ax[i] = 0
ay[i] = 0
end
end

function collideWithWalls()
hitFloor = false
for i = 1, n do
if x[i] < l then x[i] = l end
if x[i] > r then x[i] = r end
if y[i] < b then
y[i] = b
xLast[i] = x[i]
hitFloor = true
end
if y[i] > t then y[i] = t end
end
end

function collideWithTouch()
for i = 1, n do
local dx = tX - x[i]
local dy = tY - y[i]
local dSqr = dx * dx + dy * dy
dSqr < epsilon * epsilon) then
local d = math.sqrt(dSqr)
x[i] = x[i] - dx * (tRadius/d - 1)
y[i] = y[i] - dy * (tRadius/d - 1)
end
end
end

``````

An alternative to the `drawBubble()` function, for a solid bubble:

``````
function drawBubble()
local p = {}
for i = 1, n do
p[i] = vec2(x[i], y[i])
end
local t = triangulate(p)
local m = mesh()
m.vertices = t
m:setColors(0, 0, 200)
m:draw()
end

``````

Nice! This is great!

Yes, very impressive

That is fantastic. So much fun to play with.

Wow, I made some great blobs! With enough dots they are like blobs of viscous fluid…

Thanks to E W Jordan for publishing the Processing example. The code below is a variation on the same theme. The main addition allows collisions with blocks. (Updated.)

``````
--
-- Fixed-Area Bubble with Blocks
--
-- Based on E W Jordan's algorithm implemented in Processing
-- http://www.ewjordan.com/processing/VolumeBlob/ConstantAreaBlob.pde
-- Licence: Unknown
--
supportedOrientations(LANDSCAPE_ANY)
displayMode(FULLSCREEN)
function setup()
twoPi = 2 * math.pi
epsilon = .0001
g = 9.8 * 3
l = 10
r = WIDTH - 10
b = 10
t = HEIGHT - 10
tX = WIDTH/4
tY = HEIGHT*4/5
tvX = 0
tvY = 0
k = 3
drag = 2
destX = tX
destY = tY
x, y = {}, {}
isEdged = true
nIters = 5
relax = 0.9
boxes = {}
for i = 1, 3 do
boxes[i * 2 - 1] =
initBox(WIDTH/3, HEIGHT * i/5, WIDTH/(i + 3), 50)
boxes[i * 2] =
initBox(WIDTH * 2/3, HEIGHT * i/5, WIDTH/(i + 3), 50)
end
textMode(CORNER)
end

function draw()
background(0)
if hasBubble then
integrate(DeltaTime)
constrainEdges()
collideWithWalls()
collideWithTouch()
collideWithBoxes()
updateTouch(DeltaTime)
else
fill(255)
text("Fixed-area Bubble with Blocks"..
" (with acknowledgements to E W Jordan).",
10 , HEIGHT - 25)
text("Draw a closed bubble clockwise, slowly, to begin.",
10, HEIGHT - 50)
text("Manipulate the bubble with the pulsing orb.",
10, HEIGHT - 75)
end
for i = 1, #boxes do
boxes[i]:drawBox()
end
if n then drawBubble() end
drawTouch()
end

function touched(touch)
if not hasBubble then
table.insert(x, touch.x)
table.insert(y, touch.y)
n = #x
if touch.state == ENDED then
hasBubble = true
setupBubble()
end
return
end
destX = touch.x
destY = touch.y
end

function updateTouch(dt)
local fX = (destX - tX) * k - tvX * drag
local fY = (destY - tY) * k - tvY * drag
tvX = tvX + fX * dt
tvY = tvY + fY * dt
tX = tX + tvX * dt
tY = tY + tvY * dt
end

function drawTouch()
noStroke()
local a = 32 + 8 * math.sin(ElapsedTime * 3)
fill(255, 0, 192, a)
for i = 1, 16 do
end
end

function drawBubble()
local p = {}
for i = 1, n do
p[i] = vec2(x[i], y[i])
end
local t = triangulate(p)
local m = mesh()
m.vertices = t
m:setColors(0, 0, 200)
m:draw()
if isEdged then
stroke(0, 255, 255)
strokeWidth(5)
for i = 1, n do
line(x[i], y[i], x[i % n + 1], y[i % n + 1])
end
end
end

function setupBubble()
xLast = {}
yLast = {}
ax    = {}
ay    = {}
for i = 1, n do
xLast[i] = x[i]
yLast[i] = y[i]
ax[i] = 0
ay[i] = 0
end
bubbleAreaTarget = bubbleArea()
len = 2 * math.sqrt(math.pi * math.abs(bubbleAreaTarget)) / n
end

function fixEdge()
local dx = {}
local dy = {}
for i = 1, n do
dx[i] = 0
dy[i] = 0
end
for count = 1, nIters do
for i = 1, n do
local j = i % n + 1
local eX = x[j] - x[i]
local eY = y[j] - y[i]
local d = math.sqrt(eX * eX + eY * eY)
if d < epsilon then d = 1 end
local ratio = 1 - len / d
dx[i] = dx[i] + relax * eX * ratio / 2
dy[i] = dy[i] + relax * eY * ratio / 2
dx[j] = dx[j] - relax * eX * ratio / 2
dy[j] = dy[j] - relax * eY * ratio / 2
end
for i = 1, n do
x[i] = x[i] + dx[i]
y[i] = y[i] + dy[i]
dx[i] = 0
dy[i] = 0
end
end
end

function constrainEdges()
fixEdge()
local edge = 0
local nx = {}
local ny = {}
for i = 1, n do
local j = i % n + 1
local dx = x[j] - x[i]
local dy = y[j] - y[i]
local d = math.sqrt(dx * dx + dy * dy)
if d < epsilon then d = 1 end
nx[i] =  dy / d
ny[i] = -dx / d
edge = edge + d
end
local dArea = bubbleAreaTarget - bubbleArea()
local dH = 0.5 * dArea / edge
for i = 1, n do
local j = i % n + 1
x[j] = x[j] + dH * (nx[i] + nx[j])
y[j] = y[j] + dH * (ny[i] + ny[j])
end
end

function bubbleArea()
local area = 0
for i= 1, n do
area = area + x[i] * y[i % n + 1] - x[i % n + 1] * y[i]
end
area = area/2
return area
end

-- Verlet integration
function integrate(dt)
local dtSqr = dt * dt
for i = 1, n do
local tempX = x[i]
local tempY = y[i]
x[i] = 2 * x[i] - xLast[i] + ax[i] * dtSqr
y[i] = 2 * y[i] - yLast[i] + ay[i] * dtSqr - g * dtSqr
xLast[i] = tempX
yLast[i] = tempY
ax[i] = 0 -- Reset acceleration
ay[i] = 0
end
end

function collideWithWalls()
for i = 1, n do
if x[i] < l then x[i] = l end
if x[i] > r then x[i] = r end
if y[i] < b then
y[i] = b
xLast[i] = x[i]
end
if y[i] > t then y[i] = t end
end
end

function collideWithTouch()
for i = 1, n do
local dx = tX - x[i]
local dy = tY - y[i]
local dSqr = dx * dx + dy * dy
dSqr < epsilon * epsilon) then
local d = math.sqrt(dSqr)
x[i] = x[i] - dx * (tRadius/d - 1)
y[i] = y[i] - dy * (tRadius/d - 1)
end
end
end

function collideWithBoxes()
for j = 1, #boxes do
local box = boxes[j]
for i = 1, n do
if box:isInBox(x[i], y[i]) then
x[i], y[i] = box:moveToBoxEdge(x[i], y[i])
end
end
end
end

function isInBox(self, px, py)
if px < self.x - self.w/2 then return false end
if px > self.x + self.w/2 then return false end
if py < self.y - self.h/2 then return false end
if py > self.y + self.h/2 then return false end
return true
end

function moveToBoxEdge(self, px, py)
local dx = px - self.x
local dy = py - self.y
local dex = self.w/2 - math.abs(dx)
local dey = self.h/2 - math.abs(dy)
if dex < dey then
if dx > 0 then
return self.x + self.w/2, py
else
return self.x - self.w/2, py
end
else
if dy > 0 then
return px, self.y + self.h/2
else
return px, self.y - self.h/2
end
end
end

function initBox(x, y, w, h)
return {x = x, y = y, w = w, h = h,
drawBox = drawBox,
isInBox = isInBox,
moveToBoxEdge = moveToBoxEdge}
end

function drawBox(self)
fill(255, 127, 0, 255)
noStroke()
rectMode(CENTER)
rect(self.x, self.y, self.w, self.h)
end

``````

I have updated `setupBubble()` in the variation above. `len` is the constraint for the distance between each of the `n` points of the edge. For a bubble, `len` should have been 1/n th of the circumference of a circle of equivalent target area, not 1/n th of the total length of the edge of the initial bubble.

As, for a circle, `area = pi * r * r` and `circumference = 2 * pi * r`, that gives:

``````
function setupBubble()
...
bubbleAreaTarget = bubbleArea()
len = 2 * math.sqrt(math.pi * math.abs(bubbleAreaTarget)) / n
end

``````

`math.abs()` is required because the signed area of the bubble (calculated by `bubbleArea()`) can be (and is) negative.

Before I ran out of characters in the comment above, I wanted to acknowledge that, in the revised code, drawing the initial bubble was inspired by @juaxix’s 2D physics game sandbox and the shading effect on the orb’s force field was inspired by part of one of @Connorbot999’s special effects.

Cool you you got inspired by my rubish coding skills

My attempt of soft bodey’s (goo ball’s):

``````function setup()
iparameter("xforce",-50,50,0)
physics.body(EDGE,vec2(0,0),vec2(WIDTH,0))
thingy = makejelli(333,333,20,math.random(0,900),5)
end
function draw()
background(0, 0, 0, 255)
strokeWidth(30)
stroke(35, 255, 0, 255)
drawjelli(thingy)
end
function makejelli(xx,yy,dist,angle,squish)
local squishy = 3/squish
local poses = {
vec2(-dist,-dist),
vec2(dist,-dist),
vec2(dist,dist),
vec2(-dist,dist),
vec2(0,0)
}
local derps = {}
for i = 1,5 do
local merr = 0
if i == 5 then
merr = physics.body(CIRCLE,5)  else
merr = physics.body(CIRCLE,3) end
merr.position = merr.position + vec2(xx,yy)
merr.fixedRotation = false
merr.linearVelocity= vec2(math.random(-1,1),math.random(-1,1))
merr.friction = 1.1
derps[i] = merr
end
for j = 1,4 do
local aa = derps[5]
local bb = derps[j]
local mup =    physics.joint(DISTANCE,aa,bb,aa.position,bb.position)
mup.frequency = 222/dist*squishy
for k = 1,4 do
if j~= k then
local aa = derps[j]
local bb = derps[k]
local mup =    physics.joint(DISTANCE,aa,bb,aa.position,bb.position)
if math.abs(j-k) == 2 then mup.frequency = 88/dist*squishy else
mup.frequency = 66/dist*squishy
end
end
end
end
return derps
end
function drawjelli(derps)
for j = 1,5 do
for k = 1,5 do
if j~= k then
local aa = derps[j]
local bb = derps[k]
line(aa.x,aa.y,bb.x,bb.y)
end
end
end
end
``````

Hello @Connorbot999. I see that you make your soft body from five particles in a square (one at its centre), with soft distance joints between all of the particles; and that you render the body by using fat lines between particles.

However, you double-up on the joints between the corners and on the lines that you render. I think you also run the risk of an automatic garbage collection destroying the floor `EDGE` and all the joints, because they are not coded to be preserved (unlike the particles, which are preserved in `derps`).

See, for example, the alternative code below:

``````
supportedOrientations(LANDSCAPE_ANY)
function setup()
size = 50
floor = physics.body(EDGE, vec2(0, size), vec2(WIDTH, size))
joints = {} -- This will hold the joints
thingy = makeJelli(WIDTH/2, HEIGHT * 4/5, size, math.random(0, 90))
stroke(35, 255, 0)
strokeWidth(size * 1.5)
-- collectgarbage()
end

function draw()
background(0)
drawJelli(thingy)
end

-- Touch the viewer to see the underlying structure of the goo ball
function touched(touch)
if touch.state == ENDED then
strokeWidth(size * 1.5)
else
strokeWidth(5)
end
end

function makeJelli(xx, yy, dist, angle)
local poses = {
vec2(-dist, -dist),
vec2( dist, -dist),
vec2( dist,  dist),
vec2(-dist,  dist),
vec2(0, 0)
}
local derps = {}
for i = 1, 5 do
local merr = physics.body(CIRCLE, 3)
merr.position = poses[i]:rotate(rad) + vec2(xx, yy)
merr.friction = 1.1
derps[i] = merr
end
local cc = derps[5]
for j = 1, 4 do
local bb = derps[j]
local mup = addJ(physics.joint(DISTANCE, cc, bb,
cc.position, bb.position))
mup.frequency = 7
k = j % 4 + 1
local aa = derps[k]
local mup = addJ(physics.joint(DISTANCE, aa, bb,
aa.position, bb.position))
mup.frequency = 4
end
return derps
end

function drawJelli(derps)
for j = 1, 3 do
local aa = derps[j]
for k = j + 1, 4 do
local bb = derps[k]
line(aa.x, aa.y, bb.x, bb.y)
end
end
end

-- Helper function
table.insert(joints, joint)
return joint
end

``````

A problem you may encounter is: What is to happen when more than one of your goo balls collide? That is why my goo balls here have linked thin rectangles around the edge.

@Connorbot999, is your code based on KMEB example ? I’m just curious! http://twolivesleft.com/Codea/Talk/discussion/1141/softbodies/p1

.@yelnats the code @Connorbot999 posted appears to be a slight modification of @KMEB’s example.

.@Connorbot999 it would be good if you could acknowledge this next time.

I jest you’d his Method?