Strandbeest Mechanism Simulation

Simulation of Theo Jansen’s Strandbeest (http://www.strandbeest.com). I first wanted to use the new physics engine for this, but decided against it after I discovered an instance can not change it’s dimensions (which is the main purpose of this simulation).

`
-- Strandbeest Mechanism Simulation
-- invented by Theo Jansen
-- http://www.strandbeest.com/
-- Herwig Van Marck
    
-- Use this function to perform your initial setup
function setup()
    print("Pinch to Zoom the view")
    print("Set clear to 1 to clear path")
    print("Reduce step to slow down")
    touches = {}
    lastPinchDist = 0
    pinchDelta = 1.0
    pinchCenter = vec2(WIDTH/2,HEIGHT/2)
    offset = vec2(0,0)
    zoom = 1
    T=0
    parameter("step",0,0.3,0.12)
    iparameter("clear",0,1,0)
    parameter("a",10,200, 38*3)
    parameter("b", 10, 200, 41.5*3)
    parameter("c", 10, 200, 39.3*3)
    parameter("d", 10, 200, 40.1*3)
    parameter("e", 10, 200, 55.8*3)
    parameter("f", 10, 200, 39.4*3)
    parameter("g", 10, 200, 36.7*3)
    parameter("h", 10, 200, 65.7*3)
    parameter("i", 10, 200, 49*3)
    parameter("j", 10, 200, 50*3)
    parameter("k", 10, 200, 61.9*3)
    parameter("l", 10, 200, 7.8*3)
    parameter("m", 10, 200, 15*3)
    
    img=image(WIDTH,HEIGHT)
    spriteMode(CORNER)
end
 
function touched(touch)
    if touch.state == ENDED then
        touches[touch.id] = nil
    else
        touches[touch.id] = touch
    end
end

function processTouches()
    touchArr = {}
    for k,touch in pairs(touches) do
        -- push touches into array
        table.insert(touchArr,touch)
    end

    if #touchArr == 2 then
        t1 = vec2(touchArr[1].x,touchArr[1].y)
        t2 = vec2(touchArr[2].x,touchArr[2].y)

        dist = t1:dist(t2)
        if lastPinchDist > 0 then 
            pinchDelta = dist/lastPinchDist
            
        else
            offset= offset + ((t1 + t2)/2-pinchCenter)/zoom
        end
        pinchCenter = (t1 + t2)/2
        lastPinchDist = dist
    else
        pinchDelta = 1.0
        lastPinchDist = 0
    end
end

function draw()
    if (clear==1) then
        img=image(WIDTH,HEIGHT)
        clear=0
    end
    -- This sets the background color to black
    background(0, 0, 0)
    
    font("Arial-BoldMT")
    fontSize(50)
    fill(0, 19, 255, 255)
    text("Theo Jansen's",WIDTH/2,HEIGHT -60)
    text("Strandbeest Mechanism",WIDTH/2,HEIGHT -120)
    -- compute pinch delta
    processTouches()
    -- scale by pinch delta
    zoom = math.max( zoom*pinchDelta, 0.2 )

    translate(pinchCenter.x-offset.x*zoom,pinchCenter.y-offset.y*zoom)
    
    scale(zoom,zoom)
    sprite(img,-WIDTH/2,-HEIGHT/2)
    --translate(pinchCenter.x,pinchCenter.y)

    pinchDelta = 1.0
    
    -- This sets the line thickness
    strokeWidth(4)
    
    font("AmericanTypewriter-Condensed")
    fontSize(24)
    fill(248, 248, 248, 255)
    -- Draw mechanism
    x1=0
    y1=0
    x2=a
    y2=l
    stroke(255, 0, 0, 255)
    line(x1,y1,x2,y1)
    text("a",(x2-x1)/2,y1-8)
    line(x2,y1,x2,y2)
    text("l",x2+8,(y2-y1)/2)
    x3=x2+m*math.cos(T)
    y3=y2+m*math.sin(T)
    stroke(251, 255, 0, 255)
    pushStyle()
    fill(0, 0, 0, 0)
    ellipse(x2,y2,m*2,m*2)
    fill(251,255,0,255)
    text("m",x2+m,y2+m+8)
    popStyle()
    line(x2,y2,x3,y3)
    tmp=math.acos((j^2-b^2-x3^2-y3^2)/(-2*b*math.sqrt(x3^2+y3^2)))+math.atan(y3/x3)
    x4=b*math.cos(tmp)
    y4=b*math.sin(tmp)
    stroke(255, 255, 255, 255)
    line(x1,y1,x4,y4)
    text("b",(x1+x4)/2+8,(y1+y4)/2)
    line(x3,y3,x4,y4)
    text("j",(x3+x4)/2,(y3+y4)/2+16)
    tmp2=math.atan(y3/x3)-math.acos((k^2-c^2-x3^2-y3^2)/(-2*c*math.sqrt(x3^2+y3^2)))
    x7=c*math.cos(tmp2)
    y7=c*math.sin(tmp2)
    line(x1,y1,x7,y7)
    text("c",(x1+x7)/2-8,(y1+y7)/2)
    line(x3,y3,x7,y7)
    text("k",(x3+x7)/2+8,(y3+y7)/2-4)
    tmp3=math.acos((e^2-b^2-d^2)/(-2*b*d))+tmp
    x5=d*math.cos(tmp3)
    y5=d*math.sin(tmp3)
    line(x1,y1,x5,y5)
    text("d",(x1+x5)/2,(y1+y5)/2+8)
    line(x4,y4,x5,y5)
    text("e",(x4+x5)/2+8,(y4+y5)/2)
    n=math.sqrt(c^2+d^2-2*math.cos(2*math.pi-tmp3+tmp2)*c*d)
    tmp4=math.acos((n^2-f^2-g^2)/(-2*f*g))
    tmp5=math.acos((f^2-n^2-g^2)/(-2*n*g))+math.acos((d^2-n^2-c^2)/(-2*n*c))
    o=math.sqrt(c^2+g^2-2*math.cos(tmp5)*c*g)
    tmp6=math.acos((g^2-o^2-c^2)/(-2*o*c))
    x6=o*math.cos(tmp2-tmp6)
    y6=o*math.sin(tmp2-tmp6)
    line(x5,y5,x6,y6)
    text("f",(x5+x6)/2-8,(y5+y6)/2)
    line(x7,y7,x6,y6)
    text("g",(x6+x7)/2,(y6+y7)/2-8)
    tmp7=math.acos((h^2-i^2-g^2)/(-2*i*g))+tmp5
    p=math.sqrt(c^2+i^2-2*math.cos(tmp7)*c*i)
    tmp8=tmp2-math.asin(i*math.sin(tmp7)/p)
    x8=p*math.cos(tmp8)
    y8=p*math.sin(tmp8)
    line(x6,y6,x8,y8)
    text("h",(x6+x8)/2-8,(y6+y8)/2-4)
    line(x7,y7,x8,y8)
    text("i",(x7+x8)/2+8,(y7+y8)/2)
    if (math.abs(x8)<WIDTH/2 and math.abs(y8)<HEIGHT/2) then
        img:set(x8+WIDTH/2,y8+HEIGHT/2,0,0,255,255)
    end
    T=T+step
end
    
    
`

Corrected the code just now (forgot to escape the ‘<’) :frowning:

Cool. I’ll try it whe. I get a chance. Just one thing that could easily reduce your code by atleast two lines is to use /n to create a new line, so you would do something like this:
Pinch to Zoom the Viewer/nSet clear to one to clear path/nReduce steps to slow down..
I just wanted to give you a little help. It’s small, but I find it helps in the long run. :smiley:

omg this is wonterful!!
you should know i alreadey met theo jansen personally and helped him digging out a forgotten old strandbeest… im a big fan!

This is great, @Herwig. I uploaded it to YouTube here:

http://www.youtube.com/watch?v=Yjpeey1HVO4

Thanks! I haven’t got round to trying the new video generation!

Updated version using my Zoom class (so labels are better when zooming in).

-- Strandbeest Mechanism Simulation
-- invented by Theo Jansen
-- http://www.strandbeest.com/
-- Herwig Van Marck
    
-- Use this function to perform your initial setup
function setup()
    zoom=Zoom(WIDTH/2,HEIGHT/2)
    print("Set clear to 1 to clear path")
    print("Reduce step to slow down")
    T=0
    parameter("step",0,0.3,0.12)
    iparameter("clear",0,1,0)
    parameter("a",10,200, 38*3)
    parameter("b", 10, 200, 41.5*3)
    parameter("c", 10, 200, 39.3*3)
    parameter("d", 10, 200, 40.1*3)
    parameter("e", 10, 200, 55.8*3)
    parameter("f", 10, 200, 39.4*3)
    parameter("g", 10, 200, 36.7*3)
    parameter("h", 10, 200, 65.7*3)
    parameter("i", 10, 200, 49*3)
    parameter("j", 10, 200, 50*3)
    parameter("k", 10, 200, 61.9*3)
    parameter("l", 10, 200, 7.8*3)
    parameter("m", 10, 200, 15*3)
    
    img=image(WIDTH,HEIGHT)
    spriteMode(CORNER)
end
 
function touched(touch)
    zoom:touched(touch)
end

function draw()
    zoom:draw()
    if (clear==1) then
        img=image(WIDTH,HEIGHT)
        clear=0
    end
    -- This sets the background color to black
    background(0, 0, 0)
    
    font("Arial-BoldMT")
    fontSize(50)
    fill(0, 19, 255, 255)
    
    zoom:text("Theo Jansen's",0,HEIGHT/2 -60)
    zoom:text("Strandbeest Mechanism",0,HEIGHT/2 -120)

    sprite(img,-WIDTH/2,-HEIGHT/2)

    strokeWidth(4)
    
    font("AmericanTypewriter-Condensed")
    fontSize(24)
    fill(248, 248, 248, 255)
    -- Draw mechanism
    x1=0
    y1=0
    x2=a
    y2=l
    stroke(255, 0, 0, 255)
    line(x1,y1,x2,y1)
    zoom:text("a",(x2-x1)/2,y1-8)
    line(x2,y1,x2,y2)
    zoom:text("l",x2+8,(y2-y1)/2)
    x3=x2+m*math.cos(T)
    y3=y2+m*math.sin(T)
    stroke(251, 255, 0, 255)
    pushStyle()
    fill(0, 0, 0, 0)
    ellipse(x2,y2,m*2,m*2)
    fill(251,255,0,255)
    zoom:text("m",x2+m,y2+m+8)
    popStyle()
    line(x2,y2,x3,y3)
    tmp=math.acos((j^2-b^2-x3^2-y3^2)/(-2*b*math.sqrt(x3^2+y3^2)))+math.atan(y3/x3)
    x4=b*math.cos(tmp)
    y4=b*math.sin(tmp)
    stroke(255, 255, 255, 255)
    line(x1,y1,x4,y4)
    zoom:text("b",(x1+x4)/2+8,(y1+y4)/2)
    line(x3,y3,x4,y4)
    zoom:text("j",(x3+x4)/2,(y3+y4)/2+16)
    tmp2=math.atan(y3/x3)-math.acos((k^2-c^2-x3^2-y3^2)/(-2*c*math.sqrt(x3^2+y3^2)))
    x7=c*math.cos(tmp2)
    y7=c*math.sin(tmp2)
    line(x1,y1,x7,y7)
    zoom:text("c",(x1+x7)/2-8,(y1+y7)/2)
    line(x3,y3,x7,y7)
    zoom:text("k",(x3+x7)/2+8,(y3+y7)/2-4)
    tmp3=math.acos((e^2-b^2-d^2)/(-2*b*d))+tmp
    x5=d*math.cos(tmp3)
    y5=d*math.sin(tmp3)
    line(x1,y1,x5,y5)
    zoom:text("d",(x1+x5)/2,(y1+y5)/2+8)
    line(x4,y4,x5,y5)
    zoom:text("e",(x4+x5)/2+8,(y4+y5)/2)
    n=math.sqrt(c^2+d^2-2*math.cos(2*math.pi-tmp3+tmp2)*c*d)
    tmp4=math.acos((n^2-f^2-g^2)/(-2*f*g))
    tmp5=math.acos((f^2-n^2-g^2)/(-2*n*g))+math.acos((d^2-n^2-c^2)/(-2*n*c))
    o=math.sqrt(c^2+g^2-2*math.cos(tmp5)*c*g)
    tmp6=math.acos((g^2-o^2-c^2)/(-2*o*c))
    x6=o*math.cos(tmp2-tmp6)
    y6=o*math.sin(tmp2-tmp6)
    line(x5,y5,x6,y6)
    zoom:text("f",(x5+x6)/2-8,(y5+y6)/2)
    line(x7,y7,x6,y6)
    zoom:text("g",(x6+x7)/2,(y6+y7)/2-8)
    tmp7=math.acos((h^2-i^2-g^2)/(-2*i*g))+tmp5
    p=math.sqrt(c^2+i^2-2*math.cos(tmp7)*c*i)
    tmp8=tmp2-math.asin(i*math.sin(tmp7)/p)
    x8=p*math.cos(tmp8)
    y8=p*math.sin(tmp8)
    line(x6,y6,x8,y8)
    zoom:text("h",(x6+x8)/2-8,(y6+y8)/2-4)
    line(x7,y7,x8,y8)
    zoom:text("i",(x7+x8)/2+8,(y7+y8)/2)
    if (math.abs(x8)<WIDTH/2 and math.abs(y8)<HEIGHT/2) then
        img:set(x8+WIDTH/2,y8+HEIGHT/2,0,0,255,255)
    end
    T=T+step
end

-- Zoom library
-- Herwig Van Marck
-- usage:
--[[
function setup()
    zoom=Zoom(WIDTH/2,HEIGHT/2)
end
function touched(touch)
    zoom:touched(touch)
end
function draw()
    zoom:draw()
end
]]--

Zoom = class()

function Zoom:init(x,y)
    -- you can accept and set parameters here
    self.touches = {}
    self.initx=x
    self.inity=y
    self:clear()
    print("Tap and drag to move\
Pinch to zoom\
Double tap to reset")
end

function Zoom:clear()
    self.lastPinchDist = 0
    self.pinchDelta = 1.0
    self.center = vec2(self.initx,self.inity)
    self.offset = vec2(0,0)
    self.zoom = 1
    self.started = false
    self.started2 = false
end

function Zoom:touched(touch)
    -- Codea does not automatically call this method
    if touch.state == ENDED then
        self.touches[touch.id] = nil
    else
        self.touches[touch.id] = touch
        if (touch.tapCount==2) then
            self:clear()
        end
    end
end

function Zoom:processTouches()
    local touchArr = {}
    for k,touch in pairs(self.touches) do
        -- push touches into array
        table.insert(touchArr,touch)
    end

    if #touchArr == 2 then
        self.started = false
        local t1 = vec2(touchArr[1].x,touchArr[1].y)
        local t2 = vec2(touchArr[2].x,touchArr[2].y)

        local dist = t1:dist(t2)
        if self.started2 then
        --if self.lastPinchDist > 0 then 
            self.pinchDelta = dist/self.lastPinchDist          
        else
            self.offset= self.offset + ((t1 + t2)/2-self.center)/self.zoom
            self.started2 = true
        end
        self.center = (t1 + t2)/2
        self.lastPinchDist = dist
    elseif (#touchArr == 1) then
        self.started2 = false
        local t1 = vec2(touchArr[1].x,touchArr[1].y)
        self.pinchDelta = 1.0
        self.lastPinchDist = 0
        if not(self.started) then
            self.offset = self.offset + (t1-self.center)/self.zoom
            self.started = true
        end
        self.center=t1
    else
        self.pinchDelta = 1.0
        self.lastPinchDist = 0
        self.started = false
        self.started2 = false
    end
end

function Zoom:clip(x,y,w,h)
    clip(x*self.zoom+self.center.x- self.offset.x*self.zoom,
        y*self.zoom+self.center.y- self.offset.y*self.zoom,
        w*self.zoom+1,h*self.zoom+1)
end

function Zoom:text(str,x,y)
    local fSz = fontSize()
    local xt=x*self.zoom+self.center.x- self.offset.x*self.zoom
    local yt=y*self.zoom+self.center.y- self.offset.y*self.zoom
    fontSize(fSz*self.zoom)
    local xtsz,ytsz=textSize(str)
    tsz=xtsz
    if tsz<ytsz then tsz=ytsz end
    if (tsz>2048) then
        local eZoom= tsz/2048.0
        fontSize(fSz*self.zoom/eZoom)
        pushMatrix()
        resetMatrix()
        translate(xt,yt)
        scale(eZoom)
        text(str,0,0)
        popMatrix()
        fontSize(fSz)
    else
        pushMatrix()
        resetMatrix()
        fontSize(fSz*self.zoom)
        text(str,xt,yt)
        popMatrix()
        fontSize(fSz)
    end
end

function Zoom:draw()
    -- compute pinch delta
    self:processTouches()
    -- scale by pinch delta
    self.zoom = math.max( self.zoom*self.pinchDelta, 0.2 )

    translate(self.center.x- self.offset.x*self.zoom,
        self.center.y- self.offset.y*self.zoom)
    
    scale(self.zoom,self.zoom)

    self.pinchDelta = 1.0
end

Just finished collecting your recent works. This one I’d never heard of. Really fun to watch.

When I first came across it I wanted to see how other dimensions worked. I managed to simulate it in Spacetime (on iPad), but I have to say it is much better in Codea!