Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

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

Comments

  • dave1707dave1707 Mod
    Posts: 7,737

    @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.

  • dave1707dave1707 Mod
    edited March 2016 Posts: 7,737

    @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
    
  • dave1707dave1707 Mod
    Posts: 7,737

    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
    
  • Posts: 82
    Interesting stuff, I read the wiki article. I really like the 'butterfly effect' nature of it. This would be great in combination with CodeaCraft!
  • Posts: 181

    That's pretty interesting, thanks for sharing all!

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

  • Posts: 181

    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!

  • Posts: 116

    I dont really know much in this area, but i have written something similar. Check it out:

    -- Dragon Fractal
    --0=up,1=right,2=down,3=left
    function setup()
        quant = {0}
        translate(0,5)
        pos = vec2(0,500)
        vec = vec2(0,500)
    
        strokeWidth(1)
        parameter.action("next",next)
        parameter.integer("angle",0,360,0)
        parameter.integer("scl",1,100,1)
    end
    function draw()
        strokeWidth(1/scl) 
        background(37, 41, 50, 255)
        translate(WIDTH/2,HEIGHT/2)
        rotate(angle)
        scale(scl)
        for num, val in ipairs(quant) do
            rotate(val*90)
            line(0,0,0,vec.y)
            pos = vec2(0,0) + vec 
            translate(pos.x,pos.y)
            rotate(val*-90)
        end
    end
    function next()
        for num, val in ipairs(quant) do
            n = num
        end
        for i = 1, n do
            if quant[i] == 3 then
                tempval = 0
            else
                tempval = quant[i]+1
            end
            table.insert(quant,n+1, tempval)
        end
    print(table.concat(quant))
        --print("################")
        --print("################")
        vec = vec /2 
    end
    
Sign In or Register to comment.