├── LICENSE
├── README.md
├── core
└── draw.js
├── index.html
├── methods
├── perlin-noise.js
├── value-noise.js
└── white-noise.js
├── screen
├── canvas.js
├── globals.js
└── ui.js
├── styles.css
├── utils
├── draw.js
├── math.js
├── misc.js
└── worker.js
└── visualisations
├── 1d-ball.js
├── 1d-color-gradient.js
├── 1d-colorful-ball.js
├── 1d-colorful-line.js
├── 1d-colorful-triangle.js
├── 1d-line.js
├── 1d-radius-ball.js
├── 1d-rgb-lines.js
├── 2d-colorful-image.js
├── 2d-colorful-marble-image.js
├── 2d-colorful-wood-image.js
├── 2d-grayscale-image.js
├── 2d-marble-image.js
├── 2d-terrain.js
└── 2d-wood-image.js
/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2019, Rameş Aliyev
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Visual Noises
2 |
3 | [Go play with it ->](https://ramesaliyev.com/visual-noises/)
4 |
5 | 1D and 2D visualisations of White Noise, Value Noise and Perlin Noise. For further understanding refer to [learning resources](https://github.com/ramesaliyev/visual-noises/issues/1).
6 |
7 | 
--------------------------------------------------------------------------------
/core/draw.js:
--------------------------------------------------------------------------------
1 | let drawScheduled = false;
2 |
3 | function scheduleDraw() {
4 | drawScheduled = true;
5 |
6 | window.requestAnimationFrame(() => {
7 | drawScheduled = false;
8 | draw(true);
9 | });
10 | }
11 |
12 | function draw(schedule) {
13 | if (drawScheduled) {
14 | return false;
15 | }
16 |
17 | if (leaveTail) {
18 | context.globalAlpha = 0.1;
19 | context.fillStyle = '#000';
20 | context.fillRect(0, 0, screenWidth, screenHeight);
21 | context.globalAlpha = 1;
22 | } else if (clearCanvas) {
23 | context.clearRect(0, 0, screenWidth, screenHeight);
24 | }
25 |
26 | const {
27 | visualisationFn,
28 | methodFn,
29 | filterFn,
30 | outputFilterFn,
31 | } = getCurrentState();
32 |
33 | const isAsync = visualisationFn({
34 | getValueFn: ((x = 0, y = 0) => {
35 | let noiseSum = 0;
36 | let _amplitude = 0.5;
37 | let _frequency = frequency;
38 |
39 | let xOffset = yOffset = offset;
40 |
41 | if (!syncOffsetsXY) {
42 | yOffset = 0;
43 | }
44 |
45 | let maxValue = 0;
46 |
47 | for (let i = 0; i < octave; i++) {
48 | const noise = methodFn(
49 | (x + xOffset) * _frequency,
50 | (y + yOffset) * _frequency,
51 | filterFn
52 | )
53 |
54 | noiseSum += outputFilterFn(noise) * _amplitude;
55 |
56 | maxValue += _amplitude;
57 | _frequency *= lacunarity;
58 | _amplitude *= gain;
59 | }
60 |
61 | return (noiseSum / maxValue) * amplitude;
62 | }),
63 | offsetX: 200,
64 | offsetY: 0,
65 | width: screenWidth - 200,
66 | height: screenHeight - 100,
67 | done: () => !stopped && scheduleDraw()
68 | });
69 |
70 | if (!paused) {
71 | offsetInput.value = parseFloat(offset.toFixed('10'));
72 | offset += speed;
73 | }
74 |
75 | if (stopped && debugMode) console.log('Draw stopped.');
76 |
77 | clearCanvas = !isAsync;
78 |
79 | if (!stopped && schedule && !isAsync) {
80 | scheduleDraw();
81 | }
82 | }
83 |
84 | // Initialize seed.
85 | setRandomSeed(seed);
86 |
87 | // Warm up noise algorithms.
88 | ValueNoise2D({});
89 | PerlinNoise2D({});
90 |
91 | onResize();
92 |
93 | applySettings(visualisationDefaults['1d-line']);
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Visual Noises @ ramesaliyev
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
30 |
52 |
62 |
73 |
82 |
88 |
94 |
100 |
106 |
112 |
118 |
124 |
133 |
139 |
145 |
151 |
157 |
158 |
159 |
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 | onResize(true));
37 |
38 | body.appendChild(canvas);
39 |
--------------------------------------------------------------------------------
/screen/globals.js:
--------------------------------------------------------------------------------
1 | let debugMode = false;
2 |
3 | let screenWidth;
4 | let screenHeight;
5 | let mouseX;
6 | let mouseY;
7 |
8 | let visualisation = '1d-line';
9 | let method = 'value-noise-2d';
10 | let filter = 'smoothstep';
11 | let outputFilter = 'none';
12 |
13 | let seed = Date.now();
14 |
15 | let stopped = false;
16 | let paused = false;
17 | let leaveTail = false;
18 | let applyDefaults = true;
19 | let syncOffsetsXY = false;
20 | let clearCanvas = true;
21 |
22 | let speed;
23 | let offset = 0;
24 | let amplitude;
25 | let frequency;
26 |
27 | let octave = 1;
28 | let lacunarity = 1;
29 | let gain = 1;
--------------------------------------------------------------------------------
/screen/ui.js:
--------------------------------------------------------------------------------
1 | const presetSelect = document.getElementById('presetSelect');
2 | const visualisationSelect = document.getElementById('visualisationSelect');
3 | const methodSelect = document.getElementById('methodSelect');
4 | const filterSelect = document.getElementById('filterSelect');
5 | const outputFilterSelect = document.getElementById('outputFilterSelect');
6 | const speedInput = document.getElementById('speedInput');
7 | const offsetInput = document.getElementById('offsetInput');
8 | const frequencyInput = document.getElementById('frequencyInput');
9 | const amplitudeInput = document.getElementById('amplitudeInput');
10 | const octaveInput = document.getElementById('octaveInput');
11 | const lacunarityInput = document.getElementById('lacunarityInput');
12 | const gainInput = document.getElementById('gainInput');
13 | const drawButton = document.getElementById('drawButton');
14 | const playPauseButton = document.getElementById('playPauseButton');
15 | const incrementOffsetButton = document.getElementById('incrementOffsetButton');
16 | const resetOffsetButton = document.getElementById('resetOffsetButton');
17 | const seedInput = document.getElementById('seedInput');
18 |
19 | const leaveTailInput = document.getElementById('leaveTailInput');
20 | const applyDefaultsInput = document.getElementById('applyDefaultsInput');
21 | const syncOffsetsInput = document.getElementById('syncOffsetsInput');
22 |
23 | const elements = [
24 | presetSelect,
25 | visualisationSelect,
26 | methodSelect,
27 | filterSelect,
28 | outputFilterSelect,
29 | speedInput,
30 | offsetInput,
31 | frequencyInput,
32 | amplitudeInput,
33 | octaveInput,
34 | lacunarityInput,
35 | gainInput,
36 | drawButton,
37 | playPauseButton,
38 | incrementOffsetButton,
39 | resetOffsetButton,
40 | seedInput,
41 | leaveTailInput,
42 | applyDefaultsInput,
43 | syncOffsetsInput
44 | ];
45 |
46 | const visualisationFnsMap = {
47 | '1d-line': draw1DLine,
48 | '1d-colorful-line': draw1DColorfulLine,
49 | '1d-color-gradient': draw1DColorGradient,
50 | '1d-rgb-lines': draw1DRGBLines,
51 | '1d-ball': draw1DBall,
52 | '1d-colorful-ball': draw1DColorfulBall,
53 | '1d-radius-ball': draw1DRadiusBall,
54 | '1d-colorful-triangle': draw1DColorfulTriangle,
55 | '2d-grayscale-image': draw2DGrayscaleImage,
56 | '2d-colorful-image': draw2DColorfulImage,
57 | '2d-marble-image': draw2DMarbleImage,
58 | '2d-colorful-marble-image': draw2DColorfulMarbleImage,
59 | '2d-wood-image': draw2DWoodImage,
60 | '2d-colorful-wood-image': draw2DColorfulWoodImage,
61 | '2d-terrain': draw2DTerrainImage
62 | };
63 |
64 | const methodFnsMap = {
65 | 'white-noise': WhiteNoise,
66 | 'value-noise-2d': ValueNoise2D,
67 | 'perlin-noise-2d': PerlinNoise2D,
68 | };
69 |
70 | const filterFnsMap = {
71 | 'none': id,
72 | 'cosine': cosFilter,
73 | 'smoothstep': smoothStepFilter,
74 | 'fade': fadeFilter,
75 | };
76 |
77 | const outputFilterFnsMap = {
78 | 'none': id,
79 | 'turbulance': turbulenceOFilter,
80 | };
81 |
82 | const visualisationDefaults = {
83 | '1d-line': {speed: 30, amplitude: 500, frequency: 0.001, octave: 1, lacunarity: 1, gain:1, method:'value-noise-2d', syncOffsetsXY: false, stop: false},
84 | '1d-colorful-line': {speed: 30, amplitude: 500, frequency: 0.001, octave: 1, lacunarity: 1, gain:1, method:'value-noise-2d', syncOffsetsXY: false, stop: false},
85 | '1d-color-gradient': {speed: 30, amplitude: 500, frequency: 0.0003, octave: 1, lacunarity: 1, gain:1, method:'value-noise-2d', syncOffsetsXY: false, stop: false},
86 | '1d-rgb-lines': {speed: 30, amplitude: 500, frequency: 0.001, octave: 1, lacunarity: 1, gain:1, method:'value-noise-2d', syncOffsetsXY: false, stop: false},
87 | '1d-ball': {speed: 30, amplitude: 500, frequency: 0.001, octave: 1, lacunarity: 1, gain:1, method:'value-noise-2d', syncOffsetsXY: false, stop: false},
88 | '1d-colorful-ball': {speed: 30, amplitude: 500, frequency: 0.001, octave: 1, lacunarity: 1, gain:1, method:'value-noise-2d', syncOffsetsXY: false, stop: false},
89 | '1d-radius-ball': {speed: 30, amplitude: 500, frequency: 0.001, octave: 1, lacunarity: 1, gain:1, method:'value-noise-2d', syncOffsetsXY: false, stop: false},
90 | '1d-colorful-triangle': {speed: 30, amplitude: 500, frequency: 0.001, octave: 1, lacunarity: 1, gain:1, method:'value-noise-2d', syncOffsetsXY: false, stop: false},
91 | '2d-grayscale-image': {speed: 0, offset: 0, amplitude: 1.8, frequency: 0.02, octave: 5, lacunarity: 2, gain:0.5, method:'perlin-noise-2d', filter:'fade', disable: [playPauseButton, speedInput], syncOffsetsXY: true, stop: true},
92 | '2d-colorful-image': {speed: 0, offset: 0, amplitude: 1.8, frequency: 0.02, octave: 5, lacunarity: 2, gain:0.5, method:'perlin-noise-2d', filter:'fade', disable: [playPauseButton, speedInput], syncOffsetsXY: true, stop: true},
93 | '2d-marble-image': {speed: 0, offset: 0, amplitude: 1.8, frequency: 0.02, octave: 5, lacunarity: 2, gain:0.5, method:'perlin-noise-2d', filter:'fade', disable: [playPauseButton, speedInput], syncOffsetsXY: true, stop: true},
94 | '2d-colorful-marble-image': {speed: 0, offset: 0, amplitude: 1.8, frequency: 0.02, octave: 5, lacunarity: 2, gain:0.5, method:'perlin-noise-2d', filter:'fade', disable: [playPauseButton, speedInput], syncOffsetsXY: true, stop: true},
95 | '2d-wood-image': {speed: 0, offset: 0, amplitude: 1.8, frequency: 0.008, octave: 1, lacunarity: 1, gain:1, method:'perlin-noise-2d', filter:'fade', disable: [playPauseButton, speedInput], syncOffsetsXY: true, stop: true},
96 | '2d-colorful-wood-image': {speed: 0, offset: 0, amplitude: 1.8, frequency: 0.008, octave: 1, lacunarity: 1, gain:1, method:'perlin-noise-2d', filter:'fade', disable: [playPauseButton, speedInput], syncOffsetsXY: true, stop: true},
97 | '2d-terrain': {speed: 10, offset: 0, amplitude: 0.95, frequency: 0.01, octave: 5, lacunarity: 2, gain:0.5, method:'perlin-noise-2d', filter:'fade', syncOffsetsXY: true, stop: false},
98 | };
99 |
100 | Object.keys(visualisationDefaults).map(key => visualisationDefaults[key].visualisation = key);
101 |
102 | const presets = {
103 | 'dancing-mountains': {visualisation: '1d-line', method: 'value-noise-2d', filter: 'smoothstep', outputFilter: 'none', speed: 1, amplitude: 500, frequency: 0.0005, octave: 5, lacunarity: 3, gain:1, syncOffsetsXY:true, stop:false},
104 | 'rainy-clouds': {visualisation: '2d-grayscale-image', method: 'value-noise-2d', filter: 'smoothstep', outputFilter: 'none', speed: 0, offset: 0, amplitude: 1.8, frequency: 0.002, octave: 5, lacunarity: 2, gain:0.5, stop:true},
105 | 'dream-clouds': {visualisation: '2d-colorful-image', method: 'value-noise-2d', filter: 'smoothstep', outputFilter: 'none', speed: 0, offset: 0, amplitude: 1.8, frequency: 0.002, octave: 5, lacunarity: 2, gain:0.5, stop:true},
106 | }
107 |
108 | Object.keys(presets).map(key => presets[key].disable = visualisationDefaults[presets[key].visualisation].disable);
109 |
110 | function setOption(el, val) {
111 | el.selectedIndex = [...el.options].find(({value}) => value === val).index;
112 | }
113 |
114 | function applySettings(settings) {
115 | method = first(settings.method, method);
116 | filter = first(settings.filter, filter);
117 | outputFilter = first(settings.outputFilter, outputFilter);
118 | visualisation = first(settings.visualisation, visualisation);
119 | syncOffsetsXY = first(settings.syncOffsetsXY, syncOffsetsXY);
120 |
121 | if (applyDefaults) {
122 | speed = first(settings.speed, speed);
123 | offset = first(settings.offset, offset)
124 | frequency = first(settings.frequency, frequency)
125 | amplitude = first(settings.amplitude, amplitude)
126 | octave = first(settings.octave, octave)
127 | lacunarity = first(settings.lacunarity, lacunarity)
128 | gain = first(settings.gain, gain)
129 | }
130 |
131 | elements.forEach(element => element.disabled = false);
132 |
133 | speedInput.value = speed;
134 | offsetInput.value = offset;
135 | frequencyInput.value = frequency;
136 | amplitudeInput.value = amplitude;
137 | octaveInput.value = octave;
138 | lacunarityInput.value = lacunarity;
139 | gainInput.value = gain;
140 | seedInput.value = seed;
141 | syncOffsetsInput.checked = syncOffsetsXY;
142 |
143 | setOption(visualisationSelect, visualisation);
144 | setOption(methodSelect, method);
145 | setOption(filterSelect, filter);
146 | setOption(outputFilterSelect, outputFilter);
147 |
148 | (settings.disable || []).forEach(element => element.disabled = true);
149 |
150 | stopped = first(settings.stop, stopped);
151 | draw(!stopped);
152 | }
153 |
154 | function getCurrentState() {
155 | const visualisationFn = visualisationFnsMap[visualisation];
156 | const methodFn = methodFnsMap[method];
157 | const filterFn = filterFnsMap[filter];
158 | const outputFilterFn = outputFilterFnsMap[outputFilter];
159 |
160 | return {
161 | visualisationFn,
162 | methodFn,
163 | filterFn,
164 | outputFilterFn
165 | }
166 | }
167 |
168 | function on(element, eventName, fn) {
169 | element.addEventListener(eventName, e => {
170 | if (element !== presetSelect) {
171 | setOption(presetSelect, 'none');
172 | }
173 |
174 | fn(e);
175 | });
176 | }
177 |
178 | function pauseOnFocus(element) {
179 | let prevPaused;
180 | on(element, 'focus', e => {prevPaused = paused; paused = true});
181 | on(element, 'blur', e => {paused = prevPaused});
182 | }
183 |
184 | function onChange(element, fn) {
185 | on(element, 'change', e => fn(e.target.value));
186 | }
187 |
188 | function onCheck(element, fn) {
189 | on(element, 'change', e => fn(e.target.checked));
190 | }
191 |
192 | function onChangeGetFloat(element, fn) {
193 | onChange(element, val => fn(parseFloat(val, 10)));
194 | }
195 |
196 | function onChangeGetInt(element, fn) {
197 | onChange(element, val => fn(parseInt(val, 10)));
198 | }
199 |
200 | onChange(presetSelect, presetName => {
201 | applySettings(presets[presetName]);
202 | });
203 |
204 | onChange(visualisationSelect, val => {
205 | applySettings(visualisationDefaults[val]);
206 | });
207 |
208 | onChange(methodSelect, val => method = val);
209 | onChange(filterSelect, val => filter = val);
210 | onChange(outputFilterSelect, val => outputFilter = val);
211 |
212 | onChangeGetFloat(speedInput, val => speed = val);
213 | onChangeGetFloat(offsetInput, val => offset = val);
214 | onChangeGetFloat(frequencyInput, val => frequency = val);
215 | onChangeGetFloat(amplitudeInput, val => amplitude = val);
216 | onChangeGetFloat(octaveInput, val => octave = val);
217 | onChangeGetFloat(lacunarityInput, val => lacunarity = val);
218 | onChangeGetFloat(gainInput, val => gain = val);
219 | pauseOnFocus(offsetInput);
220 |
221 | onChangeGetInt(seedInput, val => {
222 | seed = val;
223 | setRandomSeed(val);
224 |
225 | ValueNoise2D({});
226 | PerlinNoise2D({});
227 |
228 | offset = 0;
229 | });
230 |
231 | onCheck(leaveTailInput, val => leaveTail = val);
232 | onCheck(applyDefaultsInput, val => applyDefaults = val);
233 | onCheck(syncOffsetsInput, val => syncOffsetsXY = val);
234 |
235 | on(drawButton, 'click', () => draw(false));
236 | on(playPauseButton, 'click', () => {
237 | paused = !paused;
238 | playPauseButton.innerText = paused ? 'Start Auto Draw' : 'Auto Drawing';
239 | playPauseButton.style.color = paused ? '#e0e0e0' : 'yellowgreen';
240 | });
241 | on(incrementOffsetButton, 'click', () => {
242 | offset += speed;
243 | offsetInput.value = parseFloat(offset.toFixed('10'));
244 | });
245 | on(resetOffsetButton, 'click', () => offset = 0);
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | border: none;
5 | outline: none;
6 | }
7 |
8 | html, body {
9 | width: 100%;
10 | height: 100%;
11 | overflow: hidden;
12 | background-color: #000;
13 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
14 | color: #e0e0e0;
15 | }
16 |
17 | .flex-row {
18 | display: flex;
19 | flex-direction: row;
20 | align-items: center;
21 | justify-content: flex-start;
22 | }
23 |
24 | .pointer-cursor {
25 | cursor: pointer
26 | }
27 |
28 | .root {
29 | position: absolute;
30 | z-index: 2;
31 | top: 0;
32 | left: 0;
33 | right: 0;
34 | bottom: 0;
35 | }
36 |
37 | .panel {
38 | width: 211px;
39 | position: absolute;
40 | top: 0;
41 | left: 0;
42 | bottom: 0;
43 | display: flex;
44 | flex-direction: column;
45 | background: #111;
46 | box-sizing: border-box;
47 | overflow: hidden;
48 | border-right: 1px solid #333;
49 | }
50 |
51 | .panel-header {
52 | padding: 20px 20px 10px 20px;
53 | }
54 |
55 | .panel-body {
56 | flex: 1;
57 | padding: 0 20px 20px 20px;
58 | overflow: auto;
59 | }
60 |
61 | .panel-footer {
62 | width: 210px;
63 | height: 41px;
64 | padding: 10px 20px;
65 | background: #111;
66 | box-sizing: border-box;
67 | display: flex;
68 | justify-content: space-between;
69 | border-top: 1px solid #333;
70 | box-shadow: -10px -5px 18px 1px #000;
71 | }
72 |
73 | .page-title {
74 | font-size: 52px;
75 | text-align: center;
76 | color: #333;
77 | line-height: 47px;
78 | }
79 |
80 | .input-wrapper {
81 | padding: 10px 0;
82 | }
83 |
84 | .input-checkbox-wrapper {
85 | padding: 2px 0;
86 | }
87 |
88 | .input-name,
89 | .input-label {
90 | color: #999;
91 | font-size: 14px;
92 | }
93 |
94 | .input-name {
95 | margin-bottom: 4px;
96 | }
97 |
98 | .input-label {
99 | flex: 1;
100 | }
101 |
102 | .input-content {
103 | }
104 |
105 | .input-element {
106 | padding: 5px 10px;
107 | border-radius: 3px;
108 | font-size: 14px;
109 | width: 100%;
110 | box-sizing: border-box;
111 | background: #333;
112 | color: #e0e0e0;
113 | }
114 |
115 | .input-select {
116 | -webkit-appearance: none;
117 | appearance: none;
118 | }
119 |
120 | .input-button {
121 | margin-bottom: 2px;
122 | }
123 |
124 | .input-button:hover {
125 | opacity: 0.9;
126 | cursor: pointer;
127 | }
128 |
129 | .input-checkbox {
130 | width: 25px;
131 | }
132 |
133 | .input-element:disabled,
134 | .input-element:disabled:hover {
135 | opacity: 0.5;
136 | cursor: not-allowed;
137 | }
--------------------------------------------------------------------------------
/utils/draw.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Draw circle
3 | */
4 | function circle(x, y, r, options) {
5 | const {
6 | color = '#fff',
7 | fill = true,
8 | startAngle = 0,
9 | endAngle = TWO_PI,
10 | aCW = true,
11 | lineWidth = 1,
12 | } = (options || {});
13 |
14 | context.lineWidth = lineWidth;
15 | context.strokeStyle = color;
16 | context.fillStyle = color;
17 | context.moveTo(x, y);
18 | context.beginPath();
19 |
20 | fill && context.moveTo(x, y);
21 |
22 | context.arc(x, y, r, startAngle, endAngle, aCW);
23 |
24 | fill && context.closePath();
25 | fill ? context.fill() : context.stroke();
26 | };
27 |
28 | /**
29 | * Draw line
30 | */
31 | function line(fromX, fromY, toX, toY, options) {
32 | const {
33 | color = '#fff',
34 | lineWidth = 1,
35 | } = (options || {});
36 |
37 | context.setLineDash([]);
38 | context.lineWidth = lineWidth;
39 | context.strokeStyle = color;
40 | context.beginPath();
41 | context.moveTo(fromX, fromY);
42 | context.lineTo(toX, toY);
43 | context.stroke();
44 | }
45 |
46 | /**
47 | * Draw dashed line
48 | */
49 | function dashedLine(xFrom, yFrom, xTo, yTo, options) {
50 | const {
51 | dashLen = 10,
52 | sepLen = 7,
53 | color = '#fff',
54 | lineWidth = 1
55 | } = (options || {});
56 |
57 | context.lineWidth = lineWidth;
58 | context.strokeStyle = color;
59 |
60 | context.beginPath();
61 | context.setLineDash([dashLen, sepLen]);
62 | context.moveTo(xFrom, yFrom);
63 | context.lineTo(xTo, yTo);
64 | context.stroke();
65 | context.setLineDash([]);
66 | }
67 |
68 | /**
69 | * Draw rectangle
70 | */
71 | function rect(x, y, w, h, options) {
72 | const {
73 | color = '#fff',
74 | fill = true,
75 | lineWidth = 1,
76 | } = (options || {});
77 |
78 | context.beginPath();
79 | context.lineWidth = lineWidth,
80 | context.strokeStyle = color;
81 | context.fillStyle = color;
82 | context.rect(x, y, w, h);
83 | context.closePath();
84 | fill ? context.fill() : context.stroke();
85 | };
86 |
87 | /**
88 | * Draw text.
89 | */
90 | const text = (x, y, text, options = {}) => {
91 | const {
92 | color = '#fff',
93 | size = 14,
94 | align = 'center',
95 | baseline = 'bottom',
96 | } = options;
97 |
98 | context.font = `${size}px sans-serif`;
99 | context.textBaseline = baseline;
100 | context.textAlign = align;
101 | context.fillStyle = color;
102 | context.fillText(text, x, y);
103 | };
--------------------------------------------------------------------------------
/utils/math.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Builtins
3 | */
4 | const max = Math.max;
5 | const min = Math.min;
6 | const ceil = Math.ceil;
7 | const floor = Math.floor;
8 | const round = Math.round;
9 | const cos = Math.cos;
10 | const pow = Math.pow;
11 | const abs = Math.abs;
12 | const sin = Math.sin;
13 |
14 | /**
15 | * Constants
16 | */
17 | const PI = Math.PI;
18 | const TWO_PI = PI * 2;
19 |
20 | /**
21 | * Linear Congruential Generator
22 | */
23 | const LCGMultiplier = 1664525;
24 | const LCGIncrement = 1013904223;
25 | const LCGModulus = pow(2, 32);
26 |
27 | let LCGSeed = Date.now();
28 | function setRandomSeed(seed) {
29 | LCGSeed = seed;
30 | }
31 |
32 | function srandInt() {
33 | return LCGSeed = (LCGMultiplier * LCGSeed + LCGIncrement) % LCGModulus;
34 | }
35 |
36 | function srand() {
37 | return srandInt() / LCGModulus;
38 | }
39 |
40 | function srandRange(min, max) {
41 | return srand() * (max - min) + min;
42 | }
43 |
44 | function srandRangeInt(min, max) {
45 | return round(srandRange(min,max));
46 | }
47 |
48 | /**
49 | * Filter methods
50 | */
51 | function fadeFilter(t) {
52 | return t * t * t * (t * (t * 6 - 15) + 10);
53 | }
54 |
55 | function cosFilter(t) {
56 | return (1 - cos(t * PI)) * 0.5;
57 | }
58 |
59 | function smoothStepFilter(t) {
60 | return t * t * (3 - 2 * t);
61 | }
62 |
63 | /**
64 | * Output filter methods.
65 | */
66 | function turbulenceOFilter(t) {
67 | return abs((2 * t) - 1);
68 | }
69 |
70 | /**
71 | * Interpolation
72 | */
73 | function lerp(a, b, t) {
74 | return a*(1-t) + b*t;
75 | }
76 |
77 | function bilerp(c00, c10, c01, c11, tx, ty) {
78 | return lerp(
79 | lerp(c00, c10, tx),
80 | lerp(c01, c11, tx),
81 | ty
82 | );
83 | }
84 |
85 | /**
86 | * Map number n which between the range a..b to another range x..y
87 | */
88 | function map(currFrom, currTo, targetFrom, targetTo, n) {
89 | return ((n - currFrom) / (currTo - currFrom) * (targetTo - targetFrom)) + targetFrom;
90 | }
91 |
92 | /**
93 | * Constrain number between min max.
94 | */
95 | function constrain(low, high, n) {
96 | return max(min(n, high), low);
97 | }
--------------------------------------------------------------------------------
/utils/misc.js:
--------------------------------------------------------------------------------
1 | function id(t) {
2 | return t;
3 | }
4 |
5 | function first(...args) {
6 | for (let i = 0; i < args.length; i++) {
7 | if (typeof args[i] !== 'undefined') {
8 | return args[i];
9 | }
10 | }
11 | }
12 |
13 | function flat(arr) {
14 | return arr.flat ? arr.flat() : arr.reduce((a, b) => a.concat(b), []);
15 | }
--------------------------------------------------------------------------------
/utils/worker.js:
--------------------------------------------------------------------------------
1 | function run(fn, globals, done) {
2 | const now = debugMode ? performance.now() : 0;
3 | const worker = new Worker(URL.createObjectURL(new Blob([`
4 | // Utils
5 | const PI = Math.PI;
6 | const TWO_PI = PI * 2;
7 | const max = Math.max;
8 | const min = Math.min;
9 | const ceil = Math.ceil;
10 | const floor = Math.floor;
11 | const round = Math.round;
12 | const cos = Math.cos;
13 | const pow = Math.pow;
14 | const abs = Math.abs;
15 | const sin = Math.sin;
16 | const LCGMultiplier = ${LCGMultiplier};
17 | const LCGIncrement = ${LCGIncrement};
18 | const LCGModulus = ${LCGModulus};
19 | let LCGSeed = ${LCGSeed};
20 | ${setRandomSeed};
21 | ${srandInt};
22 | ${srand};
23 | ${srandRange};
24 | ${fadeFilter};
25 | ${cosFilter};
26 | ${smoothStepFilter};
27 | ${turbulenceOFilter};
28 | ${lerp};
29 | ${bilerp};
30 | ${map};
31 | ${constrain};
32 | ${id};
33 | ${first};
34 | ${flat};
35 |
36 | // Globals
37 | let speed = ${speed};
38 | let offset = ${offset};
39 | let amplitude = ${amplitude};
40 | let frequency = ${frequency};
41 | let octave = ${octave};
42 | let lacunarity = ${lacunarity};
43 | let gain = ${gain};
44 | let seed = ${seed};
45 | let syncOffsetsXY = ${syncOffsetsXY};
46 |
47 | // Value Noise 2D
48 | const VALUE_NOISE_2D_DEFAULT_MAX_VERTICES = ${VALUE_NOISE_2D_DEFAULT_MAX_VERTICES};
49 | const VALUE_NOISE_2D_DEFAULT_MAX_VERTICES_MASK = ${VALUE_NOISE_2D_DEFAULT_MAX_VERTICES_MASK};
50 | const ValueNoise2DPermutationTable = ${JSON.stringify(ValueNoise2DPermutationTable)};
51 | const ValueNoise2DRandomsBySeed = ${JSON.stringify(ValueNoise2DRandomsBySeed)};
52 | ${ValueNoise2D};
53 |
54 | // Perlin Noise 2D
55 | const PERLIN_NOISE_2D_DEFAULT_MAX_VERTICES = ${PERLIN_NOISE_2D_DEFAULT_MAX_VERTICES};
56 | const PERLIN_NOISE_2D_DEFAULT_MAX_VERTICES_MASK = ${PERLIN_NOISE_2D_DEFAULT_MAX_VERTICES_MASK};
57 | let PerlinNoise2DPermutationTableBase = ${JSON.stringify(PerlinNoise2DPermutationTableBase)};
58 | let PerlinNoise2DPermutationTableBySeed = ${JSON.stringify(PerlinNoise2DPermutationTableBySeed)};
59 | ${getPerlinNoise2dPermutationTableBySeed};
60 | ${Perlin2DGradientDotProd};
61 | ${PerlinNoise2D};
62 |
63 | // State
64 | const visualisationFn = ${getCurrentState().visualisationFn};
65 | const methodFn = ${getCurrentState().methodFn};
66 | const filterFn = ${getCurrentState().filterFn};
67 | const outputFilterFn = ${getCurrentState().outputFilterFn};
68 |
69 | // Initialize
70 | setRandomSeed(seed);
71 |
72 | // Supplied Globals
73 | ${Object.keys(globals).map(name => `const ${name} = ${globals[name]};`).join('\n')}
74 |
75 | // Run fn.
76 | postMessage((${fn})());
77 | `])));
78 | debugMode && console.log(`Worker prepared, took ${performance.now() - now}ms.`);
79 |
80 | worker.onmessage = ({data}) => {
81 | debugMode && console.log(`Worker job completed, took ${performance.now() - now}ms.`);
82 | worker.terminate();
83 | done(data);
84 | };
85 | }
--------------------------------------------------------------------------------
/visualisations/1d-ball.js:
--------------------------------------------------------------------------------
1 | function draw1DBall({
2 | getValueFn,
3 | offsetX,
4 | offsetY,
5 | width,
6 | height,
7 | color = '#f0f0f0',
8 | }) {
9 | context.clearRect(offsetX, height-1, offsetX + width, 3);
10 | dashedLine(offsetX, height, offsetX + width, height, {color:'#333'});
11 |
12 | const y = getValueFn(0);
13 |
14 | const radius = 20;
15 | const xPos = offsetX + (width / 2);
16 | const yPos = offsetY + height - radius/2 - y;
17 |
18 | circle(xPos, yPos, radius, {color});
19 | }
--------------------------------------------------------------------------------
/visualisations/1d-color-gradient.js:
--------------------------------------------------------------------------------
1 | function draw1DColorGradient({
2 | getValueFn,
3 | offsetX,
4 | offsetY,
5 | width,
6 | height
7 | }) {
8 | for (let x = 0; x < width - 100; x++) {
9 | const xPos = offsetX + x;
10 |
11 | const r = floor(255 * getValueFn(x + 2/frequency) / amplitude);
12 | const g = floor(255 * getValueFn(x + 64/frequency) / amplitude);
13 | const b = floor(255 * getValueFn(x + 128/frequency) / amplitude);
14 |
15 | line(xPos + 50, offsetY + 50, xPos + 50, offsetY + 150, {color: `rgb(${r}, ${g}, ${b})`});
16 | }
17 | }
--------------------------------------------------------------------------------
/visualisations/1d-colorful-ball.js:
--------------------------------------------------------------------------------
1 | function draw1DColorfulBall({
2 | getValueFn,
3 | offsetX,
4 | offsetY,
5 | width,
6 | height,
7 | color = '#f0f0f0',
8 | }) {
9 | context.clearRect(offsetX, height-1, offsetX + width, 3);
10 | dashedLine(offsetX, height, offsetX + width, height, {color:'#333'});
11 |
12 | const y = getValueFn(0);
13 |
14 | const radius = 20;
15 | const xPos = offsetX + (width / 2);
16 | const yPos = offsetY + height - radius/2 - y;
17 |
18 | const r = floor(255 * getValueFn(y + 2/frequency) / amplitude);
19 | const g = floor(255 * getValueFn(y + 64/frequency) / amplitude);
20 | const b = floor(255 * getValueFn(y + 128/frequency / amplitude));
21 |
22 | circle(xPos, yPos, radius, {color: `rgb(${r}, ${g}, ${b})`});
23 | }
--------------------------------------------------------------------------------
/visualisations/1d-colorful-line.js:
--------------------------------------------------------------------------------
1 | function draw1DColorfulLine({
2 | getValueFn,
3 | offsetX,
4 | offsetY,
5 | width,
6 | height
7 | }) {
8 | context.clearRect(offsetX, height-1, offsetX + width, 3);
9 | dashedLine(offsetX, height, offsetX + width, height, {color:'#333'});
10 |
11 | for (let x = 0; x < width; x++) {
12 | const y = getValueFn(x);
13 |
14 | const xPos = offsetX + x;
15 | const yPos = offsetY + height - y;
16 |
17 | const r = floor(255 * getValueFn(x + 2/frequency)) / amplitude;
18 | const g = floor(255 * getValueFn(x + 64/frequency)) / amplitude;
19 | const b = floor(255 * getValueFn(x + 128/frequency)) / amplitude;
20 |
21 | line(xPos, yPos, xPos, yPos - 1, {color: `rgb(${r}, ${g}, ${b})`});
22 | }
23 | }
--------------------------------------------------------------------------------
/visualisations/1d-colorful-triangle.js:
--------------------------------------------------------------------------------
1 | function draw1DColorfulTriangle({
2 | getValueFn,
3 | offsetX,
4 | offsetY,
5 | width,
6 | height
7 | }) {
8 | const a = getValueFn(offsetX + 2/frequency) / amplitude;
9 | const b = getValueFn(offsetX + 64/frequency) / amplitude;
10 | const c = getValueFn(offsetX + 128/frequency) / amplitude;
11 |
12 | const x1 = offsetX + floor(width * a);
13 | const y1 = offsetY + floor(height * b);
14 |
15 | const x2 = offsetX + floor(width * b);
16 | const y2 = offsetY + floor(height * c);
17 |
18 | const x3 = offsetX + floor(width * c);
19 | const y3 = offsetY + floor(height * a);
20 |
21 | // the triangle
22 | context.beginPath();
23 | context.moveTo(x1, y1);
24 | context.lineTo(x2, y2);
25 | context.lineTo(x3, y3);
26 | context.closePath();
27 |
28 | // the outline
29 | context.lineWidth = 3;
30 | context.strokeStyle = '#fff';
31 | context.stroke();
32 |
33 | // the fill color
34 | context.fillStyle = `rgb(
35 | ${map(0, 1, 0, 255, a)},
36 | ${map(0, 1, 0, 255, b)},
37 | ${map(0, 1, 0, 255, c)}
38 | )`;
39 | context.fill();
40 |
41 | circle(x1, y1, 10, {color:'#f00'});
42 | circle(x2, y2, 10, {color:'#0f0'});
43 | circle(x3, y3, 10, {color:'#00f'});
44 | }
--------------------------------------------------------------------------------
/visualisations/1d-line.js:
--------------------------------------------------------------------------------
1 | function draw1DLine({
2 | getValueFn,
3 | offsetX,
4 | offsetY,
5 | width,
6 | height,
7 | color = '#f0f0f0',
8 | }) {
9 | context.clearRect(offsetX, height-1, offsetX + width, 3);
10 | dashedLine(offsetX, height, offsetX + width, height, {color:'#333'});
11 |
12 | for (let x = 0; x < width; x++) {
13 | const y = getValueFn(x);
14 |
15 | const xPos = offsetX + x;
16 | const yPos = offsetY + height - y;
17 |
18 | line(xPos, yPos, xPos, yPos - 1, {color});
19 | }
20 | }
--------------------------------------------------------------------------------
/visualisations/1d-radius-ball.js:
--------------------------------------------------------------------------------
1 | function draw1DRadiusBall({
2 | getValueFn,
3 | offsetX,
4 | offsetY,
5 | width,
6 | height,
7 | color = '#f0f0f0',
8 | }) {
9 | context.clearRect(offsetX, height-1, offsetX + width, 3);
10 | dashedLine(offsetX, height, offsetX + width, height, {color:'#333'});
11 |
12 | const y = getValueFn(0) / amplitude;
13 |
14 | const maxRadius = 200;
15 | const radius = y * maxRadius;
16 | const xPos = offsetX + width/2;
17 | const yPos = offsetY + height/2;
18 |
19 | circle(xPos, yPos, radius, {color});
20 | }
--------------------------------------------------------------------------------
/visualisations/1d-rgb-lines.js:
--------------------------------------------------------------------------------
1 | function draw1DRGBLines({
2 | getValueFn,
3 | offsetX,
4 | offsetY,
5 | width,
6 | height
7 | }) {
8 | context.clearRect(offsetX, height-1, offsetX + width, 3);
9 | dashedLine(offsetX, height, offsetX + width, height, {color:'#333'});
10 |
11 | for (let x = 0; x < width; x++) {
12 | const xPos = offsetX + x;
13 | const yPosBase = offsetY + height;
14 |
15 | const rV = getValueFn(x + 2/frequency) / amplitude;
16 | const gV = getValueFn(x + 64/frequency) / amplitude;
17 | const bV = getValueFn(x + 128/frequency) / amplitude;
18 |
19 | const r = floor(255 * rV);
20 | const g = floor(255 * gV);
21 | const b = floor(255 * bV);
22 |
23 | const rY = rV * amplitude;
24 | const gY = gV * amplitude;
25 | const bY = bV * amplitude;
26 |
27 | line(xPos, 100, xPos, 101, {color: `rgb(${r}, ${g}, ${b})`});
28 | line(xPos, yPosBase - rY, xPos, yPosBase - rY - 1, {color: `rgb(${r}, ${0}, ${0})`});
29 | line(xPos, yPosBase - gY, xPos, yPosBase - gY - 1, {color: `rgb(${0}, ${g}, ${0})`});
30 | line(xPos, yPosBase - bY, xPos, yPosBase - bY - 1, {color: `rgb(${0}, ${0}, ${b})`});
31 | }
32 | }
--------------------------------------------------------------------------------
/visualisations/2d-colorful-image.js:
--------------------------------------------------------------------------------
1 | function draw2DColorfulImage({
2 | getValueFn,
3 | offsetX,
4 | offsetY,
5 | width,
6 | height
7 | }) {
8 | height -= 100;
9 | width -= 200;
10 |
11 | context.clearRect(0, 0, screenWidth, screenHeight);
12 | text(offsetX/2 + screenWidth/2, screenHeight/2, 'Calculating...', {color: '#666'});
13 |
14 | const calculate = () => {
15 | const result = [];
16 | const xPosBase = offsetX + 100;
17 | const yPosBase = offsetY + height + 100;
18 |
19 | let maxValue = -Infinity;
20 | let minValue = Infinity;
21 |
22 | for (let y = 0; y < height; y++) {
23 | for (let x = 0; x < width; x++) {
24 | const r = map(0, amplitude, 0, 255, getValueFn(x + 2/frequency, y + 2/frequency));
25 | const g = map(0, amplitude, 0, 255, getValueFn(x + 64/frequency, y + 64/frequency));
26 | const b = map(0, amplitude, 0, 255, getValueFn(x + 128/frequency, y + 128/frequency));
27 |
28 | maxValue = max(maxValue, r, g, b);
29 | minValue = min(minValue, r, g, b);
30 |
31 | result.push([xPosBase + x, yPosBase - y, r, g, b]);
32 | }
33 | }
34 |
35 | result.forEach(r => {
36 | r[2] = map(minValue, maxValue, 0, 255, r[2]);
37 | r[3] = map(minValue, maxValue, 0, 255, r[3]);
38 | r[4] = map(minValue, maxValue, 0, 255, r[4]);
39 | });
40 |
41 | return result;
42 | }
43 |
44 | run(calculate, {getValueFn, height, width, offsetX, offsetY}, result => {
45 | context.clearRect(0, 0, screenWidth, screenHeight);
46 |
47 | result.forEach(([xPos, yPos, r, g, b]) => {
48 | context.fillStyle = `rgb(${r}, ${g}, ${b})`;
49 | context.fillRect(xPos, yPos, 1, 1);
50 | });
51 | });
52 | }
--------------------------------------------------------------------------------
/visualisations/2d-colorful-marble-image.js:
--------------------------------------------------------------------------------
1 | function draw2DColorfulMarbleImage({
2 | getValueFn,
3 | offsetX,
4 | offsetY,
5 | width,
6 | height
7 | }) {
8 | height -= 100;
9 | width -= 200;
10 |
11 | context.clearRect(0, 0, screenWidth, screenHeight);
12 | text(offsetX/2 + screenWidth/2, screenHeight/2, 'Calculating...', {color: '#666'});
13 |
14 | const calculate = () => {
15 | const result = [];
16 | const xPosBase = offsetX + 100;
17 | const yPosBase = offsetY + height + 100;
18 |
19 | let maxValue = -Infinity;
20 | let minValue = Infinity;
21 |
22 | for (let y = 0; y < height; y++) {
23 | for (let x = 0; x < width; x++) {
24 | const r = map(0, amplitude, 0, 255, getValueFn(x + 2/frequency, y + 2/frequency));
25 | const g = map(0, amplitude, 0, 255, getValueFn(x + 64/frequency, y + 64/frequency));
26 | const b = map(0, amplitude, 0, 255, getValueFn(x + 128/frequency, y + 128/frequency));
27 |
28 | maxValue = max(maxValue, r, g, b);
29 | minValue = min(minValue, r, g, b);
30 |
31 | result.push([xPosBase + x, yPosBase - y, x, r, g, b]);
32 | }
33 | }
34 |
35 | result.forEach(rs => {
36 | const [,,x,rV,gV,bV] = rs;
37 |
38 | const rI = map(minValue, maxValue, 0, 1, rV);
39 | const gI = map(minValue, maxValue, 0, 1, gV);
40 | const bI = map(minValue, maxValue, 0, 1, bV);
41 |
42 | const r = (sin((x + rI * 100) * 2 * PI / 200) + 1) / 2;
43 | const g = (sin((x + gI * 100) * 2 * PI / 200) + 1) / 2;
44 | const b = (sin((x + bI * 100) * 2 * PI / 200) + 1) / 2;
45 |
46 | rs[3] = map(0, 1, 0, 255, r);
47 | rs[4] = map(0, 1, 0, 255, g);
48 | rs[5] = map(0, 1, 0, 255, b);
49 | });
50 |
51 | return result;
52 | }
53 |
54 | run(calculate, {getValueFn, height, width, offsetX, offsetY}, result => {
55 | context.clearRect(0, 0, screenWidth, screenHeight);
56 |
57 | result.forEach(([xPos, yPos, , r, g, b]) => {
58 | context.fillStyle = `rgb(${r}, ${g}, ${b})`;
59 | context.fillRect(xPos, yPos, 1, 1);
60 | });
61 | });
62 | }
--------------------------------------------------------------------------------
/visualisations/2d-colorful-wood-image.js:
--------------------------------------------------------------------------------
1 | function draw2DColorfulWoodImage({
2 | getValueFn,
3 | offsetX,
4 | offsetY,
5 | width,
6 | height
7 | }) {
8 | height -= 100;
9 | width -= 200;
10 |
11 | context.clearRect(0, 0, screenWidth, screenHeight);
12 | text(offsetX/2 + screenWidth/2, screenHeight/2, 'Calculating...', {color: '#666'});
13 |
14 | const calculate = () => {
15 | const result = [];
16 | const xPosBase = offsetX + 100;
17 | const yPosBase = offsetY + height + 100;
18 |
19 | let maxValue = -Infinity;
20 | let minValue = Infinity;
21 |
22 | for (let y = 0; y < height; y++) {
23 | for (let x = 0; x < width; x++) {
24 | const r = map(0, amplitude, 0, 255, getValueFn(x + 2/frequency, y + 2/frequency));
25 | const g = map(0, amplitude, 0, 255, getValueFn(x + 64/frequency, y + 64/frequency));
26 | const b = map(0, amplitude, 0, 255, getValueFn(x + 128/frequency, y + 128/frequency));
27 |
28 | maxValue = max(maxValue, r, g, b);
29 | minValue = min(minValue, r, g, b);
30 |
31 | result.push([xPosBase + x, yPosBase - y, r, g, b]);
32 | }
33 | }
34 |
35 | result.forEach(r => {
36 | const gR = map(minValue, maxValue, 0, 1, r[2]) * 10;
37 | const gG = map(minValue, maxValue, 0, 1, r[3]) * 10;
38 | const gB = map(minValue, maxValue, 0, 1, r[4]) * 10;
39 |
40 | r[2] = map(0, 1, 0, 255, gR - floor(gR));
41 | r[3] = map(0, 1, 0, 255, gG - floor(gG));
42 | r[4] = map(0, 1, 0, 255, gB - floor(gB));
43 | });
44 |
45 | return result;
46 | }
47 |
48 | run(calculate, {getValueFn, height, width, offsetX, offsetY}, result => {
49 | context.clearRect(0, 0, screenWidth, screenHeight);
50 |
51 | result.forEach(([xPos, yPos, r, g, b]) => {
52 | context.fillStyle = `rgb(${r}, ${g}, ${b})`;
53 | context.fillRect(xPos, yPos, 1, 1);
54 | });
55 | });
56 | }
--------------------------------------------------------------------------------
/visualisations/2d-grayscale-image.js:
--------------------------------------------------------------------------------
1 | function draw2DGrayscaleImage({
2 | getValueFn,
3 | offsetX,
4 | offsetY,
5 | width,
6 | height
7 | }) {
8 | height -= 100;
9 | width -= 200;
10 |
11 | context.clearRect(0, 0, screenWidth, screenHeight);
12 | text(offsetX/2 + screenWidth/2, screenHeight/2, 'Calculating...', {color: '#666'});
13 |
14 | const calculate = () => {
15 | const result = [];
16 | const xPosBase = offsetX + 100;
17 | const yPosBase = offsetY + height + 100;
18 |
19 | let maxValue = -Infinity;
20 | let minValue = Infinity;
21 |
22 | for (let y = 0; y < height; y++) {
23 | for (let x = 0; x < width; x++) {
24 | const value = getValueFn(x, y);
25 |
26 | maxValue = max(maxValue, value);
27 | minValue = min(minValue, value);
28 |
29 | result.push([xPosBase + x, yPosBase - y, value]);
30 | }
31 | }
32 |
33 | result.forEach(r => {
34 | r[2] = map(minValue, maxValue, 0, 255, r[2]);
35 | });
36 |
37 | return result;
38 | }
39 |
40 | run(calculate, {getValueFn, height, width, offsetX, offsetY}, result => {
41 | context.clearRect(0, 0, screenWidth, screenHeight);
42 |
43 | result.forEach(([xPos, yPos, value]) => {
44 | context.fillStyle = `rgb(${value}, ${value}, ${value})`;
45 | context.fillRect(xPos, yPos, 1, 1);
46 | });
47 | });
48 | }
--------------------------------------------------------------------------------
/visualisations/2d-marble-image.js:
--------------------------------------------------------------------------------
1 | function draw2DMarbleImage({
2 | getValueFn,
3 | offsetX,
4 | offsetY,
5 | width,
6 | height
7 | }) {
8 | height -= 100;
9 | width -= 200;
10 |
11 | context.clearRect(0, 0, screenWidth, screenHeight);
12 | text(offsetX/2 + screenWidth/2, screenHeight/2, 'Calculating...', {color: '#666'});
13 |
14 | const calculate = () => {
15 | const result = [];
16 |
17 | for (let y = 0; y < height; y++) {
18 | for (let x = 0; x < width; x++) {
19 | const value = multiply(getValueFn(x, y)[0], octave);
20 |
21 | const xPos = offsetX + x + 100;
22 | const yPos = offsetY + height + 100 - y;
23 |
24 | result.push({xPos, yPos, value, x});
25 | }
26 | }
27 |
28 | return result;
29 | }
30 |
31 | run(calculate, {getValueFn, height, width, offsetX, offsetY}, result => {
32 | context.clearRect(0, 0, screenWidth, screenHeight);
33 |
34 | let maxValue = 0;
35 |
36 | result.forEach(({value}) => {
37 | maxValue = Math.max(maxValue, value);
38 | });
39 |
40 | result.forEach(({xPos, yPos, value, x}) => {
41 | const i = map(0, maxValue, 0, 1, value);
42 | const val = (sin((x + i * 100) * 2 * PI / 200) + 1) / 2;
43 | const bw = map(0, 1, 0, 255, val);
44 |
45 | context.fillStyle = `rgb(${bw}, ${bw}, ${bw})`;
46 | context.fillRect(xPos, yPos, 1, 1);
47 | });
48 | });
49 | }
50 |
51 | function draw2DMarbleImage({
52 | getValueFn,
53 | offsetX,
54 | offsetY,
55 | width,
56 | height
57 | }) {
58 | height -= 100;
59 | width -= 200;
60 |
61 | text(offsetX/2 + screenWidth/2, screenHeight/2, 'Calculating...', {color: '#666'});
62 |
63 | const calculate = () => {
64 | const result = [];
65 | const xPosBase = offsetX + 100;
66 | const yPosBase = offsetY + height + 100;
67 |
68 | let maxValue = -Infinity;
69 | let minValue = Infinity;
70 |
71 | for (let y = 0; y < height; y++) {
72 | for (let x = 0; x < width; x++) {
73 | const value = getValueFn(x, y);
74 |
75 | maxValue = max(maxValue, value);
76 | minValue = min(minValue, value);
77 |
78 | result.push([xPosBase + x, yPosBase - y, x, value]);
79 | }
80 | }
81 |
82 | result.forEach(r => {
83 | const [,,x,value] = r;
84 | const i = map(minValue, maxValue, 0, 1, value);
85 | const val = (sin((x + i * 100) * 2 * PI / 200) + 1) / 2;
86 | r[3] = map(0, 1, 0, 255, val);
87 | });
88 |
89 | return result;
90 | }
91 |
92 | run(calculate, {getValueFn, height, width, offsetX, offsetY}, result => {
93 | context.clearRect(0, 0, screenWidth, screenHeight);
94 |
95 | result.forEach(([xPos, yPos, , value]) => {
96 | context.fillStyle = `rgb(${value}, ${value}, ${value})`;
97 | context.fillRect(xPos, yPos, 1, 1);
98 | });
99 | });
100 | }
--------------------------------------------------------------------------------
/visualisations/2d-terrain.js:
--------------------------------------------------------------------------------
1 | function draw2DTerrainImage({
2 | getValueFn,
3 | offsetX,
4 | offsetY,
5 | width,
6 | height,
7 | done
8 | }) {
9 | height -= 100;
10 | width -= 200;
11 |
12 | const tileSize = 4;
13 |
14 | const calculate = () => {
15 | const result = [];
16 | const xPosBase = offsetX + 100;
17 | const yPosBase = offsetY + height + 100;
18 |
19 | for (let y = 0; y < height/tileSize; y++) {
20 | for (let x = 0; x < width/tileSize; x++) {
21 | const value = getValueFn(x, y);
22 |
23 | result.push([
24 | xPosBase + (x * tileSize),
25 | yPosBase - (y * tileSize),
26 | value
27 | ]);
28 | }
29 | }
30 |
31 | return result;
32 | }
33 |
34 | run(calculate, {getValueFn, height, width, offsetX, offsetY, tileSize}, result => {
35 | context.clearRect(0, 0, screenWidth, screenHeight);
36 |
37 | result.forEach(([xPos, yPos, value]) => {
38 | let color;
39 |
40 | if (value < 0.4) { color = '#05B2DC'; } // sea
41 | else if (value < 0.42) { color = '#FFC857'; } // sand
42 | else if (value < 0.54) { color = '#52AA5E'; } // grass
43 | else { color = '#004F2D'; } // forest
44 |
45 | context.fillStyle = `${color}`;
46 | context.fillRect(xPos, yPos, tileSize, tileSize);
47 | });
48 |
49 | done();
50 | });
51 |
52 | return true;
53 | }
--------------------------------------------------------------------------------
/visualisations/2d-wood-image.js:
--------------------------------------------------------------------------------
1 | function draw2DWoodImage({
2 | getValueFn,
3 | offsetX,
4 | offsetY,
5 | width,
6 | height
7 | }) {
8 | height -= 100;
9 | width -= 200;
10 |
11 | context.clearRect(0, 0, screenWidth, screenHeight);
12 | text(offsetX/2 + screenWidth/2, screenHeight/2, 'Calculating...', {color: '#666'});
13 |
14 | const calculate = () => {
15 | const result = [];
16 | const xPosBase = offsetX + 100;
17 | const yPosBase = offsetY + height + 100;
18 |
19 | let maxValue = -Infinity;
20 | let minValue = Infinity;
21 |
22 | for (let y = 0; y < height; y++) {
23 | for (let x = 0; x < width; x++) {
24 | const value = getValueFn(x, y);
25 |
26 | maxValue = max(maxValue, value);
27 | minValue = min(minValue, value);
28 |
29 | result.push([xPosBase + x, yPosBase - y, value]);
30 | }
31 | }
32 |
33 | result.forEach(r => {
34 | const g = map(minValue, maxValue, 0, 1, r[2]) * 10;
35 | const val = g - floor(g);
36 | r[2] = map(0, 1, 0, 255, val);
37 | });
38 |
39 | return result;
40 | }
41 |
42 | run(calculate, {getValueFn, height, width, offsetX, offsetY}, result => {
43 | context.clearRect(0, 0, screenWidth, screenHeight);
44 |
45 | result.forEach(([xPos, yPos, value]) => {
46 | context.fillStyle = `rgb(${value}, ${value}, ${value})`;
47 | context.fillRect(xPos, yPos, 1, 1);
48 | });
49 | });
50 | }
--------------------------------------------------------------------------------