random_fractal_generator/
random_fractal_generator.rs

1#![allow(clippy::clone_double_ref)]
2
3use image::Rgb;
4use rand::seq::SliceRandom;
5use rand::{thread_rng, Rng};
6
7use dcc_lsystem::renderer::ImageRendererOptionsBuilder;
8use dcc_lsystem::renderer::Renderer;
9use dcc_lsystem::turtle::{TurtleAction, TurtleLSystemBuilder};
10
11fn valid_rule(rule: &[&str]) -> bool {
12    if rule.is_empty() {
13        return false;
14    }
15
16    let r = rule.join("");
17
18    if r.contains("+-") || !r.contains('+') {
19        return false;
20    }
21
22    let mut level = 0;
23
24    for c in rule {
25        if c == &"+" {
26            level += 1;
27        } else if c == &"-" {
28            if level == 0 {
29                return false;
30            }
31            level -= 1;
32        }
33    }
34
35    level == 0
36}
37
38pub fn main() {
39    'processing: loop {
40        // generate random axiom out of L, R, F, X, Y
41        // and random rules for X, Y
42        let mut rng = thread_rng();
43
44        // generate a random axiom
45        let axiom_length = rng.gen_range(0..=2);
46        let mut axiom = vec!["X"];
47        let choices = ["L", "R", "F", "X", "Y"];
48        let weighted_choices = [
49            ("F", rng.gen_range(1..=8)),
50            ("X", rng.gen_range(2..=4)),
51            ("Y", rng.gen_range(2..=4)),
52            ("L", rng.gen_range(2..=6)),
53            ("R", rng.gen_range(2..=6)),
54            ("+", rng.gen_range(4..=8)),
55            ("-", rng.gen_range(4..=8)),
56        ];
57
58        for _ in 0..axiom_length {
59            axiom.push(choices.choose(&mut rng).unwrap().clone());
60        }
61
62        // generate a random X rule
63        let mut x_rule = Vec::new();
64
65        while !valid_rule(&x_rule) {
66            x_rule.clear();
67
68            let x_rule_length = rng.gen_range(4..=10);
69
70            for _ in 0..x_rule_length {
71                x_rule.push(
72                    weighted_choices
73                        .choose_weighted(&mut rng, |item| item.1)
74                        .unwrap()
75                        .0
76                        .clone(),
77                );
78            }
79        }
80
81        let mut y_rule = Vec::new();
82
83        while !valid_rule(&y_rule) {
84            y_rule.clear();
85            // generate a random Y rule
86            let y_rule_length = rng.gen_range(4..=10);
87
88            for _ in 0..y_rule_length {
89                y_rule.push(
90                    weighted_choices
91                        .choose_weighted(&mut rng, |item| item.1)
92                        .unwrap()
93                        .0
94                        .clone(),
95                );
96            }
97        }
98
99        let mut builder = TurtleLSystemBuilder::new();
100
101        // Build our system up
102        builder
103            .token("L", TurtleAction::Rotate(25))
104            .token("R", TurtleAction::Rotate(-25))
105            .token("F", TurtleAction::Forward(100))
106            .token("+", TurtleAction::Push)
107            .token("-", TurtleAction::Pop)
108            .token("X", TurtleAction::Nothing)
109            .token("Y", TurtleAction::Nothing)
110            .axiom(&axiom.join(" "))
111            .rule(format!("X => {}", x_rule.join(" ")).as_str())
112            .rule(format!("Y => {}", y_rule.join(" ")).as_str());
113
114        // Consume the builder to construct an LSystem and the associated renderer
115        let (mut system, renderer) = builder.finish();
116
117        let options = ImageRendererOptionsBuilder::new()
118            .padding(20)
119            .thickness(1.0)
120            .fill_color(Rgb([0u8, 0u8, 0u8]))
121            .line_color(Rgb([218u8, 112u8, 214u8]))
122            .build();
123
124        // Iterate the system a few times
125        system.step_by(10);
126
127        // Render away
128        let buffer = renderer.render(&system, &options);
129
130        if buffer.width() < 1000 || buffer.height() < 1000 {
131            continue 'processing;
132        }
133
134        buffer.save("test_render.png").expect("Saving file failed");
135
136        break;
137    }
138}