L-System Generator

Hey all,
Today I was reading about the upcoming game No Man’s Sky and its procedural generation techniques, and I basically read up on every one. I was intruiged by L-Systems, so I decided to first make my own “turtle” graphics drawing thing(which basically generates one continuous line as in the Logo programming language I believe). Then, I coded my own l-system interpreter. If you don’t know what an L-System is, here is the wikipedia page: https://en.m.wikipedia.org/wiki/L-system
The code is quite neat for me, but still a bit hard to understand but its also really hard to explain. The wikipedia page should explain some of it, but not my thought process but here’s the code anyway(I have it pre-loaded with the Sierpinski’s Triangle Fractal but it can generate any of the ones on the wikipedia page and more with a bit of fiddling):

--# lSystem
lSystem = class()

function lSystem:init(var,const,axiom,rules,n,turtle,funcs)
    -- you can accept and set parameters here
    self.vars = var
    self.consts = const
    self.cur = axiom
    self.finds = {}
    self.replaces = {}
    self.funcs = funcs
    for i,ii in string.gmatch(rules, "(["..self.vars.."]+)>(["..self.vars..self.consts.."]+)") do
        print(i,ii)
        table.insert(self.finds,i)
        table.insert(self.replaces,ii)
    end
    local count = 0
    
    while count < n do
        local newString = ""
        for i=1,string.len(self.cur),1 do
            local replaced = false
            for a,b in pairs(self.finds) do
                if string.sub(self.cur,i,i) == b and replaced == false then
                    newString = newString .. self.replaces[a]
                    replaced = true
                    break
                else 
                    
                end
            end
            if replaced == false then
                newString = newString .. string.sub(self.cur,i,i)
            end
        end
        self.cur = newString
        count = count + 1
    end
    print(self.cur)
    self:interpret(self.cur)
end

function lSystem:interpret(s)
    -- Codea does not automatically call this method
    for i=1,string.len(s),1 do
        for a,v in pairs(self.funcs) do
            if string.sub(s,i,i) == v.char then
                v.func()
            end
        end
    end
end

function lSystem:touched(touch)
    -- Codea does not automatically call this method
end

--# Main
-- Turtle Graphics

-- Use this function to perform your initial setup
function setup()
    print("Hello World!")
    t = Turtle(0,0)
    t:rotate(0)
    local funcs = {
        {char = "a",func = function() t:moveForward(10) end},
        {char = "b",func = function() t:moveForward(10) end},
        {char = "+",func = function() t:rotate(60) end},
        {char = "-",func = function() t:rotate(-60) end},
    }
    l = lSystem("ab","+-","a","a>+b-a-b+;b>-a+b+a-",8,t,funcs)
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(255, 255, 255, 255)

    -- Do your drawing here
    scale(1/3)
    t:draw()
end

function replaceString(s,i,p)
    return string.sub(s,1,i-1) .. p .. string.sub(s,i+1)
end

--# Turtle
Turtle = class()

function Turtle:init(x,y)
    -- you can accept and set parameters here
    self.pos = vec2(x,y)
    self.stack = {}
    self.rot = 0
    self.lines = {}
end

function Turtle:draw()
    -- Codea does not automatically call this method
    stroke(0)
    for i,v in pairs(self.lines) do
        strokeWidth(5)
        line(v.p1.x,v.p1.y,v.p2.x,v.p2.y)
    end
end

function Turtle:move(vec)
    local opos = self.pos
    local newpos = self.pos + vec
    self.pos = self.pos + vec
    self:addLine(opos,newpos)
end

function Turtle:moveForward(amount)
    local opos = self.pos
    local newpos = self.pos + vec2(amount,0):rotate(math.rad(self.rot)) 
    self.pos = self.pos + vec2(amount,0):rotate(math.rad(self.rot))
    self:addLine(opos,newpos)
end

function Turtle:rotate(amount)
    self.rot = self.rot + amount 
end

function Turtle:push()
    table.insert(self.stack, {pos = vec2(self.pos.x,self.pos.y),rot = self.rot})
end

function Turtle:pop()
    self.pos = self.stack[#self.stack].pos
    self.rot = self.stack[#self.stack].rot
    table.remove(self.stack,#self.stack)
end

function Turtle:addLine(pos1,pos2)
    self.lines[#self.lines+1] = {p1 = pos1,p2 = pos2}
end

@TheSolderKing Nice program. I haven’t looked thru the code yet, but I was wondering what it would take to add a parameter to vary the draw speed. It might be interesting to watch it drawing the triangle.

@TheSolderKing I didn’t know what an L-System was, so I went to the link you showed. I found it very interesting. I was still determined to show the Sierpinski Triangle as it was created, but I didn’t want to try and figure out your code. I wrote my own version for the triangle. I was able to show it as it was being created, but I didn’t like the way it was do it. The drawing was jumping around because of the translates, so I removed that part of the code. But anyways, here’s my code which draws the triangle when complete. I’m still determined to have it draw the triangle as its being created.

displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)

function setup()
    size=4
    size1=size
    tab={a="+b-a-b+",b="-a+b+a-"}
    str="a"
    for t=1,8 do
        str1=""
        for z=1,#str do
            v=string.sub(str,z,z)
            t=tab[tostring(v)]
            if t==nil then
                str1=str1..v
            else
                str1=str1..t 
            end     
        end
        str=str1
    end
    stroke(255)
    strokeWidth(2)
end

function draw()
    background(0)
    translate(100,40)
    scale(.8)
    for z=1,#str do
        v=string.sub(str,z,z)
        if v=="a" or v=="b" then
            size=size1
            line(0,0,size,0)
        else
            translate(size,0)    
            size=0       
            if v=="+" then
                rotate(60)
            end
            if v=="-" then
                rotate(-60)
            end 
        end 
    end
end

Here’s a version that shows the Sierpinski Triangle being created. You can display the parameter window to change the speed as it’s running.

displayMode(FULLSCREEN)

function setup()
    parameter.integer("speed",1,20,20)
    size=3
    size1=size
    tab={a="+b-a-b+",b="-a+b+a-"}
    str="a"
    for t=1,8 do
        str1=""
        for z=1,#str do
            v=string.sub(str,z,z)
            t=tab[tostring(v)]
            if t==nil then
                str1=str1..v
            else
                str1=str1..t 
            end     
        end
        str=str1
    end
    stroke(255)
    strokeWidth(2)
    c=0
end

function draw()
    background(0)
    if c<#str then
        c=c+speed*2
    end
    translate(150,80)
    for z=1,c do
        v=string.sub(str,z,z)
        if v=="a" or v=="b" then
            size=size1
            line(0,0,size,0)
        else
            translate(size,0)
            size=0
            if v=="+" then
                rotate(60)
            elseif v=="-" then
                rotate(-60)
            end
        end
    end
end

Interesting stuff, I read the wiki article. I really like the ‘butterfly effect’ nature of it. This would be great in combination with CodeaCraft!

That’s pretty interesting, thanks for sharing all!

@TheSolderKing
Could you link to the article about the no mans sky techniques?

https://en.m.wikipedia.org/wiki/No_Man's_Sky
lol just the wikipedia page.
But also:
http://venturebeat.com/2015/06/21/how-no-mans-sky-creator-is-using-clever-tech-to-build-a-truly-indie-game-universe/

Ah I should’ve checked wikipedia myself, many thanks!

I wrote a couple of procedural plant/tree/bush functions that I’d like to improve with this. Very cool technique!