Crates.io | dcc-lsystem |
lib.rs | dcc-lsystem |
version | 0.6.3 |
source | src |
created_at | 2019-08-12 18:00:39.948177 |
updated_at | 2021-05-17 11:56:48.492168 |
description | An implementation of a Lindenmayer system together with some rendering tools |
homepage | |
repository | https://github.com/dcchut/dcc-lsystem |
max_upload_size | |
id | 156185 |
size | 107,534 |
A crate for working with Lindenmayer systems.
An L-System consists of an alphabet of symbols that can be used to make strings, a collection of production rules that expand each symbol into a larger string of symbols, an initial axiom string from which to begin construction, and a mechanism for transforming the generated strings into geometric structures.
Lindenmayer's original L-System for modelling the growth of Algae had
variables A
and B
, axiom A
, and production rules A -> AB
, B -> A
. Iterating
this system produces the following output:
A
AB
ABA
ABAAB
Put the following in your Cargo.toml
:
dcc-lsystem = "0.6"
LSystemBuilder
An L-system is represented by an instance of LSystem
. To create a barebones LSystem
,
the LSystemBuilder
struct is useful. The following example shows an implementation of
Lindenmayer's Algae system.
use dcc_lsystem::LSystemBuilder;
let mut builder = LSystemBuilder::new();
// Set up the two tokens we use for our system.
let a = builder.token("A");
let b = builder.token("B");
// Set up our axiom (i.e. initial state)
builder.axiom(vec![a]);
// Set the transformation rules
builder.transformation_rule(a, vec![a,b]); // A -> AB
builder.transformation_rule(b, vec![a]); // B -> A
// Build our LSystem, which should have initial state A
let mut system = builder.finish();
assert_eq!(system.render(), "A");
// system.step() applies our production rules a single time
system.step();
assert_eq!(system.render(), "AB");
system.step();
assert_eq!(system.render(), "ABA");
// system.step_by() applies our production rule a number of times
system.step_by(5);
assert_eq!(system.render(), "ABAABABAABAABABAABABAABAABABAABAAB");
It is possible to render an L-system into an image or gif. Typically this is done using
a turtle - each token in the L-system's state is associated with some movement or rotation
(or perhaps something more complicated) of a turtle. The TurtleLSystemBuilder
struct offers
a convenient way of constructing such renderings.
The Koch curve can be generated using an L-system with 3 symbols: F
, +
, and -
,
where F
corresponds to moving forwards, +
denotes a left rotation by 90°,
and -
denotes a right rotation by 90°. The system has axiom F
and transformation
rule F => F+F-F-F+F
. This is implemented in the following example.
use image::Rgb;
use dcc_lsystem::turtle::{TurtleLSystemBuilder, TurtleAction};
use dcc_lsystem::renderer::{ImageRendererOptions, Renderer};
let mut builder = TurtleLSystemBuilder::new();
builder
.token("F", TurtleAction::Forward(30)) // F => go forward 30 units
.token("+", TurtleAction::Rotate(90)) // + => rotate left 90°
.token("-", TurtleAction::Rotate(-90)) // - => rotate right 90°
.axiom("F")
.rule("F => F + F - F - F + F");
let (mut system, renderer) = builder.finish();
system.step_by(5); // Iterate our L-system 5 times
let options = ImageRendererOptionsBuilder::new()
.padding(10)
.thickness(4.0)
.fill_color(Rgb([255u8, 255u8, 255u8]))
.line_color(Rgb([0u8, 0u8, 100u8]))
.build();
renderer
.render(&system, &options)
.save("koch_curve.png")
.expect("Failed to save koch_curve.png");
The resulting image is shown in the Examples section below.
It is also possible to render a GIF using an L-system. The individual frames of the GIF correspond to partial renderings of the L-system's state.
use image::Rgb;
use dcc_lsystem::renderer::{Renderer, VideoRendererOptions};
use dcc_lsystem::turtle::{TurtleAction, TurtleLSystemBuilder};
fn main() {
let mut builder = TurtleLSystemBuilder::new();
builder
.token("F", TurtleAction::Forward(30))
.token("+", TurtleAction::Rotate(90))
.token("-", TurtleAction::Rotate(-90))
.axiom("F")
.rule("F => F + F - F - F + F");
let (mut system, renderer) = builder.finish();
system.step_by(5);
let options = VideoRendererOptionsBuilder::new()
.filename("koch_curve.gif")
.fps(20)
.skip_by(0)
.padding(10)
.thickness(4.0)
.fill_color(Rgb([255u8, 255u8, 255u8]))
.line_color(Rgb([0u8, 0u8, 100u8]))
.progress_bar(true)
.build();
renderer
.render(&system, &options);
}
Currently the following actions are available:
TurtleAction |
Description |
---|---|
Nothing |
The turtle does nothing. |
Rotate(i32) |
Rotate the turtle through an angle. |
Forward(i32) |
Move the turtle forwards. |
Push |
Push the turtle's current heading and location onto the stack. |
Pop |
Pop the turtle's heading and location off the stack. |
StochasticRotate(Box<dyn Distribution>) |
Rotate the turtle through an angle specified by some probability distribution. |
StochasticForward(Box<dyn Distribution>) |
Move the turtle forwards through a distance specified by some probability distribution. |
The Distribution
trait is given by:
pub trait Distribution: dyn_clone::DynClone {
fn sample(&self) -> i32;
}
A possible implementation of a Uniform distribution (using the rand
crate) is as follows:
use rand::Rng;
#[derive(Clone)]
pub struct Uniform {
lower: i32,
upper: i32,
}
impl Uniform {
pub fn new(lower: i32, upper: i32) -> Self {
Self { lower, upper }
}
}
impl Distribution for Uniform {
fn sample(&self) -> i32 {
let mut rng = rand::thread_rng();
rng.gen_range(self.lower..=self.upper)
}
}
Examples are located in dcc-lsystem/examples
.
Licensed under either of
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.