-
Notifications
You must be signed in to change notification settings - Fork 0
/
ls-turtle.html
261 lines (246 loc) · 10.9 KB
/
ls-turtle.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width,initial-scale=1.0">
<meta name="description" content="Iterated Function Systems and L-Systems">
<meta name="keywords" content="IFS,iterated,function system,
Lindenmayer System, L-system">
<meta name="theme-color" content="#00519e" />
<title>L-Systems</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="lsys-style.css">
<script src="collapsible-pane.js"></script>
<script src="draggable-canvas.js"></script>
<script src="ls-parser.js"></script>
<script src="turtle-vm.js"></script>
<!-- <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"> -->
</head>
<body>
<header>
<h1>L-Systems</h1>
<a href="index.html" id="back">
<img src="https://fonts.gstatic.com/s/i/materialicons/keyboard_arrow_left/v6/24px.svg" alt="Go back"/>
</a>
</header>
<main id="main">
<div id="pane-settings" class="pane">
<div class="pane-title" title="Click to minimise this pane"><h2>Settings</h2></div>
<div class="pane-content scrollable-y">
<form>
<!-- onchange="updateSettings();" -->
<label for="grammar">Grammar definitions:</label>
<textarea type="text" id="sgrammar" name="grammar" placeholder="Grammar definitions" style="min-height: 17em;">
;; The line below can be used to set the initial direction
;; and the line width depending on the current iteration
0: set_linewidth{this.iteration}; set_direction{0}
;; The main definitions:
F: draw_forward{50}
G: draw_forward{50}
L: turn_left{60}
R: turn_right{60}
r: turn_right{120}
;; Note that r and R are different! The alphabet is case-sensitive!</textarea>
<br/>
<label for="axiom">Axiom:</label>
<textarea id="saxiom" name="axiom" placeholder="Axiom">0F</textarea>
<br/>
<label for="reprules">Replacement rules:</label>
<textarea id="srules" name="reprules">F: FLFRRFLF</textarea>
<span class="iteration-highlight">
<input type="number" style="width: 5em" min="0" value="0" step="1" id="sn-iterruns" name="iterruns" onchange="updateSettings();">
<label for="iterruns">Number of iterations</label>
</span>
<input type="number" style="width: 5em" min="1" value="1.5" step="0.1" id="ssfactor" name="zfactor">
<label for="zfactor">Inverse scale factor (zoom out)</label>
<br/>
<input type="checkbox" name="animate" id="sanimate" checked="true">
<label for="animate">Animate (slower)</label>
<!-- <br/> -->
<span style="display: block">
<input type="reset" style="float: right;" class="danger" value="reset options" onclick="updateSettings();">
<input type="button" style="float: right;" class="semidanger" value="reset canvas" onclick="setupCanvas();">
<input type="button" style="float: right;" class="greenbox" value="draw!" onclick="updateSettings();" id="drawbtn">
</span>
</form>
<br/>
<br/>
<center>
<table id="wordlengths-table"></table>
</center>
</div>
<div class="pane-bottom"></div>
</div>
<div id="pane-preview" class="pane">
<div class="pane-title"><h2>Preview</h2></div>
<div class="pane-content">
<div id="scrollable-canvas" class="">
<canvas id="canvas" width="1000px" height="1000px"></canvas>
</div>
</div>
<div class="pane-bottom"></div>
</div>
<div id="pane-help" class="pane">
<div class="pane-title" title="Click to minimise this pane"><h2>Help</h2></div>
<div class="pane-content">
<div class="scrollable-y">
This pane will help you understand the settings in the left pane.
<h4>Grammar & Turtle commands</h4>
<p class="indent">
Here you can define the <var>actions</var> associated with a <var>letter</var> that can be used to produce pretty images:<br/>
<code class="block">
<var>letter</var>: <var>action</var>{<var>paramter</var>}; <var>action</var>{<var>paramter</var>}; ...<br />
<var>letter</var>: <var>action</var>{<var>paramter</var>}; <var>action</var>{<var>paramter</var>}; ...<br />
<b>;;</b> A comment line starts with two semicolons. Inline comments are not allowed.<br/>
...
</code>
<var>Letter</var> is a single character, followed by a colon. The allowed <var>action</var>s are the following:
<ul>
<li><code>draw_forward{length}</code>: Move the turtle foward while drawing a line</li>
<li><code>jump_forward{length}</code>: Move the turtle foward without drawing a line</li>
<li><code>turn_left{angle}</code>: Turn the turtle <var>angle</var> degrees to the left</li>
<li><code>turn_right{angle}</code>: Turn the turtle <var>angle</var> degrees to the right</li>
<li><code>save_state</code>: Save the current state (position & direction)</li>
<li><code>restore_state</code>: Pop a saved state off the stack and restore it</li>
<li><code>set_direction{angle}</code>: Set the direction to <var>angle</var></li>
<li><code>set_position{[x, y]}</code>: Set the absolute position to (<var>x</var>, <var>y</var>)</li>
<li><code>no_operation</code>: Do nothing</li>
<li><code>set_color{color}</code> changes the stroke color to <var>color</var></li>
<li><code>set_linewidth{width}</code> sets the linewidth of the segments.
<code class="block">x: set_linewidth{this.iteration}</code> will increase the line width with every iteration a bit because
the final image is often scaled down. Do not forget to add <code>x</code> to your axiom!</li>
<li><code>dump_state</code>: dump the current state. This is useful for debugging, so you probably do not need this.
To see the dumped state, open the development console (press <kbd>F12</kbd>) and navigate to the console tab.</li>
<!-- TODO -->
</ul>
</p>
<h4>Axiom</h4>
<p class="indent">
The axiom is the starting point. From there the application of replacement rules will hopefully lead you to some
amazing fractals!
</p>
<h4>Replacement Rules</h4>
<p class="indent">
The core of an L system is the set of replacement rules that determine the changes when iterating.
A rule consists of two parts:
</p>
<ol>
<li>A letter to replace.</li>
<li>A word (sequence of letters) to replace the letter with.</li>
</ol>
<p class="indent">
Similar to the syntax you already know from the grammar definition, you can simply define replacement rules like this:
<code class="block">
<var><letter a></var>: <var><letter a1></var><var><letter 2></var><var><letter a3></var>...<br/>
<var><letter b></var>: <var><letter b1></var><var><letter b2></var>...<br/>
</code>
E. g.:
<code class="block">
F: GFLFRRFFLLFRFG<br/>
G: GG<br/>
</code>
<i>Note that comments are not allowed!</i>
</p>
<h4>Tips & Tricks</h4>
<p>
To simplify the implementation of this mini programming language, a bad practice is used: the parameters
are parsed and executed as JavaScript. That is probably not ideal, but it enables us to do <i>quite fancy stuff</i>:
</p>
<ul>
<li>We know in which state the turtle is. As you have already seen, we can access the current iteration number with <code>this.iteration</code>.</li>
<li>We can use math:
<code class="block">
F: draw_forward{Math.sqrt(1764)}
</code>
</li>
<li>We can introduce randomness: Just use <code>Math.rand(<var>min</var>, <var>max</var>)</code> and <code>Math.randNorm(<var>avg</var>, <var>std</var>)</code>, e. g.:
<code class="block">
F: draw_forward{Math.randNorm(50, 10)}<br/>
G: set_direction{Math.rand(0, 2 * Math.PI)}
</code>
The first macro draws a line with a length that is normally distributed around 50 with a standard deviation of 10. The second macro chooses a random direction.
</li>
</ul>
</div>
</div>
<div class="pane-bottom"></div>
</div>
</main>
<script>
p1 = new CollapsiblePane("pane-help", "click");
p2 = new CollapsiblePane("pane-settings", "click");
p1.hideHandler = function() {
if (!p2.isVisible()) p2.toggle();
document.getElementById("main").style.gridTemplateRows = "minmax(0, 1fr) auto";
};
p1.showHandler = function() {
document.getElementById("main").style.gridTemplateRows = "";
};
p2.hideHandler = function() {
if (!p1.isVisible()) p1.toggle();
document.getElementById("main").style.gridTemplateRows = "auto minmax(0, 1fr)";
};
p2.showHandler = function() {
document.getElementById("main").style.gridTemplateRows = "";
};
drawBtn = document.getElementById("drawbtn");
c = new DraggableCanvas(document.getElementById("canvas"));
c.register();
turtle = new Turtle(c, {}, 0, 0); // canvas, grammar, phi0, iteration
function setupCanvas() {
// console.log("Setting up canvas");
turtle.kill();
c.clear();
}
function afterDrawingCallback() {
console.info("Drawing done");
drawBtn.value = "draw!";
drawBtn.disabled = false;
}
function updateSettings() {
console.clear();
drawBtn.disabled = true;
drawBtn.value = "drawing...";
var animate = document.getElementById("sanimate").checked;
// c.resizeCanvas(); // TODO
console.info("Updating settings");
var grammar = parseGrammar();
var alphabet = Object.keys(grammar);
console.info("Grammar:", grammar);
console.info("Alphabet:", alphabet);
try {
var axiom = parseAxiom(alphabet);
console.info("Axiom:", axiom);
} catch (e) {
console.error("updateSettings failed!");
return;
}
var rules = parseReplacementRules(alphabet);
console.info("Rules:", rules);
var niterations = 1 * document.getElementById("sn-iterruns").value;
var scale = 1 * document.getElementById("ssfactor").value;
console.info("Number of iterations:", niterations);
for (i=0; i<=niterations; i++) {
console.info(doReplacement(axiom, rules, i).length);
};
// TEMP: This table gets very long...
makeWordlengthTable(axiom, rules, niterations);
// var turtle = new Turtle(c, grammar, 0, niterations); // canvas, grammar, phi0, iteration
turtle.reset();
turtle.iteration = niterations;
turtle.grammar = grammar;
turtle.doTheJob(doReplacement(axiom, rules, niterations),
animate, scale, afterDrawingCallback, drawBtn);
console.log(c.currentBBox);
}
// var c = x.ctx;
// c.moveTo(-500, -500);
// c.lineTo(500, 500);
// c.moveTo(500, -500);
// c.lineTo(-500, 500);
// c.strokeRect(100, 100, 30, 40);
// c.stroke();
</script>
</body>
</html>