Skip to content

SpaceAceMonkey/spaceace.lsystem

Repository files navigation

spaceace.lsystem

L-system interpreter written in Monkey X

Author "SpaceAce" can be contacted via the Monkey-X forums, at http://monkeycoder.co.nz/

 

NOTES

The source code is a trainwreck. The organization is byzantine. The functionality is pretty good. I like strawberries.

 

A BROAD, BROAD, BROAD OVERVIEW

I am going to assume you have some idea what L-systems are, and why they are totally cool. This project allows you to define rules for L-systems and watch the fun. In addition to "turn left/turn right," I've included additional commands, such as color cycling.

 

A BROAD, BROAD OVERVIEW

My L-system code has two main classes: LSystem, and LSystemOvoid. LSystem draws line segments, while LSystemOvoid draws unconnected ovals at what would be the vertices of the segments if you were using LSystem instead of LSystemOvoid.

Figures of the Segment and Ovoid type are made up of LSystemSegments and LSystemOvoidSegments. Each L-system holds a list of segments which are drawn each iteration (or every x iterations, see USAGE) to produce the figure generated by the L-system.

 

A BROAD OVERVIEW

In addition to creating L-systems from scratch, which consists of defining your own rules and axiom, you may also "check out" any of a number of pre-defined LSystems from the LSystemLibrary. There is currently no facility for editing, saving, or loading the L-systems library, but you can always include L-system definitions directly in the LSystemLibrary class code, and check them out for use as needed.

Rules in my L-system code are mostly arbitrary. There is no, "F means move forward," or, "R means turn right," or, "D means draw." Rather, you define rules as you like.

Exceptions to the above are:

"!": Reverses the current angle of movement
"[": Pushes the state of the current segment (x, y, angle, and rgb)
"(": Same as above
"]": Pops and restores the state stored by [ or (
")": Pops and restores only the color values of the last saved state

Execution of L-system rules, and drawing of the resulting figures, is handled by the LSystemGenerator class, which I apparently left in the main program file instead of moving it to its own file. You may create and execute an arbitrary number of L-systems by loading them all into the generator. Each L-system in the generator can be processed each frame by calling each curve's Iterate() method.

 

USAGE

' Here is some code for creating the famous Dragon curve using SpaceAce's L-system library
system:LSystem = New LSystem()
system.Create()
' Create(startx#, starty#, startAngle#, segmentLength#, iterations%)
' startx and starty are, duh, the coordinates where the L-system generation starts
' startAngle is the direction of movement the pen is facing when the system generation starts
' segmentLength is the distance the pen moves when encountering a move command.
' iterations is how many iterations, or loops, or the L-system will be executed
system.Create(DeviceWidth() / 3.75, DeviceHeight() / 1.75)
' Translation rules tell the system to "replace this symbol with this/these symbol(s)
' In the examples below, "a" is replaced with "a+b+", and "b" is replaced with "-a-b"
system.AddTranslationRule("a", "a+b+")
system.AddTranslationRule("b", "-a-b")
' DrawRules determine whether a segment is drawn when moving the pen. In this case,
' both "a" and "b" draw the segment they create when they are encountered in the ruleset.
system.AddDrawRule("a", True)
system.AddDrawRule("b", True)
' Turn rules tell the system to turn the pen by some number of degrees when a given symbol is encountered
system.AddTurnRule("+", -90.0)
system.AddTurnRule("-", 90.0)
' Color rules tell the system to manipulate the current color when certain symbols are encountered. The array
' given as the second argument contains RGB adjustments. In this case, encountering the symbol "a" in the
' ruleset caused 1.0 to be added to the current red value. Negative numbers are also (probably - I
' haven't looked at the code in close to a year) supported
' Note that the ColorRule shares a symbol with one of the DrawRules. This means that both things will happen
' when this symbol is encountered in the ruleset.
system.AddColorRule("a",[1.0, 0.0, 0.0])
' rgbMaxes limit how high the RGB values are allowed to go before wrapping back around.
' There is also an rgbMins member of the LSystem class.
system.rgbMaxes =[255.0, 255.0, 255.0]
' The Axiom is the starting ruleset. In this case, it is a single letter, which, on the first iteration, will
' be replaced by "a+b+" per our transation rules. The Axiom need not be a single character; it can be anything
' you want it to be, even an astronaut, President of the United States.
system.Axiom = "a"
' Start off with pen lines facing to the right
system.angle = -90
system.segmentLength = 4.4
' How many iterations of the curve to run. This will override anything you specified in Create()
' Be careful, because this can get big FAST.
system.iterations = 13
' I'm pretty sure I had a reason when I created this method. If you figure out what it was, please tell me.
' Oh, wait! I think I know. I think this is to push the current angle, color, and whatnot onto a stack for
' later popping.
system.StorestartingSegment()

' The above is what you will get by using
curve = generator.library.CheckOut(curve, "dragon curve")
' with no modifications. Once you have checked out a curve from the library, or constructed it from scratch as
' above, you can then make changes to it. For example, you can cause the curve to center itself on the screen:
curve.PCtr()
' Calling PCtr calculates the entire curve in order to determine its bounds. It's the drawing that is the most
' time-consuming, so this calculation is usually swift, but if you have some sort of ridiculous mega-curve, I
' suppose the orientation commands, like PCtr(), could end up taking significant time.
' Add the curve to the generator. The generator is an LSystemGenerator object you've created ahead of time.
generator.AddCurve(curve)

' Now, let's check out a curve from the library and modify it to suit our desires.
' You can call New LSystemOvoid() here to create a curve made of ovoid points rather
' than line segments.
curve = New LSystem()
' Check out the 'non-intersecting Sierpinksi gasket' from the library of available curves
curve = generator.library.CheckOut(curve, "non-intersecting Sierpinski gasket")
' Chunk-size? What's that? Well, Drawing can become very time-consuming when dealing with large numbers of segments.
' Chunking allows you to draw the curve x segments at a time, instead of drawing each individual segment one each
' frame. This is essentially "batching" the draws.
' A chunkSize of 0 means, "Draw me as fast as you can."
curve.chunkSize = 12
curve.segmentLength = DeviceHeight() / 160.0
curve.Angle = -45.0
' Start this curve in the top-left corner of the drawing area.
curve.PTop().PLft()
curve.rgbMaxes =[192.0, 255.0, 255.0]
curve.rgbMins =[96.0, 128.0, 128.0]
generator.AddCurve(curve)
' If this curve is an ovoid curve, set the radii of the ovals to be drawn. This is not necessary, but it makes it
' convenient to experiment by changing the curve type from LSystem with LSystemOvoid without having to create a new
' curve definition. Instead, you just change the New() call, above.
If (LSystemOvoid(curve))
LSystemOvoid(curve).radX = 2.0
LSystemOvoid(curve).radY = 2.0
EndIf
generator.AddCurve(curve)

' The LSystemGenerator class can be anything you want it to be. I should probably have created an interface or a base
' class to be extended, but I didn't, so here we are. In my code, I extended LSystemGenerator from App, so I just use
' OnUpdate and OnRender, like this:
Method OnUpdate:Int()
For Local system:LSystem = EachIn(curves)
system.Iterate()
Next
Return 1
End Method
Method OnRender:Int()
Cls(238, 238, 238)
For Local system:LSystem = EachIn(curves)
system.Draw()
Next
Return 0
End Method

There are tons of comments and examples in the code. I can't promise that they will do you any good, but they do exist.

 

FINAL THOUGHTS

Improvements to this code are highly encouraged. If someone wants to turn this into a less-awful mess of code, that would be just swell. Please let me know if you do so. If you'd like to update the code and contribute to this repository, that would also be swell.

Some ideas to make this awesomer:
Fix my crummy PFit() and other positioning code, which I don't think work quite right. I'm almost sure PFit() doesn't, but I can't remember. Maybe I fixed it. Have a look for me.
Make it so that chunkSize is adjusted automatically to meet a target number of draw calls, or some sort of constant speed.
Support partial iterations. One iteration to the next can be a HUGE number of segments. Sometimes you want something in-between.
Design your own L-systems, and share them on the Monkey X forums.

If you use anything from this repository, I'd like to hear about it. Please contact me on the Monkey X forums to let me know and show me your work.

About

L-system generator written in Monkey X

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages