Turtle (Crab) Rendering

Ok, so we have a Crab that's brandishing a pen, and we've got everything initialized and configured. So how do we actually work with this thing? The Turtle API is pretty simple and straightforward. It's easy to implement each individual rendering instruction. The complexity of how multiple instructions are combined to draw interesting shapes will be left to the L-systems.

The render function is where the magic happens. This function will be invoked many times, once for each Symbol that's produced by the L-system, based on the axiom and number of iterations. The first thing we need to do is to convert the Symbol into a RendererInstruction. Then we can use pattern matching to determine what to do. This would look something like the following:


# #![allow(unused_variables)]
#fn main() {
fn render(&mut self, symbol: impl Symbol) {
    match symbol.to_rendering_instruction() {
        RendererInstruction::Forward => self.forward(),
        RendererInstruction::RotateLeft => self.rotate_left(),
        RendererInstruction::RotateRight => self.rotate_right(),
        RendererInstruction::NoOp => { /* no-op just means that the Symbol didn't map to any particular rendering instruction */ },
        /* .... remainder omitted for brevity */
    }
}
#}

Since this pattern matching isn't particularly interesting, we'll update our Renderer trait to provide the implementation on the trait itself, which will just delegate to named functions for each instruction. We'll add a function to the Renderer trait for each instruction so that the code will be more readable. So instead of implementing render directly, we'll just implemet each of the specific functions like forward, rotate_left, rotate_right, push, pop, etc. So now our Renderer trait will look more like this:


# #![allow(unused_variables)]
#fn main() {
pub trait Renderer {
    fn global_init() where Self: Sized {}

    fn render(&mut self, instruction: RendererInstruction) {
        match instruction {
            RendererInstruction::Forward => self.forward(),
            RendererInstruction::RotateLeft => self.rotate_left(),
            RendererInstruction::RotateRight => self.rotate_right(),
            RendererInstruction::Push => self.push(),
            RendererInstruction::Pop => self.pop(),
            RendererInstruction::NoOp => { /* no-op */ },
        }
    }

    fn forward(&mut self) {}
    fn push(&mut self) {}
    /* .... remainder of functions omitted for brevity */

    fn finish(&mut self) {}
}

impl Renderer for Crab {
    fn forward(&mut self) { /* move forward */ }
    fn rotate_left(&mut self) { /* ... */ }
    fn forward(&mut self) { /* ... */ }
}
#}

Forward

The Forward instruction moves the crab forward by the step, drawing a straight line along the way. Implementing this is quite simple. Just call the Turtle::forward function, passing in the current value for step.

Rotation

RotateLeft, RotateRight rotate the crab in place. "Right" means clockwise, and "left" means counter-clockwise. Keep in mind that we're using degrees instead of radians (although, this is a fairly arbitrary decision and it's also reasonable for an l-system to use radians).