L-System drawing

L-System drawing program.

-- L-System
-- Herwig Van Marck

function setup()
    print("L-System\
While title is red the next level is calculated.")
    print("Change timeStep to slow down or speed up time to next level.")
    print("Double tap to restart")
    iparameter("timeStep",0,30,10)
    iparameter("example",1,9,9)
    time=0
    curExample=example
    lsys=initExample(curExample)
    zoom=Zoom(WIDTH/2,HEIGHT/2)
    strokeWidth(2)
    noSmooth()
end

function touched(touch)
    if (touch.state==BEGAN and touch.tapCount==2) then
        lsys=initExample(curExample)
        zoom:touched(touch)
    else
        zoom:touched(touch)
    end
end

function draw()
    if example~=curExample then
        curExample=example
        lsys=initExample(curExample)
        zoom:clear()
    end
    if (time>=600) then
        time=0
        if (lsys.level<lsys.maxlevel) then
            lsys:transform()
        end
    else
        time = time + timeStep
    end
    background(0, 0, 0, 255)
    lsys:drawTitle()
    zoom:draw()
    lsys:draw()
end

function initExample()
    if (example==1) then
        return LSystem("Plant",40,"NSL",
        {
            A=Step(Step.LINE,500),
            B=Step(Step.LINE,100),
            C=Step(Step.LINE,200),
            D=Step(Step.LINE,300),
            E=Step(Step.LINE,400),
            L=Step(Step.LINE,200),
            P=Step(Step.ROT,25),
            M=Step(Step.ROT,-29),
            U=Step(Step.PUSH),
            O=Step(Step.POP),
            N=Step(Step.TRANS,-250,0),
            S=Step(Step.SCALE,0.875)
        },
        {
            Rule("AB","AC"),
            Rule("AC","AD"),
            Rule("AD","AE"),
            Rule("AE","AA"),
            Rule("A","AB"),
            Rule("BL","CL"),
            Rule("CL","DL"),
            Rule("DL","EL"),
            Rule("L","BL"),
            Rule("EL","AUPLOML"),
            Rule("NS","NSS")
        })
    elseif example==2 then
        return LSystem("Dragon",14,"NABFX",
        {
            A=Step(Step.ROT,-45),
            B=Step(Step.SCALE,0.75),
            N=Step(Step.TRANS,-50,50),
            F=Step(Step.LINE,200),
            X=Step(Step.NOP),
            Y=Step(Step.NOP),
            P=Step(Step.ROT,90),
            M=Step(Step.ROT,-90)
        },
        {
            Rule("X","XPYF"),
            Rule("Y","FXMY"),
            Rule("NAB","NABAB")
        })
    elseif example==3 then
        return LSystem("Cantor Dust",8,"NSA",
        {
            N=Step(Step.TRANS,-250,0),
            A=Step(Step.LINE,1500),
            B=Step(Step.TRANS,1500,0),
            S=Step(Step.SCALE,1/3.0)
        },
        {
            Rule("NS","NSS"),
            Rule("A","ABA"),
            Rule("B","BBB")
        }
        )
    elseif example==4 then
        return LSystem("Koch curve",7,"NSA",
        {
            N=Step(Step.TRANS,-300,0),
            S=Step(Step.SCALE,1/3.0),
            A=Step(Step.LINE,1500),
            P=Step(Step.ROT,60),
            M=Step(Step.ROT,-120)
        },
        {
            Rule("NS","NSS"),
            Rule("A","APAMAPA")
        }
        )
    elseif example==5 then
        return LSystem("Right angle Koch curve",6,"NSA",
        {
            N=Step(Step.TRANS,-300,0),
            S=Step(Step.SCALE,1/3.0),
            A=Step(Step.LINE,1500),
            P=Step(Step.ROT,90),
            M=Step(Step.ROT,-90)
        },
        {
            Rule("NS","NSS"),
            Rule("A","APAMAMAPA")
        }
        )
    elseif example==6 then
        return LSystem("Sierpinsky Triangle",9,"NSQA",
        {
            N=Step(Step.TRANS,-200,-200),
            S=Step(Step.SCALE,0.5),
            Q=Step(Step.NOP),
            R=Step(Step.ROT,60),
            A=Step(Step.LINE,800),
            B=Step(Step.LINE,800),
            P=Step(Step.ROT,60),
            M=Step(Step.ROT,-60)
        },
        {
            Rule("NS","NSS"),
            Rule("A","BMAMB"),
            Rule("B","APBPA"),
            Rule("Q","R"),
            Rule("R","Q")
        }
        )
    elseif example==7 then
        return LSystem("Fractal Plant",7,"NSX",
        {
            N=Step(Step.TRANS,-300,0),
            S=Step(Step.SCALE,0.5),
            X=Step(Step.NOP),
            F=Step(Step.LINE,500),
            M=Step(Step.ROT,-25),
            P=Step(Step.ROT,25),
            U=Step(Step.PUSH),
            O=Step(Step.POP)
            
        },
        {
            Rule("NS","NSS"),
            Rule("X","FMUUXOPXOPFUPFXOMX"),
            Rule("F","FF")
        }
        )
    elseif example==8 then
        return LSystem("Hilbert curve",7,"NSA",
        {
            N=Step(Step.TRANS,-200,-200),
            Q=Step(Step.TRANS,-100,-100),
            S=Step(Step.SCALE,0.5),
            A=Step(Step.NOP),
            B=Step(Step.NOP),
            M=Step(Step.ROT,-90),
            P=Step(Step.ROT,90),
            F=Step(Step.LINE,800)
        },
        {
            Rule("NS","QSNS"),
            Rule("A","PBFMAFAMFBP"),
            Rule("B","MAFPBFBPFAM")
        }
        )
    elseif example==9 then
        return LSystem("Penrose Tiling",6,"NSUBOPPUBOPPUBOPPUBOPPUBO",
        {
            N=Step(Step.NOP),
            S=Step(Step.SCALE,2.0/3.0),
            A=Step(Step.NOP),
            B=Step(Step.NOP),
            C=Step(Step.NOP),
            D=Step(Step.NOP),
            F=Step(Step.LINE,200),
            U=Step(Step.PUSH),
            O=Step(Step.POP),
            M=Step(Step.ROT,-36),
            P=Step(Step.ROT,36)
        },
        {
            Rule("NS","NSS"),
            Rule("A","CFPPDFMMMMBFUMCFMMMMAFOPP"),
            Rule("B","PCFMMDFUMMMAFMMBFOP"),
            Rule("C","MAFPPBFUPPPCFPPDFOM"),
            Rule("D","MMCFPPPPAFUPDFPPPPBFOMMBF"),
            Rule("F",""),
            Rule("PPPMMM",""),
            Rule("MMMPPP",""),
            Rule("PPMM",""),
            Rule("MMPP",""),
            Rule("PM",""),
            Rule("MP","")
        }
        )
    else -- placeholder to copy and paste from
        return LSystem("",1,"N",
        {
            N=Step(Step.NOP)
        },
        {
            Rule("N","N")
        }
        )
    end
end

L-System class

LSystem = class()

function LSystem:init(name,maxlevel,start,steps,rules)
    self.name = name
    self.maxlevel = maxlevel
    self.level = 1
    self.str = start
    self.steps = steps
    self.rules = rules
    self:updateCount()
end

function LSystem:drawTitle()
    textMode(CENTER)
    font("ArialRoundedMTBold")
    fontSize(50)
    if self.level<self.maxlevel then
        fill(255, 0, 0, 255)
    else
        fill(0, 1, 255, 255)
    end
    text(self.name.." ("..self.level..")",WIDTH/2,HEIGHT -75)
end

function LSystem:draw()
    local cnt=0
    for i=1,string.len(self.str) do
        stroke(255*(1-cnt/self.count),0,255*cnt/self.count,255)
        local step=self.steps[string.sub(self.str,i,i)]
        step:draw()
        if (step.type==Step.LINE) then
            cnt = cnt + 1
        end
    end
end

function LSystem:updateCount()
    local cnt=0
    for k=1,#self.str do
        if self.steps[string.sub(self.str,k,k)].type==Step.LINE then
            cnt = cnt + 1
        end
    end
    self.count=cnt
end

function LSystem:transform()
    local i=1
    local res=""
    local cnt=0
    while (i<=string.len(self.str)) do
        local flg=true
        for j=1,#self.rules do
            local stri=self.rules[j].stri
            local stro=self.rules[j].stro
            if (string.sub(self.str,i,i+string.len(stri)-1)==stri) then
                i = i + string.len(stri)
                res = res .. stro
                flg=false
                break
            end
        end
        if flg then
            res = res .. string.sub(self.str,i,i)
            i = i + 1
        end
    end
    --print(string.len(res))
    self.str=res
    self.level = self.level + 1
    self:updateCount()
end

Rule = class()

function Rule:init(stri,stro)
    self.stri = stri
    self.stro = stro
end

Step = class()

Step.NOP=0
Step.TRANS=1
Step.ROT=2
Step.PUSH=3
Step.POP=4
Step.SCALE=5

function Step:init(type,val,val2)
    self.type = type
    self.val = val
    self.val2 = val2
end

function Step:draw()
    -- Codea does not automatically call this method
    if (self.type==Step.TRANS) then
        translate(self.val,self.val2)
    elseif (self.type==Step.LINE) then
        line(0,0,self.val,0)
        translate(self.val,0)
    elseif (self.type==Step.ROT) then
        rotate(self.val)
    elseif (self.type==Step.PUSH) then
        pushMatrix()
    elseif (self.type==Step.POP) then
        popMatrix()
    elseif (self.type==Step.SCALE) then
        scale(self.val)
    end
end

Zoom class

-- Zoom library v1.1
-- 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 or 0;
    self.inity=y or 0;
    self:clear()
    print("Tap and drag to move\
Pinch to zoom\
Double tap to reset zoom")
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

function Zoom:getWorldPoint(pt)
    return vec2(self.offset.x-(self.center.x- pt.x)/self.zoom,
        self.offset.y-(self.center.y- pt.y)/self.zoom)
end

function Zoom:getLocalPoint(pt)
    return vec2(pt.x*self.zoom+self.center.x- self.offset.x*self.zoom,
        pt.y*self.zoom+self.center.y- self.offset.y*self.zoom)
end

Very cool. Amazing what some simple rules can end up doing with this type of system. Wasn’t even aware of L-systems, after reading through the wikipedia article and looking at your code Ive got a lot better understanding.

Awsome

@Herwig: do you have a good source for Lindenmayer alike “functions”, means axioms and rules? I wrote my own set of functions, but slowly running out of examples …

@CrazyEd I took most of my examples from http://www.nahee.com/spanky/www/fractint/lsys/tutor.html

To update the examples first change the parameter line in the setup() with

iparameter("example",1,15,15)

and update the initExample function with


    elseif example==10 then
        return LSystem("Triangular Grid",7,"NSX",
        {
            N=Step(Step.TRANS,-300,0),
            S=Step(Step.SCALE,0.5),
            X=Step(Step.NOP),
            Y=Step(Step.NOP),
            F=Step(Step.LINE,1200),
            M=Step(Step.ROT,-60),
            P=Step(Step.ROT,60),
            U=Step(Step.PUSH),
            O=Step(Step.POP)
        },
        {
            Rule("NS","NSS"),
            Rule("X","FYUPFYOUMMFYOFY"),
            Rule("Y","FXUPPFXOUMFXOFX"),
            Rule("F","")
        }
        )
    elseif example==11 then
        return LSystem("Hexagonal",7,"NSF",
        {
            N=Step(Step.TRANS,-150,0),
            S=Step(Step.SCALE,0.5),
            F=Step(Step.LINE,600),
            G=Step(Step.TRANS,1200,0),
            M=Step(Step.ROT,-60),
            P=Step(Step.ROT,60),
            U=Step(Step.PUSH),
            O=Step(Step.POP)
        },
        {
            Rule("NS","NSS"),
            Rule("F","UMFPFOGPUPFPFOM"),
            Rule("G","GG")
        }
        )
    elseif example==12 then
        return LSystem("Terdragon",9,"NSF",
        {
            N=Step(Step.TRANS,-250,0),
            S=Step(Step.SCALE,0.577735),
            R=Step(Step.ROT,-30),
            F=Step(Step.LINE,800),
            M=Step(Step.ROT,-120),
            P=Step(Step.ROT,120)
        },
        {
            Rule("NS","NSRS"),
            Rule("F","FPFMF")
        }
        )
    elseif example==13 then
        return LSystem("Cross",6,"NSFX",
        {
            N=Step(Step.TRANS,-170,0),
            S=Step(Step.SCALE,0.425),
            R=Step(Step.ROT,-117),
            X=Step(Step.NOP),
            Y=Step(Step.NOP),
            F=Step(Step.LINE,1100),
            M=Step(Step.ROT,-90),
            P=Step(Step.ROT,90)
        },
        {
            Rule("NS","NSRS"),
            Rule("X","FXPFXPFXFYMFYM"),
            Rule("Y","PFXPFXFYMFYMFY"),
            Rule("F","")
        }
        )
    elseif example==14 then
        return LSystem("Pentigree",5,"NSFMFMFMFMF",
        {
            N=Step(Step.TRANS,-100,100),
            S=Step(Step.SCALE,0.37),
            R=Step(Step.ROT,-36),
            F=Step(Step.LINE,800),
            M=Step(Step.ROT,-72),
            P=Step(Step.ROT,72)
        },
        {
            Rule("NS","NSRS"),
            Rule("F","FMFPPFPFMFMF")
        }
        )
    elseif example==15 then
        return LSystem("XmasTree",9,"NSPPA",
        {
            N=Step(Step.TRANS,-100,-200),
            S=Step(Step.SCALE,0.6),
            R=Step(Step.ROT,0),
            F=Step(Step.LINE,800),
            A=Step(Step.NOP),
            B=Step(Step.NOP),
            C=Step(Step.NOP),
            D=Step(Step.NOP),
            E=Step(Step.NOP),
            G=Step(Step.NOP),
            M=Step(Step.ROT,-36),
            P=Step(Step.ROT,36)
        },
        {
            Rule("NS","NSRS"),
            Rule("A","MMFCPPPPFDMMFG"),
            Rule("B","FEPPFCMMMMFDPP"),
            Rule("C","PPFAMMMMFBPPFE"),
            Rule("D","FGMMFAPPPPFBMM"),
            Rule("E","PFGMMFAP"),
            Rule("G","MFBPPFEM"),
            Rule("F","")
        }
        )