dcc_lsystem/
lib.rs

1/*!
2# dcc-lsystem
3
4A crate for working with [Lindenmayer systems](https://en.wikipedia.org/wiki/L-system).
5
6## Background
7
8An L-System consists of an alphabet of symbols that can be used to make strings,
9a collection of production rules that expand each symbol into a larger string of symbols,
10an initial axiom string from which to begin construction, and a mechanism for transforming
11the generated strings into geometric structures.
12
13## Algae example
14Lindenmayer's original L-System for modelling the growth of Algae had
15variables `A` and `B`, axiom `A`, and production rules `A -> AB`, `B -> A`.  Iterating
16this system produces the following output:
17
180. `A`
191. `AB`
202. `ABA`
213. `ABAAB`
22
23## Basic usage
24
25Put the following in your `Cargo.toml`:
26
27```toml
28dcc-lsystem = "0.6"
29```
30
31### [`LSystemBuilder`]
32
33An L-system is represented by an instance of [`LSystem`].  To create a barebones [`LSystem`],
34the [`LSystemBuilder`] struct is useful.  The following example shows an implementation of
35Lindenmayer's Algae system.
36
37```rust
38use dcc_lsystem::LSystemBuilder;
39
40let mut builder = LSystemBuilder::new();
41
42// Set up the two tokens we use for our system.
43let a = builder.token("A");
44let b = builder.token("B");
45
46// Set up our axiom (i.e. initial state)
47builder.axiom(vec![a]);
48
49// Set the transformation rules
50builder.transformation_rule(a, vec![a,b]); // A -> AB
51builder.transformation_rule(b, vec![a]);   // B -> A
52
53// Build our LSystem, which should have initial state A
54let mut system = builder.finish();
55assert_eq!(system.render(), "A");
56
57// system.step() applies our production rules a single time
58system.step();
59assert_eq!(system.render(), "AB");
60
61system.step();
62assert_eq!(system.render(), "ABA");
63
64// system.step_by() applies our production rule a number of times
65system.step_by(5);
66assert_eq!(system.render(), "ABAABABAABAABABAABABAABAABABAABAAB");
67```
68## Rendering L-systems
69
70It is possible to render an L-system into an image or gif.  Typically this is done using
71a turtle - each token in the L-system's state is associated with some movement or rotation
72(or perhaps something more complicated) of a turtle.  The [`TurtleLSystemBuilder`] struct offers
73a convenient way of constructing such renderings.
74
75### Images
76
77The Koch curve can be generated using an L-system with 3 symbols: `F`, `+`, and `-`,
78where `F` corresponds to moving forwards, `+` denotes a left rotation by 90°,
79and `-` denotes a right rotation by 90°. The system has axiom `F` and transformation
80rule `F => F+F-F-F+F`. This is implemented in the following example.
81
82```rust,no_run
83use image::Rgb;
84
85use dcc_lsystem::turtle::{TurtleLSystemBuilder, TurtleAction};
86use dcc_lsystem::renderer::{ImageRendererOptionsBuilder, Renderer};
87
88let mut builder = TurtleLSystemBuilder::new();
89
90builder
91    .token("F", TurtleAction::Forward(30)) // F => go forward 30 units
92    .token("+", TurtleAction::Rotate(90))  // + => rotate left 90°
93    .token("-", TurtleAction::Rotate(-90)) // - => rotate right 90°
94    .axiom("F")
95    .rule("F => F + F - F - F + F");
96
97let (mut system, renderer) = builder.finish();
98system.step_by(5); // Iterate our L-system 5 times
99
100let options = ImageRendererOptionsBuilder::new()
101    .padding(10)
102    .thickness(4.0)
103    .fill_color(Rgb([255u8, 255u8, 255u8]))
104    .line_color(Rgb([0u8, 0u8, 100u8]))
105    .build();
106
107renderer
108    .render(&system, &options)
109    .save("koch_curve.png")
110    .expect("Failed to save koch_curve.png");
111```
112
113The resulting image is shown in the Examples section below.
114
115### GIFs
116
117It is also possible to render a GIF using an L-system.  The individual frames
118of the GIF correspond to partial renderings of the L-system's state.
119
120```rust,no_run
121use image::Rgb;
122
123use dcc_lsystem::renderer::{Renderer, VideoRendererOptionsBuilder};
124use dcc_lsystem::turtle::{TurtleAction, TurtleLSystemBuilder};
125
126let mut builder = TurtleLSystemBuilder::new();
127
128builder
129    .token("F", TurtleAction::Forward(30))
130    .token("+", TurtleAction::Rotate(90))
131    .token("-", TurtleAction::Rotate(-90))
132    .axiom("F")
133    .rule("F => F + F - F - F + F");
134
135let (mut system, renderer) = builder.finish();
136system.step_by(5);
137
138let options = VideoRendererOptionsBuilder::new()
139    .filename("koch_curve.gif")
140    .fps(20)
141    .skip_by(0)
142    .padding(10)
143    .thickness(4.0)
144    .fill_color(Rgb([255u8, 255u8, 255u8]))
145    .line_color(Rgb([0u8, 0u8, 100u8]))
146    .progress_bar(true)
147    .build();
148
149renderer
150    .render(&system, &options);
151```
152
153### Turtle actions
154
155Currently the following actions are available:
156
157| [`TurtleAction`]                             | Description                                                                             |
158|--------------------------------------------|-----------------------------------------------------------------------------------------|
159| `Nothing`                                  | The turtle does nothing.                                                                |
160| `Rotate(i32)`                              | Rotate the turtle through an angle.                                                     |
161| `Forward(i32)`                             | Move the turtle forwards.                                                               |
162| `Push`                                     | Push the turtle's current heading and location onto the stack.                          |
163| `Pop`                                      | Pop the turtle's heading and location off the stack.                                    |
164| `StochasticRotate(Box<dyn Distribution>)`  | Rotate the turtle through an angle specified by some probability distribution.          |
165| `StochasticForward(Box<dyn Distribution>)` | Move the turtle forwards through a distance specified by some probability distribution. |
166
167The [`Distribution`] trait is given by:
168
169```rust
170pub trait Distribution: dyn_clone:: DynClone {
171    fn sample(&self) -> i32;
172}
173```
174
175The [`Uniform`] distribution (using the `rand` crate) is implemented as follows:
176
177```rust
178# pub trait Distribution: dyn_clone::DynClone {
179#     fn sample(&self) -> i32;
180# }
181use rand::Rng;
182
183#[derive(Clone)]
184pub struct Uniform {
185    lower: i32,
186    upper: i32,
187}
188
189impl Uniform {
190    pub fn new(lower: i32, upper: i32) -> Self {
191        Self { lower, upper }
192    }
193}
194
195impl Distribution for Uniform {
196    fn sample(&self) -> i32 {
197        let mut rng = rand::thread_rng();
198        rng.gen_range(self.lower..=self.upper)
199    }
200}
201```
202
203## Examples
204
205Examples are located in `dcc-lsystem/examples`.
206
207#### Sierpinski Arrowhead
208
209![Sierpinski Arrowhead](https://user-images.githubusercontent.com/266585/62997521-73583380-be1d-11e9-8451-5ebf32216550.png)
210
211#### Koch curve
212
213![Koch curve](https://user-images.githubusercontent.com/266585/62997274-90403700-be1c-11e9-9f80-80968e265a8f.png)
214
215#### Dragon curve
216
217![Dragon curve](https://user-images.githubusercontent.com/266585/62997357-d5646900-be1c-11e9-8c24-c7da5958ef48.png)
218
219#### Fractal plant
220
221![Fractal plant](https://user-images.githubusercontent.com/266585/62997436-21afa900-be1d-11e9-8222-dfdc2ef18b72.png)
222
223### License
224
225Licensed under either of
226
227 * Apache License, Version 2.0
228   ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
229 * MIT license
230   ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
231
232at your option.
233
234### Contribution
235
236Unless you explicitly state otherwise, any contribution intentionally submitted
237for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
238dual licensed as above, without any additional terms or conditions.
239
240[`Lsystem`]: system/struct.LSystem.html
241[`LSystemBuilder`]: builder/struct.LSystemBuilder.html
242[`TurtleLSystemBuilder`]: turtle/struct.TurtleLSystemBuilder.html
243[`Distribution`]: turtle/struct.Distribution.html
244[`Uniform`]: turtle/struct.Uniform.html
245[`TurtleAction`]: turtle/enum.TurtleAction.html
246*/
247
248extern crate self as dcc_lsystem;
249
250pub use arena::{Arena, ArenaId};
251pub use builder::LSystemBuilder;
252pub use system::LSystem;
253
254pub mod arena;
255pub mod builder;
256#[cfg(feature = "image_renderer")]
257pub mod image;
258#[cfg(feature = "image_renderer")]
259pub mod image_renderer;
260pub mod renderer;
261pub mod system;
262pub mod token;
263pub mod turtle;
264
265#[cfg(test)]
266mod tests;