Skip to content

dwayne/elm-l-system-studio

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

103 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

L-System Studio - Live Demo

An environment for creating and exploring L-Systems.

A screenshot of L-System Studio

Table of Contents

Features

  • 30+ presets
    • Including the Koch Curve, Quadratic Gosper, and Square Sierpinski
  • Unlimited iterations
    • It uses lazy evaluation to generate, translate, and render L-Systems with billions trillions infinitely many symbols
  • 2D camera
    • Supports an infinite canvas since you can position the camera anywhere you want
    • Supports panning horizontally and vertically
    • Supports zooming in and out
  • Performant
    • Render 1,000,000 instructions per frame at 60 frames per second

Backstory

When hunorg/L-System-Studio was first submitted to Built with Elm I was excited to play with the application. However, my experience was tainted a bit because the application bogged down my browser when I tried rendering any of the interesting L-Systems for what I considered to be a very small number of iterations. Occassionally, the application would even fail completely. I also wasn't able to view the rendering properly since a 2D viewing pipeline wasn't available, i.e. world coordinates and canvavs coordinates coincided. I knew these shortcomings weren't inherent to Elm but rather to the way in which the application was built. These bad experiences prompted me to review the code and think about ways in which I could improve upon it.

I don't want performance at the expense of sanity. I want to be able to express what's in my mind in a modular way while still being able to achieve reasonable performance.

My first foray into the code led to me improving the L-System generation which I wrote about in Diary of an Elm Developer - Lazy L-System generation and Diary of an Elm Developer - Improving Rules.lookup. These initial successes drew me further into the project until I was fully consumed by it.

L-System generation was solved but I wasn't 100% certain that it would lead to improvements in translation and rendering. In order for the performance to be improved, both translation and rendering needed to be done lazily as well.

Translation involved keeping track of the turtle's state. It took me a while to figure it out but in the end I was able to keep translation lazy. Data.Translator.translate uses Lib.Sequence.filterMapWithState to map and filter lazily over a sequence while threading state.

Rendering involved dropping SVG and drawing on an HTML5 canvas instead. Due to the potential of a generated L-System to require a billion+ SVG nodes I knew I needed to switch to HTML5 canvas for output. I never used canvas before so this requirement sent me on an interesting and fruitful journey where I learned about HTML5 canvas, requestAnimationFrame, web components, the difference between JavaScript attributes and properties, the 2D viewing pipeline, and so much more. I explored a variety of questions and scenarios in separate branches before settling on my final solution.

Branch Experiment
svg-experiment Is it really true that I can't use SVG? I ran into a bug when rendering a certain number of SVG/HTML nodes.
explore-using-a-web-component Can I follow what joakin/elm-canvas did and use a web component? As I tried to increase the amount of instructions I rendered per frame I saw artifacts in the drawing which showed that instructions were being skipped on the first frame. Maybe it was a timing issue but I wasn't able to resolve it.
canvas-experiment Can I stream drawing commands to the canvas? Yes! The port solution is correct and performant. I can crank up the amount of instructions I render per frame and I see no visual anomalies.

I settled on streaming drawing instructions via ports to an HTML5 canvas.

The rest of the work wasn't specific to L-Systems so I won't go into that here.

Canvas performance

These are some of the things I currently do to improve canvas performance:

  • I batch canvas calls together
    • If frames per second (fps) = 30 and instructions per frame (ipf) = 10 then 300 instructions per second (ips) would be sent to the canvas to be drawn
    • Since I use requestAnimationFrame to coordinate the drawing it distributes that many instructions over the one second based on the time elapsed between calls to requestAnimationFrame
    • So in reality, if 0.5 seconds elapsed between calls to requestAnimationFrame then 150 instructions would be sent to the canvas over the port to be drawn as a single polyline
  • I clear the canvas using ctx.clearRect(0, 0, width, height)
  • I avoid floating point coordinates
    • World coordinates use floats but device (canvas) coordinates use integers, see Data.Transformer
  • I optimize my animations with requestAnimationFrame

Future improvements

The application is nowhere near complete but it satisfies my goals of being performant and allowing me to explore interesting L-Systems without having the browser crash on me. That said here are a few things that could be improved:

  • The generated L-System string may have multiple forward movements one after the other
    • These can be combined into one forward movement of a larger length
    • Currently 10 consecutive forward movements would lead to 10 line drawing instructions
  • When the view transformation is performed, multiple world coordinates get mapped to the same canvas coordinates in succession
    • The repeats can be removed
  • Lines entirely outside the canvas can be skipped so as to avoid sending them over the port to the canvas only to be clipped by the canvas
  • Drawing lines of even width, i.e. lineWidth is an even number, so that it looks good on the canvas
  • Add color support
  • Design a nicer UI

Interesting future work

I'm equally excited about the future work that this project generated for me as well.

  • Extract an L-System rules and generator module, see Data.Rules and Data.Generator
  • Extract a 2D camera module, see Data.Transformer
  • Extract an animation timing module for regulating work to be done within a given number of frames per second, see Lib.Timer and Data.Renderer
  • Explore using Taylor series expansion to calculate sine and cosine to arbitrary precision
  • Explore Lib.Field
    • It seems to be a promising abstraction upon which to base a form library

Usage

This project is managed with Devbox and a few Bash scripts. Enter the development environment with devbox shell.

Build

build-development # alias: b
build-production

Serve

serve-development # alias: s
serve-production

Deploy

deploy-production

Format

format # alias: f

Sanity checks

Individually:

check-scripts
test-elm # alias: t
test-elm-main
review   # alias: r

All at once:

check

Credits

About

An environment for creating and exploring L-Systems.

Topics

Resources

License

Stars

Watchers

Forks

Contributors