1 | ## Mandelbrot, ThreeJS, WebGL
|
2 |
|
3 | **Requires WebGL be enabled in your browser.**
|
4 |
|
5 | This is a one-pint demo of how ThreeJS and Smartdown can be used to understand the [Mandelbrot Set](https://en.wikipedia.org/wiki/Mandelbrot_set). Although Smartdown is not intended to be a programming language to build *apps* with, it is perfect for *Explorable Explanations* such as this Mandelbrot Explorer.
|
6 |
|
7 | *This is a draft. I want to add more prose and more parametrization.*
|
8 |
|
9 | ---
|
10 |
|
11 | [Zoom Out](:=zoom=zoom/2.0) [Zoom In](:=zoom=zoom*2.0) [Up](:=posY=posY+0.1/zoom) [Down](:=posY=posY-0.1/zoom) [Left](:=posX=posX-0.1/zoom) [Right](:=posX=posX+0.1/zoom) **AutoZoom** [](:Xbounciness)
|
12 |
|
13 | [Entire Set](:=posX=0.6;posY=0.0;zoom=1) [Region A](:=posX=0.570;posY=0.630;zoom=25) [Region B](:=posX=0.190;posY=0.650;zoom=50) [Region C](:=posX=0.04292602539062498;posY=0.6965332031250012;zoom=2048)
|
14 | [Set X/Y/Zoom](:=useCoordinates=1) [](:?coordinates)
|
15 |
|
16 | ```javascript/playable/autoplay
|
17 | var vertexShader =
|
18 | `
|
19 | precision highp float;
|
20 | uniform float zoom;
|
21 | varying vec2 pos;
|
22 |
|
23 | void main () {
|
24 | pos = position.xy / zoom;
|
25 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
26 | }
|
27 | `;
|
28 |
|
29 | var fragmentShader =
|
30 | `
|
31 | precision highp float;
|
32 | uniform float zoom;
|
33 | varying vec2 pos;
|
34 | uniform float posX;
|
35 | uniform float posY;
|
36 |
|
37 | void main () {
|
38 | vec2 basePos = vec2(pos.x - posX, pos.y - posY);
|
39 | vec2 fractal = basePos;
|
40 | int iterations = 20 + int(zoom);
|
41 |
|
42 | for (int i = 0; i < 1000; i++) {
|
43 | if (i > iterations) {
|
44 | break;
|
45 | }
|
46 | fractal = basePos + vec2(
|
47 | fractal.x * fractal.x - fractal.y * fractal.y,
|
48 | 2.0 * fractal.x * fractal.y
|
49 | );
|
50 | }
|
51 |
|
52 | // interpolate fractal color over position
|
53 | float x = abs(fractal.x);
|
54 | float y = abs(fractal.y);
|
55 | float z = (x + y);
|
56 | float magnitude = length(fractal);
|
57 | x = 2.0 * magnitude * x;
|
58 | y = 2.0 * magnitude * y;
|
59 | if (x > 1.0) {
|
60 | x = 1.0;
|
61 | }
|
62 | if (y > 1.0) {
|
63 | y = 1.0;
|
64 | }
|
65 | if (z > 1.0) {
|
66 | z = 1.0;
|
67 | }
|
68 | gl_FragColor = vec4(x, y, z, 1.0);
|
69 | }
|
70 | `;
|
71 |
|
72 | var that = this;
|
73 | var myDiv = that.div;
|
74 | myDiv.style.background = 'darkslateblue';
|
75 | myDiv.style['vertical-align'] = 'center';
|
76 | myDiv.style['text-align'] = 'center';
|
77 | myDiv.style['padding'] = '5px';
|
78 | var width = 450.0;
|
79 | var height = 350.0;
|
80 |
|
81 | smartdown.setVariable('posX', 0.6, 'number');
|
82 | smartdown.setVariable('posY', 0.0, 'number');
|
83 | smartdown.setVariable('zoom', 0.666, 'number');
|
84 | myDiv.innerHTML = '';
|
85 | var scene = new THREE.Scene();
|
86 | var camera = new THREE.PerspectiveCamera(75.0, width / height, 0.1, 1000.0);
|
87 | camera.position.z = 1;
|
88 |
|
89 | // create canvas
|
90 | var renderer = new THREE.WebGLRenderer();
|
91 | renderer.setSize(width, height);
|
92 | myDiv.appendChild(renderer.domElement);
|
93 |
|
94 | // create mandelbrot mesh
|
95 | var geometry = new THREE.PlaneGeometry(2.0, 2.0, 0.0);
|
96 | var material = new THREE.ShaderMaterial({
|
97 | uniforms: {
|
98 | zoom: { type: 'f', value: that.env.zoom },
|
99 | posX: { type: 'f', value: that.env.posX },
|
100 | posY: { type: 'f', value: that.env.posY }
|
101 | },
|
102 | vertexShader: vertexShader,
|
103 | fragmentShader: fragmentShader
|
104 | });
|
105 |
|
106 | var mesh = new THREE.Mesh(geometry, material);
|
107 | scene.add(mesh);
|
108 |
|
109 | var zoomOld = null;
|
110 | var posXOld = null;
|
111 | var posYOld = null;
|
112 | var bouncinessOld = null;
|
113 | var coordinatesOld = null;
|
114 |
|
115 | function render(delta) {
|
116 | var posX = that.env.posX;
|
117 | var posY = that.env.posY;
|
118 | var zoom = that.env.zoom;
|
119 | var bounciness = that.env.bounciness;
|
120 | var coordinates = that.env.coordinates;
|
121 | var coordinatesNew = `${posX}/${posY}/${zoom}`;
|
122 | var useCoordinates = that.env.useCoordinates;
|
123 |
|
124 | if (delta) {
|
125 | if (useCoordinates) {
|
126 | var coordinatesParts = coordinates.split('/');
|
127 | posX = Number(coordinatesParts[0]);
|
128 | posY = Number(coordinatesParts[1]);
|
129 | zoom = Number(coordinatesParts[2]);
|
130 | coordinatesNew = coordinates;
|
131 | that.env.useCoordinates = 0;
|
132 | that.env.posX = posX;
|
133 | that.env.posY = posY;
|
134 | that.env.zoom = zoom;
|
135 | }
|
136 |
|
137 | if (bounciness || bouncinessOld ||
|
138 | zoom !== zoomOld ||
|
139 | posX !== posXOld ||
|
140 | posY !== posYOld) {
|
141 | zoomOld = zoom;
|
142 | posXOld = posX;
|
143 | posYOld = posY;
|
144 | bouncinessOld = bounciness;
|
145 | coordinatesOld = coordinatesNew;
|
146 | smartdown.setVariable('coordinates', coordinatesNew);
|
147 | var bounce = bounciness ? (1.25 + Math.cos(delta / 2500)) : 1;
|
148 | mesh.material.uniforms.zoom.value = zoom * bounce;
|
149 | mesh.material.uniforms.posX.value = posX;
|
150 | mesh.material.uniforms.posY.value = posY;
|
151 | renderer.render(scene, camera);
|
152 | }
|
153 | requestAnimationFrame(render);
|
154 | }
|
155 | else {
|
156 | requestAnimationFrame(render);
|
157 | }
|
158 | }
|
159 |
|
160 | render();
|
161 | ```
|
162 |
|
163 | ---
|
164 |
|
165 | [Back to Home](:@Home)
|
166 |
|