https://vsekar.me/blog/log_coffee/chapter_0.html
1. log_coffee!
2.
1. Sweet Jesus, Pooh! That's Not Honey! - You're Eating
Recursion!
2. Why u so mean?
3. Lindenmayer systems
3. Archive
4.
1. Visualization
2. Fractals
3. The center of the Universe is not a black hole. Its the
opposite
4. Choosing a good hash function
5. How I downloaded Big Bang Theory Season 9 in my unstable
network
5. Template
6. Keys
* Light
* Rust
* Coal
* Navy
* Ayu (default)
* Latte
* Frappe
* Macchiato
* Mocha
log_coffee!
[ ]
Lindenmayer systems
A Lindemayer system or LSystem for short consists of an alphabet of
symbols that can be used to make strings, a collection of production
that expand each symbol into some larger string of symbols, an
initial "axiom" string from which to begin construction, and a
mechanism for translating the generated strings into geometric
structures.
A few years back, I built a rudimentary website to extrapolate a few
known LSystems in pure JS. However, presently I have been getting
more comfortable with Rust and explored rebuilding it with Rust and
wasm-bindgen to get better performance for tougher iterations.
Website - vsekar.me/LSystems/
Repository - github.com/Spockuto/LSystems/
Now let's explore how an axiom, a set of rules, and an angle can be
developed into this beautiful fern Barnsley Fern
Step 0 - Define the LSystem
For Barnsley Fern, the LSystem is defined as follows
Axiom : X
Rules :
- X -> F-[[X]+X]+F[+FX]-X
- F -> FF
Angle : 22.5
This translates into the following for our Rust implementation.
#![allow(unused)]
fn main() {
lazy_static! {
static ref BARNSLEY_FERN: LSystem = LSystem {
variables: "XF",
axiom: "X",
rules: vec![
('X', "F-[[X]+X]+F[+FX]-X"),
('F', "FF"),
]
.into_iter()
.collect(),
angle: 22.5,
max_rounds: 8,
};
}
}
* variables : These are the identifiers that will be considered
during building the sequence. Others will be skipped.
* axiom : The initial sequence
* rules : Expansion rules for each identifier
* angle : Explained later
* max_rounds - Implementation detail. Maximum number of supported
iterations before running out of heap memory.
Step 1 - Generate the sequence
Once the LSystem is defined, we can build the drawing sequence.
Iterations define the number of rounds the sequence will be expanded
using the given rules. The higher the iteration, the better defined
the picture is.
#![allow(unused)]
fn main() {
/// Generate the Turtle graphics sequence for given iterations
pub fn expand(&self, iterations: u32) -> String {
// panic if given iterations are greater than the accepted limit.
if iterations > self.0.max_rounds {
panic!("Max limit reached");
}
let mut sequence = String::new();
for i in 0..iterations {
if i == 0 {
// First iteration is the axiom
sequence.insert_str(0, self.0.axiom);
} else {
let sequence_copy = sequence.to_string();
let mut insert_index = 0;
for identifier in sequence_copy.chars() {
// Skip if the variable in sequence doesn't have a rule
if !self.0.variables.contains(identifier) {
insert_index += 1;
continue;
}
// Expand the sequence based on the rule
let rule = self.0.rules.get(&identifier).unwrap();
sequence.remove(insert_index);
sequence.insert_str(insert_index, rule);
insert_index += &rule.len();
}
}
// The current sequence will be used as the generator for the next round
}
sequence
}
}
Note: The function above can also be written recursively but for
larger iterations, you would run out of stack depth way before
running out of heap memory.
For Barnsley Fern, the sequence expands as follows for subsequent
iterations
Iterations Sequence
1 X
2 F-[[X]+X]+F[+FX]-X
3 FF-[[F-[[X]+X]+F[+FX]-X]+F-[[X]+X]+F[+FX]-X]+FF[+FFF-[[X]
+X]+F[+FX]-X]-F-[[X]+X]+F[+FX]-X
FFFF-[[FF-[[F-[[X]+X]+F[+FX]-X]+F-[[X]+X]+F[+FX]-X]+FF
[+FFF-[[X]+X]+F[+FX]-X]-F-[[X]+X]+F[+FX]-X]+FF-[[F-[[X]+X]
+F[+FX]-X]+F-[[X]+X]+F[+FX]-X]+FF[+FFF-[[X]+X]+F[+FX]-X]
4 -F-[[X]+X]+F[+FX]-X]+FFFF[+FFFFFF-[[F-[[X]+X]+F[+FX]-X]+F-
[[X]+X]+F[+FX]-X]+FF[+FFF-[[X]+X]+F[+FX]-X]-F-[[X]+X]+F
[+FX]-X]-FF-[[F-[[X]+X]+F[+FX]-X]+F-[[X]+X]+F[+FX]-X]+FF
[+FFF-[[X]+X]+F[+FX]-X]-F-[[X]+X]+F[+FX]-X
Step 2 - Draw the canvas based on the sequence
Now that the sequence is generated, we can start building the canvas.
Each character in the sequence defines a particular set of operations
on the canvas. This allows us to convert the sequence into a picture.
#![allow(unused)]
fn main() {
// Define length
// Set the angle to the LSystem angle
let angle_rad = -1.0 * PI * angle / 180.0;
let (mut x, mut y) = (0.0, 0.0);
let mut stack = vec![];
for seq in sequence.chars() {
// perform operations on the canvas
match seq {
// draw a line to (x,y) at given length and angle
'F' | 'A' | 'B' => {
x += length * angle.cos();
y += length * angle.sin();
context.line_to(x, y);
context.stroke();
}
'S' => {
// move along a line to (x,y) at given length and angle
x += length * angle.cos();
y += length * angle.sin();
context.move_to(x, y);
}
'+' => {
// rotate counterclockwise by angle_rad
angle += angle_rad;
}
'-' => {
// rotate clockwise by angle rad
angle -= angle_rad;
}
'[' => {
// push a point into stack
stack.push(Line { x, y, angle });
}
']' => {
// Pop a point from the stack and move to it.
let line = stack.pop().unwrap();
(x, y, angle) = (line.x, line.y, line.angle);
context.move_to(x, y);
}
// For others, skip and continue
_ => continue,
}
}
}
Iterations Barnsley Fern
1 None
2 Barnsley Fern 2
3 Barnsley Fern 3
4 Barnsley Fern 4
5 Barnsley Fern 5
... ...
8 Barnsley Fern 8
Step 3 - Color the canvas using a linear gradient
The final step in generating our fractal is applying a linear
gradient between two colors over our canvas. This can be achieved by
applying color interpolation over each dark pixel.
#![allow(unused)]
fn main() {
let image_data = context
.get_image_data(0.0, 0.0, width as f64, height as f64)
.unwrap();
// data contains 4 u8 values per pixel indicating RGBA (red, green, blue, alpha) values
let mut data = image_data.data();
// Set the linear gradient colors : color1 -> color2
let c1 = Rgb::from_hex_str(&color1).unwrap();
let c2 = Rgb::from_hex_str(&color2).unwrap();
// linear gradient is defined using interpolation
// Assume the fraction grows from 0 to 1
// pixel.r = c1.r + (c2.r - c1.r) * fraction
// pixel.g = c1.g + (c2.g - c1.g) * fraction
// pixel.b = c1.b + (c2.b - c1.b) * fraction
for index in 0..(width * height * 4) as usize {
// Since the canvas is drawn with black, only alpha will be set initially
if data[index] > 0 {
// pixel's alpha is set
// Update the color using interpolation
let fraction = index as f32 / (height * width * 4) as f32;
data[index - 3] = (c1.get_red() + (c2.get_red() - c1.get_red()) * fraction) as u8;
data[index - 2] = (c1.get_green() + (c2.get_green() - c1.get_green()) * fraction) as u8;
data[index - 1] = (c1.get_blue() + (c2.get_blue() - c1.get_blue()) * fraction) as u8;
}
}
let slice_data = Clamped(&data.0[..]);
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(
slice_data,
width as u32,
height as u32,
)
.unwrap();
// load the data back into the canvas
context.put_image_data(&image_data, 0.0, 0.0).unwrap();
}
With this, a linear gradient between #C6EA8D and #FE90AF gives us
Barnsley Fern
Note: Colors don't exactly behave well with linear interpolation.
For more details check out The Secrets of Colour Interpolation
Fractal Tree
Axiom : F
Rules :
- F -> FF-[-F+F+F]+[+F-F-F]
Angle : 22.5
Fractal Tree
Fractal Tree 2
Axiom : VZFFF
Rules :
- F -> F
- V -> [+++W][---W]YV
- W -> +X[-W]Z
- X -> -W[+X]Z
- Y -> YZ
- Z -> [-FFF][+FFF]F
Angle : 18
Fractal Tree 2
Dragon Curve
Axiom : FX
Rules :
- X -> X+YF+
- Y -> -FX-Y
Angle : 90
Dragon Curve
32 Segment Curve
Axiom : F+F+F+F
Rules :
- F -> -F+F-F-F+F+FF-F+F+FF+F-F-FF+FF-FF+F+F-FF-F-F+FF-F-F+F+F-F+
Angle : 90
32 Segment Curve
Peano Gosper Curve
Axiom : A
Rules :
- A -> A-B--B+A++AA+B-
- B -> +A-BB--B-A++A+B
Angle : 60
Peano Gosper Curve
Koch Snowflake
Axiom : F++F++F
Rules :
- F -> F-F++F-F
Angle : 60
Koch Snowflake
Koch Snowflake 2
Axiom : F+F+F+F
Rules :
- F -> F-F+F+F-F
Angle : 60
Koch Snowflake 2
Koch Snowflake 3
Axiom : F
Rules :
- F -> F-F+F+F-F
Angle : 85
Koch Snowflake 3
Quadratic Koch Island
Axiom : F+F+F+F
Rules :
- F -> F+F-F-FF+F+F-F
Angle : 90
quad. Koch Island
Quadratic Koch Island 2
Axiom : F+F+F+F
Rules :
- F -> F+FF-FF-F-F+F+FF-F-F+F+FF+FF-F
Angle : 90
quad. Koch Island 2
Islands
Axiom : F+F+F+F
Rules :
- F -> F-SFF+F+FF+F-S-FFF-F+F+F-FFFF
- S -> SSSSSSSS
Angle : 90
Islands
Islands 2
Axiom : F+F+F+F
Rules :
- F -> F-SF+FF+F+FF-S-FF+SF-FF-F-FF+S+FFF
- S -> SSSSSS
Angle : 90
Islands 2
Sierpinski Triangle
Axiom : FXF--FF--FF
Rules :
- F -> FF
- X -> --FXF++FXF++FXF--
Angle : 60
Sierpinski Triangle
Sierpinski Square
Axiom : F+F+F+F
Rules :
- F -> FF+F+F+F+FF
Angle : 90
Sierpinski Square
Hilbert Curve
Axiom : X
Rules :
- X -> +YF-XFX-FY+
- Y -> -XF+YFY+FX-
Angle : 90
Hilbert Curve
Frec Fractal
Axiom : XYXYXYX+XYXYXYX+XYXYXYX+XYXYXYX
Rules :
- F ->
- X -> FX+FX+FXFY-FY-
- Y -> +FX+FXFY-FY-FY
Angle : 90
Frec Fractal