Flexible words

By popular request (two people asked), here’s the code for my latest mesh experiments. What I’ve been trying to do is stick a bit of physics in to a mesh by defining some dynamical rules. If you look at the start of the code then there are a few options for different settings: what touching the screen means (`method`) and how the vertices of the mesh react to each other (the idea is to model them either as connected by springs - `NEWTON` (though `HOOKE` would be more appropriate) or as a rubber sheet (`TENSION`)). `showpoints` is quite fun.

``````

displayMode(FULLSCREEN)
--supportedOrientations(LANDSCAPE_LEFT)
--startRecording()
--stopRecording()

DRAG = 1
REPELL = 2
ATTRACT = 3
NEWTON = 1
TENSION = 2

function setup()
watch("debug")
method = REPELL
ode = TENSION
--showpoints = true
screen = mesh()
side = 50
spr = 1
fr = .1
ht = side*math.sqrt(3)/2
local n = math.floor(WIDTH/side)+1
local m = math.floor(HEIGHT/ht)+1
local pts = {}
tri = {}
for i=1,m do
rpts = {}
for j = 1,n do
pt = {point = v, velocity = vec2(0,0), neighbours = {}}

if j ~= 1 then
table.insert(pt.neighbours,rpts[j-1])
table.insert(rpts[j-1].neighbours,pt)
end
if i ~= 1 then
table.insert(pt.neighbours,pts[i-1][j])
table.insert(pts[i-1][j].neighbours,pt)
if i%2 == 1 then
if j ~= 1 then
table.insert(pt.neighbours,pts[i-1][j-1])
table.insert(pts[i-1][j-1].neighbours,pt)
table.insert(tri,{pt,rpts[j-1],pts[i-1][j-1]})
table.insert(tri,{pt,pts[i-1][j-1],pts[i-1][j]})
end
else
if j ~= n then
table.insert(pt.neighbours,pts[i-1][j+1])
table.insert(pts[i-1][j+1].neighbours,pt)
table.insert(tri,{pt,pts[i-1][j],pts[i-1][j+1]})
end
if j ~= 1 then
table.insert(tri,{pt,rpts[j-1],pts[i-1][j]})
end
end
end
table.insert(rpts,pt)
end
table.insert(pts,rpts)
end
points = {}
for k,v in ipairs(pts) do
for l,u in ipairs(v) do
table.insert(points,u)
end
end
local n
for k,v in ipairs(points) do
n = 0
for l,u in ipairs(v.neighbours) do
n = n + 1
end
if n == 6 then
v.fixed = false
else
v.fixed = true
end
end
img = image(WIDTH,HEIGHT)
pushStyle()
setContext(img)
fill(0, 107, 255, 255)
noSmooth()
rect(0,0,WIDTH,HEIGHT)
font("Noteworthy-Bold")
fontSize(160)
fill(255, 255, 255, 255)
local fm = fontMetrics()
local h = 1.8*fm.xHeight
fill(0, 255, 21, 255)
text("Words",WIDTH/2,HEIGHT/2 + h)
text("are",WIDTH/2,HEIGHT/2)
text("flexible",WIDTH/2,HEIGHT/2 - h)
setContext()
popStyle()
screen.texture = img
texc = {}
for k,v in ipairs(tri) do
for l,u in ipairs(v) do
table.insert(texc,vec2(u.point.x/WIDTH,u.point.y/HEIGHT))
end
end
screen.texCoords = texc
--[[
screen = mesh()
local a = {}
local b = {}
local c = {}
a.point = vec2(WIDTH/2,HEIGHT/2) + vec2(0,200)
b.point = vec2(WIDTH/2,HEIGHT/2) + vec2(-200,0)
c.point = vec2(WIDTH/2,HEIGHT/2) + vec2(200,0)
a.velocity = vec2(0,0)
b.velocity = vec2(0,0)
c.velocity = vec2(0,0)
a.neighbours = {b,c}
b.neighbours = {a,c}
c.neighbours = {b,a}
a.fixed = false
b.fixed = false
c.fixed = false
tri = {{a,b,c}}
points = {a,b,c}
--]]
end
``````

Code continues:

``````function draw()
background(0, 0, 0, 255)
if ode == NEWTON then
local f,c,n,cl
for k,v in ipairs(points) do
if method == DRAG then
if v == touchedpt then
f = touchpt - v.point
else
f = vec2(0,0)
end
elseif method == REPELL then
if touchpt then
f = v.point - touchpt
if f:len() > 1 then
f = 100000*f/(f:len()^3)
end
else
f = vec2(0,0)
end
elseif method == ATTRACT then
if touchpt then
f = v.point - touchpt
if f:len() > 1 then
f = -5000*f/(f:len()^3)
end
else
f = vec2(0,0)
end
end
n = 0
cl = 0
for l,u in ipairs(v.neighbours) do
c = u.point - v.point
n = n + 1
f = f + spr*c
cl = cl + c:len()
end
if n == 6 then -- interior point
f = f -  fr*v.velocity
v.velocity = v.velocity + DeltaTime * f
end
cl = cl/n - side
v.colour = 64*(math.atan(cl)/math.pi+1/2) + 191
end
for k,v in ipairs(points) do
v.point = v.point + DeltaTime*v.velocity
end
else
for k,v in ipairs(points) do
v.force = vec2(0,0)
end
local a,b,c
for k,v in ipairs(tri) do
a = v[3].point - v[2].point
b = v[1].point - v[3].point
c = v[2].point - v[1].point
a,b,c = TriangleForce(a,b,c,side)
v[1].force = v[1].force + a
v[2].force = v[2].force + b
v[3].force = v[3].force + c
end
for k,v in ipairs(points) do

if not v.fixed then
if method == DRAG then
if v == touchedpt then
v.force = v.force + (touchpt - v.point)
end
elseif method == REPELL then
if touchpt then
local f = v.point - touchpt
if f:len() > side then
f = 100000*f/(f:len()^3)
v.force = v.force + f
end

end
elseif method == ATTRACT then
if touchpt then
local f = v.point - touchpt
if f:len() > 1 then
f = -100000*f/(f:len()^3)
end
v.force = v.force + f
end
end
v.force = v.force - fr*v.velocity
if showpoints then

end
v.velocity = v.velocity + DeltaTime*v.force
--v.velocity =  DeltaTime*v.force
v.point = v.point + DeltaTime*v.velocity

end

end
end
local n,c,cl
for k,v in ipairs(points) do
n = 0
cl = 0
for l,u in ipairs(v.neighbours) do
c = u.point - v.point
n = n + 1
cl = cl + c:len()
end
cl = cl/n - side
v.colour = 128*(math.atan(cl)/math.pi+1/2) + 127
end
fill(0, 255, 38, 255)
local ver = {}
local col = {}
for k,v in ipairs(tri) do
for l,u in ipairs(v) do
table.insert(ver,u.point)
table.insert(col,color(255,255,255,u.colour))
end
end
screen.vertices = ver
screen.colors = col
screen:draw()
if showpoints then

fill(255, 0, 0, 255)
for k,v in ipairs(points) do
noStroke()
ellipse(v.point.x, v.point.y,2)
strokeWidth(5)
stroke(255, 0, 0, 255)
line(v.point.x,v.point.y,
v.point.x+v.force.x,v.point.y+v.force.y)
end
fill(230, 255, 0, 135)
end
if showtouch then
if touchpt then
ellipse(touchpt.x,touchpt.y,50)
end
end
end

function touched(touch)
touchpt = vec2(touch.x,touch.y)
if touch.state == BEGAN then
if method == DRAG then
touchedpt = points[1]
end
elseif touch.state == ENDED then
touchedpt = nil
touchpt = nil
end
end

function TriangleForce(a,b,c,s)
-- force exerted on the vertices by a deformed triangle
-- first approximation: drive each point to make an isosceles
-- triangle of the same area with the other side fixed
local vv,l,h
local iarea = s^2 * math.sqrt(3)/4

local area = a:rotate90():dot(b)/2
local sides = {a,b,c}

local r = {}
for k,v in ipairs(sides) do
l = v:len()
h = iarea*2/l
vv = v:rotate90()/l
vv = h*Sign(vv:dot(sides[k%3+1]))*vv
table.insert(r, (vv + .5*v + sides[(k+1)%3+1]))
end
return unpack(r)
end

function Sign(x)
if x > 0 then
return 1
elseif x < 0 then
return 1
else
return 0
end
end

function TriangleArea(a,b,c)
return math.abs(
a:dot(b:rotate90())
+ b:dot(c:rotate90())
+ c:dot(a:rotate90())
)/2
end

``````

Cool! I’ll try it when I get a chance!

Thanks Andrew! I saw your YouTube video a day or two ago and was very impressed. Nice to have the code to see how you did it.

I’m really glad that I posted this code! Through my own stupidity, I just lost a load of code and this was the main bit - notlostforever, thankfully!

I tend to use code sharing as a form of backup as well.

Got a chance to try this out. But, is it just me or is there something wrong with the code? Because I got the text upside down. On iPad 1, btw.

Thanks for the code, Andrew. Really appreciate it.

Oh, Andrew… Would you mind sharing the code of the other program (text on a sphere)? I’m curious how you did that. Thank you in advance.

@bee Ah, the text-upside-down thing is due to a “feature” in how the image is rendered on to the mesh which meant that it got turned upside down (basically, the coordinates for the texture start at top-left instead of bottom-left like everything else in Codea). This is fixed in 1.3.1. As I’m a beta-user, I’m using the fixed version. I forgot about that, sorry.

Unfortunately, the text-on-a-sphere was part of the code I lost. I should be able to reconstruct it (I only lost that particular part of it), but I’m currently having problems with the project that that is part of so it might take me a few days.

Thank you, Andrew. Feel sorry for your lost. Looking forward for your code. I’m neither mathematician or visual programmer, I learn a lot from your code, also from other experts here.

In fact, I thought you might do the upside-down text intentionally. However,it looks good when I saw from the mirror. @Andrew_Stacey Thank you.

To flip the image the right way up (for non beta-testers), locate the section that looks like:

``````    screen.texture = img
texc = {}
for k,v in ipairs(tri) do
for l,u in ipairs(v) do
table.insert(texc,vec2(u.point.x/WIDTH,u.point.y/HEIGHT))
end
end
screen.texCoords = texc
``````

(I think it’s around line 107) and change `u.point.y/HEIGHT` to `1 - u.point.y/HEIGHT`. That ought to do it.

@Andrew Thank you. I got it works now.

@Andrew_Stacey: Pardon me for being a newbie in this mesh thingy. Would you please explain what is your code doing here? I mean in a bit detail explanation. What I have understood is that you draw an image which contains some text, devide it into triangles which become meshes, then apply some physics to it where touch happens. Is it correct? What I haven’t yet understood is how the animation become so smooth. For example, the text isn’t just got cropped by the triangles. Instead, it seems so fluid as if the text is over some water. I know it’s done somewhere in the complicated computation code, but in which part to be exact? Could you please explain how the computation is done to create such effect? For any kind of explanation, I thank you in advance. If this request bothers you, feel free to ignore it.

@bee: It doesn’t bother me at all. I like explaining stuff! I’ll have a think about how best to go through it - I had been thinking of revisiting this and changing my manual physics stuff for the inbuilt physics engine (now that I’ve had a play with that and at least learnt the basics).

@Andrew_Stacey: No, please don’t use the built-in physic. I’d like to know how you did such beautiful effect using computation. I want to learn the beauty of the mathematics behind it. I always love beautiful representation of mathematics! Though I’m not always able to understand it.