Fractal Life Engine Reference

Get the latest tarball thru here: http://sourceforge.net/projects/flea

The best fleas rendered so far: http://flea.sourceforge.net

The tutorial: http://www.rubygarden.com/ruby?FractalLifeEngine


Theory


Flea l-systems do not use the terse legacy l-system notation, where + means "right" and - means "left", etc. Any legacy l-system can express in Flea as a very narrow subset of the language Ruby, using objects as Axioms and method calls on these objects as Actions.

In theory, you can write l-systems without learning Ruby. In practice, there are just a few Ruby statements that make writing l-systems easier - statements to loop over ranges, declare scalar variables, perform mathematical operations, etc.

Those seeking deeper Ruby enlightment should start here: http://www.ruby-lang.org/en/doc.html


Globals


Support items available to all .flea scripts, but a method of no overt object.

egg = Ovum.new Our SystemMetaphor is DNA in a spore, seed, or egg. The class that contains all Axioms (as if they were nucleotide molecules) is therefor an "Ovum". Start .flea scripts by creating a "new" object from this class. As a convention we name this object an "egg" (because that's a short but complete name), but you can call it anything you like.

An Ovum begins life at the 0,0,0 location with a vector pointing up. This direction is Z internally, but the Flea console programs (fleaPOV, fleaVRML, etc.) will translate this into the back-end program's coordinate system.
egg = Ovum.new
egg.RecursionDepth = 2
egg.DefaultAngle = 90
egg.DefaultThickness = 50
ax = egg.newRootAxiom ('A')
ax.longer(8).tube
render egg

$fast Senses the command-line option +fast setting, where +fast is true and -fast is false. Use this to tune your scripts so quick thumbnails don't take forever to calculate. fleaTk sets this to true by default.
depth = if $fast then 4 else 8 end
egg = startOvum (depth, 60, 80)
root = egg.newRootAxiom ('Q')
ax = egg.newAxiom ('A')
root.longer(7).gosub(ax)

ax.shorter(0.5).tube.
   push.right.link(ax).pop.tube.
   push.right.link(ax).pop.left.link(ax)

render egg

render egg After declaring an egg and its DNA hatch it here.
egg = startOvum (6, 60, 80)
root = egg.newRootAxiom ('Q')
ax = egg.newAxiom ('A')

root.move.move.move.move.move.color(White).longer(2).
   push.link(ax).pop.right.
   push.link(ax).pop.right.
   push.link(ax).pop.right.
   push.link(ax).pop.right.
   push.link(ax).pop.right.
   push.link(ax).pop.right

ax.shorter(0.5).tube.tube.
   push.right.link(ax).pop.
   push.left.link(ax).pop.
   push.longer(2).link(ax).pop.
   push.right(180).link(ax).pop

render egg


Methods


The token "ax" is not required. It's a common variable name for Axiom, which is a list of Actions for the Turtle to execute. The "Turtle" represents a location in R3 and a vector; a direction the turtle is going. The naming system derives from "Turtle Graphics", a common system to define shapes as commands in simple languages.

Elements inside [brackets] are optional, and given ax.yo[(keyword = 0)] the "keyword =" part is apocryphal. Don't write it in a real .flea script.

The Ruby parser reads lines delimited by linefeeds, except where a line ends with a trailing operator. That's why a dot followed by a linefeed should break long lists of actions - not a linefeed followed by a dot.

Do this:

  ax.method.
    method

Not this:

  ax.method
    .method by


povray.setMaterial(idx, pigment)


The token "povray" is the object that render will use. Set idx to a number between 0 and 15, and pass in any string that POVray perceives as a pigment, texture or media.
povray.setMaterial( 1,
    'texture { T_Wood9 rotate x*45 scale 700 }' )

egg = Ovum.new
egg.RecursionDepth = 2
egg.DefaultAngle = 90
egg.DefaultThickness = 150
ax = egg.newRootAxiom ('A')
ax.longer(8).color(1).tube
render egg

ax.color[(index)] Sets the index into the table declared by setMaterial, or increments the index if none is provided. .tube, polygons and .sphere sense this setting when they render.

Currently limited to the range 0 to 15 inclusive; a future version will provide unlimited ranges.
egg = startOvum (2, 30, 100)
tubes = egg.newRootAxiom ('A')
tubes.longer(3).tube.color.tube.
    color.tube.color(12).tube
render egg

ax.decAngle[(ratio = 0.9)] Multiplies the default angle by the given ratio.

ax.down[(angle)] Pitches that flying turtle down by the DefaultAngle or a given angle. Does not move the current turtle point.
egg = startOvum (18, 30, 50)
ax = egg.newRootAxiom ('A')
r  = egg.newAxiom ('R')
l  = egg.newAxiom ('L')
u  = egg.newAxiom ('U')
d  = egg.newAxiom ('D')

ax.color(8).longer(5).
  push.color(2).gosub(d).pop

ax.push.gosub(l).pop
ax.push.gosub(u).pop
ax.push.gosub(r).pop
r.right.tube.shorter(0.6).link(r)
l.left.tube.shorter(0.6).link(l)
u.up.tube.shorter(0.6).link(u)
d.down.tube.shorter(0.6).link(d)
render egg

ax.endPolygon End recording polygon vertices. The current vertice is not recorded. The resulting "polygon" represents triangles drawn from the first vertice to each successive pair of vertices.

ax.gosub(ax) Direct the turtle to execute the actions of the target axiom. Each call to .gosub does not decrement the "depth" counter. If an axiom .gosub's to itself, directly or indirectly, an infinite loop will break ruby, and the egg.RecursionDepth will not save you.

ax.half[(percent)] calls ax.move(50) or ax.move(percent), if supplied.

ax.halfTube[(percent)] calls ax.tube(50) or ax.tube(percent), if supplied.
egg = startOvum (2, 30, 100)
ax = egg.newRootAxiom ('A')
ax.longer(4).tube.color.halfTube.color.tube
render egg

ax.hop[(percent)] Moves the turtle the current distance in the current direction. If called inside a .startPolygon / .endPolygon pair, the turtle does not record the new location as a polygon vertex.

ax.horizontal Imagine while flying an airplane you adjust the settings so that little floating ball shows a level horizon...
egg = startOvum (2, 30, 100)
ax = egg.newRootAxiom ('A')
wings = egg.newAxiom ('B')

ax.color(6).longer(2.5).right.right.
  tube.gosub(wings).left.
  tube.gosub(wings).left.
  tube.gosub(wings).left.
  tube.gosub(wings).left.
  tube.gosub(wings).left.
  tube.gosub(wings).
  horizontal.
  tube.gosub(wings)

wings.
  push.
    thinner.shorter(0.7).color(14).
    push.
      right(90).tube.
    pop.
    push.
      left(90).tube.
    pop.
  pop

render egg

ax.incAngle[(ratio = 10 / 9)] Multiplies the default angle by the given ratio.

ax.left[(angle)] Banks that flying turtle to the left by the DefaultAngle or a given angle. Does not move the current turtle point.
egg = startOvum (18, 30, 50)
ax = egg.newRootAxiom ('A')
r  = egg.newAxiom ('R')
l  = egg.newAxiom ('L')
u  = egg.newAxiom ('U')
d  = egg.newAxiom ('D')

ax.color(8).longer(5).
  push.color(2).gosub(l).pop

ax.push.gosub(r).pop
ax.push.gosub(u).pop
ax.push.gosub(d).pop
r.right.tube.shorter(0.6).link(r)
l.left.tube.shorter(0.6).link(l)
u.up.tube.shorter(0.6).link(u)
d.down.tube.shorter(0.6).link(d)
render egg

ax.link(ax) Direct the turtle to execute the actions of the target axiom. Axioms may call themselves directly or indirectly. Each call to .link decrements a "depth" counter, and each return increments it. The depth counter starts at the egg.RecursionDepth. If the current value of "depth" is zero, link will not call. Compare .gosub.
povray.setMaterial( 1,
    'texture { Copper_Metal scale 300 }' )

egg = startOvum (18, 45, 100)
ax = egg.newRootAxiom ('A')
b  = egg.newAxiom ('B')
ax.longer(6).right.color(1).gosub(b)
b.tube.shorter(0.8).left.link(b)
render egg

ax.longer[(ratio = 10 / 9)] Increments the current distance and current thickness by multiplying them by the given ratio.
egg = startOvum (2, 30, 100)
ax = egg.newRootAxiom ('A')
ax.longer(3).tube.color.
  longer.tube.color.
  longer(1.4).tube
render egg

ax.move[(percent)] Moves the turtle the current distance in the current direction. If called inside a .startPolygon / .endPolygon pair, the turtle records the new location as a polygon vertex.

ax.pop Returns the turtle to the location, color, vector & roll recorded on a LIFO stack by a previous matched call to .push.

ax.push Records the turtle's current location, color, vector & roll on a LIFO stack. The matched call to .pop will return the turtle to this location.

ax.random[(angle)] TBD

ax.right[(angle)] Banks that flying turtle to the right by the DefaultAngle or a given angle. Does not move the current turtle point.
egg = startOvum (18, 30, 50)
ax = egg.newRootAxiom ('A')
r  = egg.newAxiom ('R')
l  = egg.newAxiom ('L')
u  = egg.newAxiom ('U')
d  = egg.newAxiom ('D')

ax.color(8).longer(5).
  push.color(2).gosub(r).pop

ax.push.gosub(l).pop
ax.push.gosub(u).pop
ax.push.gosub(d).pop
r.right.tube.shorter(0.6).link(r)
l.left.tube.shorter(0.6).link(l)
u.up.tube.shorter(0.6).link(u)
d.down.tube.shorter(0.6).link(d)
render egg

ax.roll180 Calls either ax.rollRight(180) or ax.rollLeft(180).

ax.rollLeft[(angle)] Rolls the flying turtle left by the DefaultAngle or a given angle. This influences the meaning of the next .up, .right, .left or .down command.

ax.rollRight[(angle)] Rolls the flying turtle right by the DefaultAngle or a given angle. This influences the meaning of the next .up, .right, .left or .down command.

ax.set(length) Sets the current Distance setting to a non-relative value. Use this, for example, to draw fixed-size leaves on the ends of variable-size twigs.

ax.set(thickness) Sets the current thickness to a non-relative value.

ax.shorter[(ratio = 0.9)] Decrements the current distance and current thickness by multiplying them by the given ratio.

ax.sphere[(thickness)] Creates a sphere centered on the current turtle point, colored the current setMaterial color. Does not move the current turtle point. The default thickness is the same as the current .tube default thickness.
egg = startOvum (2, 30, 100)
ax = egg.newRootAxiom ('A')
ax.longer(5).tube.sphere.move(450).
    color(10).sphere(2)
render egg

#  observe how the lower sphere
#  "caps" the end of its tube

ax.startPolygon Begin recording polygon vertices. The current vertice and all vertices at the ends of .move, .tube, .vertex, .half and .halfTube commands get recorded. .endPolygon will render a polygon and clear the vertice stack.

ax.thicker[(ratio = 10 / 7)] Multiplies the current thickness by the given ratio.

ax.thinner[(ratio = 0.7)] Multiplies the current thickness by the given ratio.
egg = startOvum (2, 90, 100)
tubes = egg.newRootAxiom ('A')
tubes.color(6).right(45).longer(8).tube.left.
  thinner.tube.left.
  thinner.tube.left.
  thinner(0.5).tube
render egg

ax.tropism[(gravity = 0.2)] Warps the turtle's current vector "down" by the given "gravity" factor. This effect senses the turtle's current opinion of what "down" means. If the turtle has rolled 180, say, "down" is up.
egg = startOvum (20, 30, 50)
ax = egg.newRootAxiom ('A')
whip = egg.newAxiom ('W')

ax.push.down(8).longer(2).gosub(whip).pop.
  rollRight(360/20).color.link(ax)

whip.tube.tropism(0.4).shorter.link(whip)
render egg

ax.tube[(percent = 100)] Draws a cylinder whose length is "percent" times the current distance setting (which starts at 100 arbitrary units), plus a 5% cosmetic fudge factor. Moves the current turtle point the current distance.
egg = startOvum (1, 90, 100)
tubes = egg.newRootAxiom ('A')
tubes.longer(5).tube.tube.shorter.
    push.right.tube.pop.
    left.tube
render egg

ax.up[(angle)] Pitches that flying turtle up by the DefaultAngle or a given angle. Does not move the current turtle point.
egg = startOvum (18, 30, 50)
ax = egg.newRootAxiom ('A')
r  = egg.newAxiom ('R')
l  = egg.newAxiom ('L')
u  = egg.newAxiom ('U')
d  = egg.newAxiom ('D')

ax.color(8).longer(5).
  push.color(2).gosub(u).pop

ax.push.gosub(l).pop
ax.push.gosub(r).pop
ax.push.gosub(d).pop
r.right.tube.shorter(0.6).link(r)
l.left.tube.shorter(0.6).link(l)
u.up.tube.shorter(0.6).link(u)
d.down.tube.shorter(0.6).link(d)
render egg

ax.up180 TBD

ax.vertex Between a .startPolygon / .endPolygon pair, this records the turtle's current location as a "corner" of the polygon.