-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.html
300 lines (295 loc) · 16 KB
/
index.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
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BROKEN_FIELD</title>
<script src="./gif.js" type="module"></script>
<script src="./gif.worker.js" type="module"></script>
<script src="./index.js" type="module"></script>
<link rel="stylesheet" href="style.css">
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png">
<link rel="manifest" href="site.webmanifest">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">
</head>
<body>
<f-col class="gap-1">
<h1>
BROKEN FIELD
</h1>
<main class="gap-1">
<f-col class="gap-1">
<textarea id="input" placeholder="Enter an equation!"></textarea>
<ui-element>
<button id="restart-btn">Restart</button>
<button id="randomize-btn">Randomize</button>
<button id="mutate-btn">Mutate</button>
<button id="simplify-btn">Simplify</button>
</ui-element>
<ui-element>
<button id="share-btn">Copy Sharable Link to Clipboard</button>
<div id="share-confirm"></div>
</ui-element>
<ui-element>
<label>Time Controls:</label>
<input type="number" id="time-start" placeholder="start t" , value="0" , class="small-text-input">
-
<input type="number" id="time-end" placeholder="end t" , value="" , class="small-text-input">
</ui-element>
<ui-element>
<label for="time-scale">Time Scale:</label>
<input type="range" id="time-scale" value="0.0" min="-1.0" max="1.0" step="any">
<div id="time-scale-display">1.0x</div>
</ui-element>
<ui-element>
<label for="color">Color</label>
<input type="color" id="color" value="#00FF00">
</ui-element>
<ui-element>
<label for="wrapping-value">Wrap Value:</label>
<input type="number" id="wrapping-value" value="256" class="small-text-input">
</ui-element>
<ui-element>
<label>Canvas Size: </label>
<input type="number" id="canvas-size-x" value="1024" placeholder="x" class="small-text-input" min="1">
x
<input type="number" id="canvas-size-y" value="1024" placeholder="y" class="small-text-input" min="1">
</ui-element>
<details>
<summary>Randomization History</summary>
<ul id="equation-history"></ul>
</details>
<details>
<summary>Advanced Settings</summary>
<h3>Shader Options</h3>
<ul>
<li>
<ui-element>
<label for="shader-precision-select">Integer and Float Precision:</label>
<select id="shader-precision-select">
<option value="highp">High Precision</option>
<option value="mediump">Medium Precision</option>
<option value="lowp">Low Precision</option>
</select>
</ui-element>
</li>
</ul>
<h3>Randomize/Mutate Generation Options</h3>
<ul>
<li>
<ui-element>
<label for="randomize-reset-time" title="If checked, resets the timer when randomizing or mutataing">Reset Time on Randomize</label>
<input type="checkbox" id="randomize-reset-time" checked="true">
</ui-element>
</li>
<li>
<ui-element>
<label for="randomize-enable-t" title="If checked, t will be generated in equations.">Generate <code>t </code></label>
<input type="checkbox" id="randomize-enable-t" checked="true">
</ui-element>
</li>
<li>
<ui-element>
<label for="randomize-enable-sx" title="If checked, sx will be generated in equations.">Generate <code>sx</code></label>
<input type="checkbox" id="randomize-enable-sx" checked="true">
</ui-element>
</li>
<li>
<ui-element>
<label for="randomize-enable-sy" title="If checked, sy will be generated in equations.">Generate <code>sy</code></label>
<input type="checkbox" id="randomize-enable-sy" checked="true">
</ui-element>
</li>
<li>
<ui-element>
<label for="randomize-enable-mx" title="If checked, mx will be generated in equations.">Generate <code>mx</code></label>
<input type="checkbox" id="randomize-enable-mx" checked="true">
</ui-element>
</li>
<li>
<ui-element>
<label for="randomize-enable-my" title="If checked, my will be generated in equations.">Generate <code>my</code></label>
<input type="checkbox" id="randomize-enable-my" checked="true">
</ui-element>
</li>
<li>
<ui-element>
<label for="randomize-enable-kx" title="If checked, kx will be generated in equations.">Generate <code>kx</code></label>
<input type="checkbox" id="randomize-enable-kx" checked="true">
</ui-element>
</li>
<li>
<ui-element>
<label for="randomize-enable-ky" title="If checked, ky will be generated in equations.">Generate <code>ky</code></label>
<input type="checkbox" id="randomize-enable-ky" checked="true">
</ui-element>
</li>
<li>
<ui-element>
<label for="mutate-enable-ops" title="If checked, Mutate will alter operators in your equation.">Mutate operators</label>
<input type="checkbox" id="mutate-enable-ops" checked="true">
</ui-element>
</li>
<li>
<ui-element>
<label for="mutate-enable-values" title="If checked, Mutate will alter variables and constants in your equation.">Mutate values</label>
<input type="checkbox" id="mutate-enable-values" checked="true">
</ui-element>
</li>
<li>
<ui-element>
<label for="randomize-avoid-ub" title="If checked, Randomize will try to avoid generating equations which would invoke undefined behavior.">Avoid Undefined Behavior</label>
<input type="checkbox" id="randomize-avoid-ub" checked="true">
</ui-element>
</li>
<li>
<ui-element>
<label for="randomize-depth-limit" title="The maximum depth the generator will recurse into. Higher values result in larger programs.">Maximum Depth</label>
<input type="number" id="randomize-depth-limit" value="20">
</ui-element>
</li>
</ul>
<h3>Shareable Link Options</h3>
<ul>
<li>
<ui-element>
<label for="share-link-whitespace" title="If checked, Copy Sharable Link will strip the whitespace in your equation.">Strip Whitespace</label>
<input type="checkbox" id="share-link-whitespace">
</ui-element>
</li>
</ul>
</details>
<details>
<summary>Shader Information</summary>
<h3>Parsing Information</h3>
<pre id="ub-check-display" class="red mono-text"></pre>
<pre id="parse-info-display" class="mono-text"></pre>
<h3>Shader Source</h3>
<textarea id="shader-source-display"></textarea>
</details>
<pre id="error-msg" class="red mono-text"></pre>
</f-col>
<f-col class="gap-1 canvas-column">
<canvas id="canvas" height="1024" width="1024" tabindex="1"></canvas>
<f-col class="gap-2">
<f-row>
<f-col>
<div id="coord-display"></div>
<div id="recording-indicator" class="hidden">Recording...</div>
</f-col>
<f-sp></f-sp>
<img id="image-display" class="small-square" />
<video autoplay width="128" id="video-display" loop poster="black.png" class="hidden small-square"></video>
</f-col>
<button id="screenshot-btn" class="hidden">Take Screenshot</button>
<f-col class="gap-1 align-right">
<ui-element>
<label>Manual Recording: </label>
<input type="number" placeholder="start t" id="video-encoding-start-frame" class="small-text-input">
-
<input type="number" placeholder="end t" id="video-encoding-end-frame" class="small-text-input">
</ui-element>
<button id="video-encoding-manual-record-btn">Record GIF</button>
</f-col>
</f-row>
</f-col>
</main>
<div>
<h2>Quick Start Guide</h2>
<strong>!!! Important !!! BROKEN_FIELD shaders often results in rapidly flashing or flickering output!</strong>
<p>BROKEN_FIELD is a tiny shader toy inspired by <a href="http://canonical.org/~kragen/bytebeat/">Bytebeat
music</a>. BROKEN_FIELD can be used to create or procedurally generate tiny, interactive art programs. The
textbox above defines a simple per-pixel shader. The output of the shader is then displayed on the canvas on the
right. For each pixel, the program is expected to output an integer. The value of the integer, modulo 256,
determines the brightness of the corresponding outputted pixel.</p>
<p>As an example, suppose we have the following program: <code>sx * sy</code>. This program is run for each
pixel of the output canvas. Hence, for the pixel at coordinates <code>(50, 30)</code>, the output of the program
is <code>50 * 30 = 1500 mod 256 = 220 = 0xDC</code>. By default, this gives that particular pixel the hex color
<code>0x00DC00</code>. This process is repeated for every pixel, which forms the final output image.
</p>
<p>Note that these programs are just fragments of <a
href="https://en.wikipedia.org/wiki/OpenGL_Shading_Language">GLSL</a> inserted into a GLSL shader. Hence, all
of the GLSL functions are available. See below for some technical details.</p>
<h2>Controls</h2>
<ul>
<li>Restart - Reset the current frame time to the start value.</li>
<li>Randomize - Generate a random shader.</li>
<li>Mutate - Mutate the current shader.</li>
<li>Time Scale - Set the values the frame time will start and end at. Leave the ending time blank for no looping
</li>
<li>Color - Sets the color of the shader. Defaults to green (<code>#00FF00</code>).</li>
<li>Wrap Value - Sets the range to which the shader ouput will be constrained to. For example, if the shader
outputs the value <code>735</code> and the wrap value is <code>32</code>, then the value is constrained to
<code>rem_euclid(735, 32) = 31</code>. Defaults to <code>256</code>.
</li>
<li>Canvas Size - Sets the <em>internal</em> canvas resolution. The canvas itself will not change size, but the
internal resolution will. Note that screenshots and videos will have a resolution equal to whatever this value
is. (So, for example, if the canvas size is <code>256 x 512</code>, then all screenshots and videos will have
an output resolution of <code>256 x 512</code>). Very large canvas sizes may not work properly or consume a
large amount of memory. Defaults to <code>1024 x 1024</code>.
</li>
<li>Randomization History - Contains a list of all the prior randomization/mutation attempts.</li>
</ul>
</p>
<h2>Image & Video Export</h2>
<p>You can take a screenshot of the current canvas by using the "Take Screenshot" button or clicking on the
canvas. This will take a picture of the canvas as a PNG and is shown in the small square below the canvas.</p>
<p>You can also record a video of the current canvas by pressing "R". Press "R" again to stop the recording.
Holding "Shift" while starting the recording will reset the current frame time to zero (or whatever the starting
frame time is set to), allowing you to capture the start of the shader. This will export a WebM.</p>
<p>Finally, you can also record a GIF using the "Record GIF" button. The "Manual Recording" input textboxes will
determine which values of <code>t</code> will be shown in the exported GIF. <strong>Important!</strong> Large
resolutions and long GIFs will take a long time to render and will have a large file size. You may want to use a
site like <a href="https://ezgif.com/optimize">ezgif</a> to reduce the file size of the GIF.
</p>
<h2>Complete list of variables</h2>
<ul>
<li><code>t</code> - <b>t</b>ime - Equals the current frame. The rate at which this value increases (or
decreases) can be controlled with the "Time Scale" slider.</li>
<li><code>sx</code> - <b>s</b>creen <b>x</b>-coordinate - Equals the x-coordinate of the pixel. Note that the
x-coordinate increases in the rightwards direction.</li>
<li><code>sy</code> - <b>s</b>creen <b>y</b>-coordinate - Equals the y-coordinate of the pixel. Note that the
y-coordinate increases in the upwards direction.</li>
<li><code>mx</code> - <b>m</b>ouse <b>x</b>-coordinate - Equals the x-coordinate of the mouse. Note that the
x-coordinate increases in the rightwards direction.</li>
<li><code>my</code> - <b>m</b>ouse <b>y</b>-coordinate - Equals the y-coordinate of the mouse. Note that the
y-coordinate increases in the upwards direction.</li>
<li><code>kx</code> - <b>k</b>eyboard <b>x</b>-coordinate - Increased by one by pressing right and decreased by
one by pressing left.</li>
<li><code>ky</code> - <b>k</b>eyboard <b>y</b>-coordinate - Increased by one by pressing up and decreased by one
by pressing down.</li>
</ul>
<p>You can append <code>_f</code> to any of these variables to get a float-valued verison of the variable. For
example, <code>t_f</code> is equal to the current frame value, but as a float.</p>
<h2>Technical Details</h2>
<p>All of this stuff is just a GLSL fragment shader under the hood. Specifically, it's this shader:</p>
<pre id="fragment-shader-source"></pre>
<p>In the shader above, <code>${bytebeat}</code> is substituted with whatever you type into the textarea.</p>
<p>(The vertext shader is extremely boring. it is literally this four-line shader:
<pre id="vertex-shader-source"></pre>
</p>
<h3>The Internal Parser</h3>
<p>A typical program consists of just a GLSL expression. However, BROKEN_FIELD also supports
declaring variables. This is done in typeical GLSL syntax. For example, here is a program
using variables:
<pre>int foo = sy * sx;
int bar = 56 ^ mx;
foo * t + bar
</pre>
A few extensions to this syntax are provided via the Internal Parser. First,
declarations may be elided. If this is done, BROKEN_FIELD will attempt to infer a reasonable
type for the variable. Second, types may be automatically coerced using <code>int()</code>,
<code>float()</code>, <code>bool()</code> and so on. This is only done if the program does
not already compile as normal GLSL. In otherwords, treating the input as GLSL code is
prefered over the Internal Parser when possible. Please note that the Internal Parser probably
isn't perfect and doesn't understand all of the possible constructs you can do in GLSL, so
there's a good chance it won't work if you use something really tricky with it.
</p>
<p>You can see what shader code is actually running under the Shader Information dropdown,
which also shows whether or not the code is treated as raw GLSL or was internally parsed.
</p>
</div>
</body>
</html>