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