Pim Schreurs commited on
Commit
8194362
·
1 Parent(s): 33be52b

Modernize the whole codebase; improve rendering

Browse files

Previously it was hacked together and using an out-of-date three.js
version. Besides a cleaner codebase this commit also includes a better
bloom (based on the UnrealBloom which can be found in three.js's
examples) and fixes some rendering issues on some mobile devices.

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .babelrc +6 -0
  2. .editorconfig +9 -0
  3. .eslintignore +1 -0
  4. .eslintrc.js +16 -0
  5. .gitignore +2 -0
  6. accretion_disk.png → assets/accretion_disk.png +0 -0
  7. galaxy1.png → assets/galaxy1.png +0 -0
  8. galaxy2.png → assets/galaxy2.png +0 -0
  9. saturn.jpg → assets/saturn.jpg +0 -0
  10. saturnrings.png → assets/saturnrings.png +0 -0
  11. index.html +10 -373
  12. js/Detector.js +0 -68
  13. js/Player.js +0 -153
  14. js/Simulation.js +0 -345
  15. js/controls/DeviceOrientationControls.js +0 -88
  16. js/controls/KeyboardControls.js +0 -324
  17. js/controls/MobileDeviceControls.js +0 -89
  18. js/main.js +0 -13
  19. js/postprocessing/BloomPass.js +0 -115
  20. js/postprocessing/ConvolutionShader.js +0 -101
  21. js/postprocessing/CopyShader.js +0 -46
  22. js/postprocessing/EffectComposer.js +0 -135
  23. js/postprocessing/MaskPass.js +0 -86
  24. js/postprocessing/RenderPass.js +0 -51
  25. js/postprocessing/ShaderPass.js +0 -58
  26. js/three.min.js +0 -0
  27. package-lock.json +0 -0
  28. package.json +50 -0
  29. src/Player.js +130 -0
  30. src/Simulation.js +192 -0
  31. src/SimulationRenderer.js +231 -0
  32. src/Teleporter.js +55 -0
  33. src/Ui.js +68 -0
  34. src/controls/ControlsBase.js +10 -0
  35. src/controls/ControlsManager.js +52 -0
  36. src/controls/DeviceOrientationControls.js +64 -0
  37. src/controls/KeyboardControls.js +333 -0
  38. src/controls/MobileDeviceControls.js +37 -0
  39. src/controls/TouchControls.js +80 -0
  40. src/main.js +11 -0
  41. src/postprocessing/CopyShader.js +14 -0
  42. src/postprocessing/EffectComposer.js +66 -0
  43. src/postprocessing/LuminosityHighPassShader.js +21 -0
  44. src/postprocessing/Pass.js +30 -0
  45. src/postprocessing/RenderPass.js +23 -0
  46. src/postprocessing/UnrealBloomPass.js +298 -0
  47. src/shaders/bloomComposite.glsl +24 -0
  48. src/shaders/copy.glsl +9 -0
  49. src/shaders/luminosityHighPass.glsl +20 -0
  50. src/shaders/seperableBlur.glsl +26 -0
.babelrc ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "presets": [
3
+ ["env", { "modules": false }],
4
+ "stage-3"
5
+ ]
6
+ }
.editorconfig ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ indent_style = space
6
+ indent_size = 2
7
+ end_of_line = lf
8
+ insert_final_newline = true
9
+ trim_trailing_whitespace = true
.eslintignore ADDED
@@ -0,0 +1 @@
 
 
1
+ /dist/
.eslintrc.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ root: true,
3
+ parserOptions: {
4
+ parser: 'babel-eslint'
5
+ },
6
+ env: {
7
+ browser: true
8
+ },
9
+ extends: [
10
+ 'standard'
11
+ ],
12
+ rules: {
13
+ 'brace-style': ['error', 'stroustrup'],
14
+ 'padded-blocks': ['error', { classes: 'always' }]
15
+ }
16
+ }
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ node_modules
2
+ dist
accretion_disk.png → assets/accretion_disk.png RENAMED
File without changes
galaxy1.png → assets/galaxy1.png RENAMED
File without changes
galaxy2.png → assets/galaxy2.png RENAMED
File without changes
saturn.jpg → assets/saturn.jpg RENAMED
File without changes
saturnrings.png → assets/saturnrings.png RENAMED
File without changes
index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <title>Interstellar - Interactive wormhole &amp; black hole</title>
5
  <meta charset="utf-8">
6
- <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
7
  <style>
8
  body {
9
  background:#000;
@@ -19,13 +19,14 @@
19
  }
20
 
21
  #webgl-error {
22
- display: block;
23
  text-align: center;
24
  position: fixed;
25
  left: 50%;
26
  top: 50%;
27
  -webkit-transform: translate(-50%, -50%);
28
  transform: translate(-50%, -50%);
 
29
  }
30
 
31
  #webgl-error a {
@@ -145,6 +146,11 @@
145
 
146
  <body>
147
 
 
 
 
 
 
148
  <div id="info">
149
  <h3 class="no-pointer-events">Controls:</h3>
150
  <div id="keyboard-controls" class="no-pointer-events">
@@ -172,7 +178,7 @@
172
  </div>
173
 
174
  <div id="version">
175
- <a target="_blank" href="https://github.com/sirxemic/Interstellar">version 1.5</a>
176
  </div>
177
 
178
  <div class="ui-toggle">
@@ -182,376 +188,7 @@
182
  <div id="container"></div>
183
  <div id="loading">Loading...</div>
184
 
185
- <script type="x-shader/x-fragment" id="fragmentShader">
186
- #define ID_SATURN 0
187
- #define ID_SATURN_RING 1
188
- #define ID_GALAXY1 2
189
- #define ID_GALAXY2 3
190
- #define ID_BLACKHOLE 4
191
- #define ID_BLACKHOLE_DISK 5
192
- #define ID_PLANET 6
193
-
194
- varying vec4 rayDir;
195
-
196
- uniform vec3 lightDirection;
197
-
198
- uniform vec3 planetDiffuse;
199
- uniform vec3 planetSpecular;
200
-
201
- uniform vec4 wormhole;
202
- uniform float wormholeGravityRatio;
203
- uniform vec4 blackhole;
204
-
205
- uniform vec4 saturn;
206
- uniform vec4 planet;
207
-
208
- uniform vec4 blackholeDisk;
209
- uniform vec4 saturnRings;
210
-
211
- uniform sampler2D texSaturn;
212
- uniform sampler2D texSaturnRings;
213
- uniform sampler2D texGalaxy1;
214
- uniform sampler2D texGalaxy2;
215
- uniform sampler2D texAccretionDisk;
216
-
217
- uniform float worldSize;
218
- uniform int startGalaxy;
219
-
220
- const float lightSpeed = 0.2;
221
-
222
- const float INFINITY = 1000000.0;
223
- const float GALAXY_EDGE = 10000.0;
224
-
225
- const float EPSILON = 0.0001;
226
- const float r2 = 0.0625;
227
-
228
- const float PI = 3.14159265359;
229
- const float TWOPI = 6.28318530718;
230
-
231
- float gravityWormhole = wormhole.w * lightSpeed * lightSpeed;
232
- float gravityBlackhole = blackhole.w * lightSpeed * lightSpeed;
233
-
234
- vec3 saturnColor(vec3 pos)
235
- {
236
- vec2 uv = vec2(
237
- 0.5 + atan(pos.z, pos.x) / TWOPI,
238
- 0.5 - asin(pos.y) / PI
239
- );
240
- return texture2D(texSaturn, uv).rgb;
241
- }
242
-
243
- vec3 panoramaColor(float n, vec3 pos)
244
- {
245
- vec2 uv = vec2(
246
- 0.5 - atan(pos.z, pos.x) / TWOPI,
247
- 0.5 - asin(pos.y) / PI
248
- );
249
- if (n < 0.5) return texture2D(texGalaxy1, uv).rgb;
250
- else return texture2D(texGalaxy2, uv).rgb;
251
- }
252
-
253
- vec3 accretionDiskColor(vec3 pos)
254
- {
255
- pos = pos - blackhole.xyz;
256
- float dist = length(pos);
257
-
258
- float r1 = length(blackholeDisk.xyz);
259
- float r2 = blackholeDisk.w;
260
-
261
- // Important! Scale radii according to black hole
262
- float v = clamp((dist - r1) / (r2 - r1), 0.0, 1.0);
263
-
264
- vec3 base = cross(blackholeDisk.xyz, vec3(0.0, 0.0, 1.0));
265
- float angle = acos(dot(normalize(base), normalize(pos)));
266
- if (dot(cross(base, pos), blackholeDisk.xyz) < 0.0) angle = -angle;
267
-
268
- float u = 0.5 - angle / TWOPI;
269
- return texture2D(texAccretionDisk, vec2(u, v)).rgb;
270
- }
271
-
272
- float sphereDistance(vec3 rayPosition, vec3 rayDirection, vec4 sphere)
273
- {
274
- vec3 v;
275
- float p, d;
276
- v = rayPosition - sphere.xyz;
277
- p = dot(rayDirection, v);
278
- d = p * p + sphere.w * sphere.w - dot(v, v);
279
-
280
- return d < 0.0 ? -1.0 : -p - sqrt(d);
281
- }
282
-
283
- vec4 saturnRingColor(vec3 pos)
284
- {
285
- pos = pos - saturn.xyz;
286
-
287
- float r1 = length(saturnRings.xyz);
288
- float r2 = saturnRings.w;
289
-
290
- // Important! Scale radii according to saturn
291
- float v = clamp((length(pos) - r1) / (r2 - r1), 0.0, 1.0);
292
-
293
- vec4 color = texture2D(texSaturnRings, vec2(0.5, v));
294
-
295
- float objectDistance = sphereDistance(saturn.xyz + pos, lightDirection, saturn);
296
- if (objectDistance > 0.0)
297
- {
298
- color.rgb *= 0.01;
299
- }
300
- color.rgb *= color.a;
301
-
302
- return vec4(color.rgb, -color.a);
303
- }
304
-
305
- float ringDistance(vec3 rayPosition, vec3 rayDirection, vec3 center, vec4 definition)
306
- {
307
- float r1 = length(definition.xyz);
308
- float r2 = definition.w;
309
- vec3 normal = definition.xyz / r1;
310
-
311
- float denominator = dot(rayDirection, normal);
312
- float constant = -dot(center, normal);
313
- float distanceToCenter;
314
- if (abs(denominator) < EPSILON)
315
- {
316
- return -1.0;
317
- }
318
- else
319
- {
320
- float t = -(dot(rayPosition, normal) + constant) / denominator;
321
- if (t < 0.0) return -1.0;
322
-
323
- vec3 intersection = rayPosition + t * rayDirection;
324
- distanceToCenter = length(intersection - center);
325
- if (distanceToCenter >= r1 && distanceToCenter <= r2)
326
- {
327
- return t;
328
- }
329
- return -1.0;
330
- }
331
- }
332
-
333
- vec3 computeShading(vec3 light, vec3 view, vec3 normal, vec3 diffuse, vec3 specular, vec3 ambient)
334
- {
335
- float lambertian = max(dot(light, normal), 0.0);
336
- vec3 reflectDir = reflect(-light, normal);
337
- float specAngle = max(dot(reflectDir, view), 0.0);
338
- float specularAmount = pow(specAngle, 4.0);
339
- return ambient + lambertian * diffuse + specularAmount * specular;
340
- }
341
-
342
- void testDistance(int i, float distance, inout float currentDistance, inout int currentObject)
343
- {
344
- if (distance >= EPSILON && distance < currentDistance)
345
- {
346
- currentDistance = distance;
347
- currentObject = i;
348
- }
349
- }
350
-
351
- vec3 raytrace(vec3 rayPosition, vec3 rayDirection)
352
- {
353
- float currentDistance = INFINITY;
354
- int currentObject = -1, prevObject = -1;
355
- float currentGalaxy = float(startGalaxy);
356
- vec3 currentPosition;
357
- vec3 normal;
358
-
359
- float stepSize, rayDistance;
360
- vec3 gravityVector, rayAccel;
361
- float objectDistance;
362
-
363
- vec4 color = vec4(0.0, 0.0, 0.0, 1.0);
364
-
365
- for (int i = 0; i < 100; i++)
366
- {
367
- currentDistance = INFINITY;
368
-
369
- // Bend the light towards the wormhole
370
- gravityVector = wormhole.xyz - rayPosition;
371
- rayDistance = length(gravityVector);
372
-
373
- // 0.86: rate of smaller steps when approaching wormhole
374
- stepSize = rayDistance - wormhole.w * 0.86;
375
-
376
- rayDistance -= wormhole.w * (1.0 - wormholeGravityRatio);
377
-
378
- float amount = wormholeGravityRatio / rayDistance;
379
- rayAccel = normalize(gravityVector) * gravityWormhole * amount * amount;
380
-
381
- if (currentGalaxy > 0.5)
382
- {
383
- // Bend the light towards the black hole
384
- gravityVector = blackhole.xyz - rayPosition;
385
- rayDistance = length(gravityVector);
386
-
387
- // 0.05: rate of smaller steps when approaching blackhole
388
- stepSize = min(stepSize, rayDistance - blackhole.w * 0.05);
389
-
390
- // rayAccel += normalize(gravityVector) * gravityBlackhole / (rayDistance * rayDistance)
391
- rayAccel += gravityVector * gravityBlackhole / (rayDistance * rayDistance * rayDistance);
392
- }
393
-
394
- if (length(rayAccel) > lightSpeed)
395
- {
396
- rayAccel = normalize(rayAccel) * lightSpeed;
397
- }
398
-
399
- rayDirection = normalize(rayDirection * lightSpeed + rayAccel * stepSize);
400
-
401
- if (stepSize <= 0.005)
402
- {
403
- currentObject = -1;
404
- break;
405
- }
406
-
407
- if (currentGalaxy < 0.5)
408
- {
409
- objectDistance = sphereDistance(rayPosition, rayDirection, saturn);
410
- testDistance(ID_SATURN, objectDistance, currentDistance, currentObject);
411
-
412
- objectDistance = ringDistance(rayPosition, rayDirection, saturn.xyz, saturnRings);
413
- testDistance(ID_SATURN_RING, objectDistance, currentDistance, currentObject);
414
-
415
- testDistance(ID_GALAXY1, GALAXY_EDGE, currentDistance, currentObject);
416
- }
417
- else
418
- {
419
- objectDistance = sphereDistance(rayPosition, rayDirection, planet);
420
- testDistance(ID_PLANET, objectDistance, currentDistance, currentObject);
421
-
422
- // Test against a bit smaller sphere due to precision errors
423
- objectDistance = sphereDistance(rayPosition, rayDirection, vec4(blackhole.xyz, blackhole.w * 0.93));
424
- testDistance(ID_BLACKHOLE, objectDistance, currentDistance, currentObject);
425
-
426
- objectDistance = ringDistance(rayPosition, rayDirection, blackhole.xyz, blackholeDisk);
427
- testDistance(ID_BLACKHOLE_DISK, objectDistance, currentDistance, currentObject);
428
-
429
- testDistance(ID_GALAXY2, GALAXY_EDGE, currentDistance, currentObject);
430
- }
431
-
432
- rayDistance = lightSpeed * stepSize;
433
-
434
- // Check if we hit any object, and if so, stop integrating
435
- if (currentObject != -1 && currentDistance <= rayDistance)
436
- {
437
- // But if it's something transparent, get its color, and continue
438
- if (currentObject == ID_BLACKHOLE_DISK)
439
- {
440
- currentPosition = rayPosition + rayDirection * currentDistance;
441
- color.rgb += accretionDiskColor(currentPosition).rgb * color.a;
442
- currentObject = -1;
443
- prevObject = ID_BLACKHOLE_DISK;
444
- }
445
- else if (currentObject == ID_SATURN_RING)
446
- {
447
- currentPosition = rayPosition + rayDirection * currentDistance;
448
- if (prevObject != ID_SATURN_RING)
449
- {
450
- color += saturnRingColor(currentPosition);
451
- }
452
- currentObject = -1;
453
- prevObject = ID_SATURN_RING;
454
-
455
- // Ensure we don't overstep and go through Saturn
456
- rayDistance = min(rayDistance, 0.9 * (length(saturnRings.xyz) - saturn.w));
457
- }
458
- else
459
- {
460
- break;
461
- }
462
- }
463
-
464
- float d = sphereDistance(rayPosition, rayDirection, wormhole);
465
- if (d > 0.0 && d < rayDistance)
466
- { // Ray goes through wormhole
467
- currentGalaxy = 1.0 - currentGalaxy;
468
- vec3 intersection = rayPosition + rayDirection * d;
469
- gravityVector = normalize(intersection - wormhole.xyz);
470
-
471
- rayPosition = 2.0 * wormhole.xyz - intersection;
472
- rayDirection = -reflect(rayDirection, gravityVector);
473
-
474
- rayPosition += rayDirection * d;
475
- }
476
- else
477
- {
478
- rayPosition += rayDirection * rayDistance;
479
- }
480
- }
481
-
482
- currentPosition = rayPosition + rayDirection * currentDistance;
483
-
484
- if (currentObject == ID_GALAXY1 || currentObject == ID_GALAXY2)
485
- {
486
- color.rgb += panoramaColor(currentGalaxy, rayDirection) * color.a;
487
- }
488
- else if (currentObject == ID_SATURN)
489
- {
490
- normal = (currentPosition - saturn.xyz) / saturn.w;
491
-
492
- vec3 diffuse = saturnColor(normal);
493
- vec3 specular = vec3(0.0);
494
- vec3 ambient = diffuse * 0.02;
495
-
496
- float objectDistance = ringDistance(currentPosition, lightDirection, saturn.xyz, saturnRings);
497
- if (objectDistance > 0.0)
498
- {
499
- diffuse *= 1.0 + saturnRingColor(currentPosition + lightDirection * objectDistance).a;
500
- }
501
-
502
- color.rgb += computeShading(lightDirection, -rayDirection, normal, diffuse, specular, ambient) * color.a;
503
- }
504
- else if (currentObject == ID_PLANET)
505
- {
506
- normal = (currentPosition - planet.xyz) / planet.w;
507
-
508
- // light direction for black hole-orbiting planet is towards the blackhole
509
- vec3 lightDirection2 = normalize(blackhole.xyz - planet.xyz);
510
-
511
- vec3 diffuse = planetDiffuse;
512
- vec3 specular = planetSpecular;
513
- vec3 ambient = vec3(0.0);
514
- color.rgb += computeShading(lightDirection2, -rayDirection, normal, diffuse, specular, ambient) * color.a;
515
- }
516
-
517
- return color.rgb;
518
- }
519
-
520
- void main()
521
- {
522
- gl_FragColor = vec4(raytrace(cameraPosition, normalize(rayDir.xyz)), 1.0);
523
- }
524
- </script>
525
- <script type="x-shader/x-vertex" id="vertexShader">
526
- uniform mat4 rayMatrix;
527
-
528
- varying vec4 rayDir;
529
-
530
- void main() {
531
- rayDir = rayMatrix * vec4(position.xy, 1.0, 0.0);
532
-
533
- gl_Position = vec4(position.xy, 0.0, 1.0);
534
- }
535
- </script>
536
-
537
- <script src="js/three.min.js"></script>
538
-
539
- <script src="js/postprocessing/ConvolutionShader.js"></script>
540
- <script src="js/postprocessing/CopyShader.js"></script>
541
- <script src="js/postprocessing/EffectComposer.js"></script>
542
- <script src="js/postprocessing/ShaderPass.js"></script>
543
- <script src="js/postprocessing/MaskPass.js"></script>
544
- <script src="js/postprocessing/RenderPass.js"></script>
545
- <script src="js/postprocessing/BloomPass.js"></script>
546
-
547
- <script src="js/controls/DeviceOrientationControls.js"></script>
548
- <script src="js/controls/KeyboardControls.js"></script>
549
- <script src="js/controls/MobileDeviceControls.js"></script>
550
-
551
- <script src="js/Detector.js"></script>
552
- <script src="js/Player.js"></script>
553
- <script src="js/Simulation.js"></script>
554
- <script src="js/main.js"></script>
555
 
556
  </body>
557
  </html>
 
3
  <head>
4
  <title>Interstellar - Interactive wormhole &amp; black hole</title>
5
  <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width">
7
  <style>
8
  body {
9
  background:#000;
 
19
  }
20
 
21
  #webgl-error {
22
+ display: none;
23
  text-align: center;
24
  position: fixed;
25
  left: 50%;
26
  top: 50%;
27
  -webkit-transform: translate(-50%, -50%);
28
  transform: translate(-50%, -50%);
29
+ z-index: 1;
30
  }
31
 
32
  #webgl-error a {
 
146
 
147
  <body>
148
 
149
+ <div id="webgl-error">
150
+ Your graphics card does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation">WebGL</a><br>
151
+ Find out how to get it <a href="http://get.webgl.org/">here</a>
152
+ </div>
153
+
154
  <div id="info">
155
  <h3 class="no-pointer-events">Controls:</h3>
156
  <div id="keyboard-controls" class="no-pointer-events">
 
178
  </div>
179
 
180
  <div id="version">
181
+ <a target="_blank" href="https://github.com/sirxemic/Interstellar">version 2.0.0</a>
182
  </div>
183
 
184
  <div class="ui-toggle">
 
188
  <div id="container"></div>
189
  <div id="loading">Loading...</div>
190
 
191
+ <script src="dist/build.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
193
  </body>
194
  </html>
js/Detector.js DELETED
@@ -1,68 +0,0 @@
1
- /**
2
- * @author alteredq / http://alteredqualia.com/
3
- * @author mr.doob / http://mrdoob.com/
4
- */
5
-
6
- var Detector = {
7
-
8
- canvas: !! window.CanvasRenderingContext2D,
9
- webgl: ( function () { try { var canvas = document.createElement( 'canvas' ); return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) ); } catch( e ) { return false; } } )(),
10
- workers: !! window.Worker,
11
- fileapi: window.File && window.FileReader && window.FileList && window.Blob,
12
-
13
- getWebGLErrorMessage: function () {
14
-
15
- var element = document.createElement( 'div' );
16
- element.id = 'webgl-error-message';
17
- /*
18
- element.style.fontFamily = 'monospace';
19
- element.style.fontSize = '13px';
20
- element.style.fontWeight = 'normal';
21
- element.style.textAlign = 'center';
22
- element.style.background = '#fff';
23
- element.style.color = '#000';
24
- element.style.padding = '1.5em';
25
- element.style.width = '400px';
26
- element.style.margin = '5em auto 0';
27
- */
28
-
29
- if ( ! this.webgl ) {
30
-
31
- element.innerHTML = window.WebGLRenderingContext ? [
32
- 'Your graphics card does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation">WebGL</a>.<br />',
33
- 'Find out how to get it <a href="http://get.webgl.org/">here</a>.'
34
- ].join( '\n' ) : [
35
- 'Your browser does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation">WebGL</a>.<br/>',
36
- 'Find out how to get it <a href="http://get.webgl.org/">here</a>.'
37
- ].join( '\n' );
38
-
39
- }
40
-
41
- return element;
42
-
43
- },
44
-
45
- addGetWebGLMessage: function ( parameters ) {
46
-
47
- var parent, id, element;
48
-
49
- parameters = parameters || {};
50
-
51
- parent = parameters.parent !== undefined ? parameters.parent : document.body;
52
- id = parameters.id !== undefined ? parameters.id : 'oldie';
53
-
54
- element = Detector.getWebGLErrorMessage();
55
- element.id = id;
56
-
57
- parent.appendChild( element );
58
-
59
- }
60
-
61
- };
62
-
63
- // browserify support
64
- if ( typeof module === 'object' ) {
65
-
66
- module.exports = Detector;
67
-
68
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/Player.js DELETED
@@ -1,153 +0,0 @@
1
- function Player()
2
- {
3
- this.object = new THREE.Object3D;
4
- this.eyes = new THREE.OrthographicCamera(-1, 1, -1, 1, 0, 1);
5
-
6
- this.velocity = new THREE.Vector3(0, 0, 0);
7
- this.eyeAngularVelocity = new THREE.Vector3;
8
-
9
- this.object.add(this.eyes);
10
-
11
- this.galaxy = 0;
12
-
13
- this.controls = [];
14
-
15
- this.teleportTargets = [];
16
- }
17
-
18
- Player.prototype = {
19
-
20
- addTeleportTarget: function(target) {
21
- this.teleportTargets.push(target)
22
- },
23
-
24
- lookAt: function(position)
25
- {
26
- // this.object.lookAt makes it look in the exact opposite direction, for some reason
27
- var lookAtMatrix = new THREE.Matrix4();
28
- lookAtMatrix.lookAt(this.object.position, position, this.object.up);
29
- this.object.quaternion.setFromRotationMatrix(lookAtMatrix);
30
- this.object.quaternion.multiply(this.eyes.quaternion.clone().inverse());
31
- },
32
-
33
- handleInput: function()
34
- {
35
- for (var i = 0; i < this.controls.length; i++)
36
- {
37
- this.controls[i].update();
38
- }
39
- },
40
-
41
- step: (function() {
42
-
43
- var prevPosition = new THREE.Vector3(),
44
- newVelocity = new THREE.Vector3(),
45
- acceleration = new THREE.Vector3(),
46
- gravityVector = new THREE.Vector3(),
47
- direction = new THREE.Vector3(),
48
- intersection = new THREE.Vector3(),
49
- axis = new THREE.Vector3(),
50
- rotation = new THREE.Quaternion(),
51
- temp = new THREE.Vector3(),
52
- ray = new THREE.Ray();
53
-
54
- return function(delta) {
55
- var wormholePosition = Simulation.wormholePositionSize,
56
- wormholeSize = Simulation.wormholePositionSize.w,
57
- wormholeGravityRatio = Simulation.wormholeGravityRatio,
58
- wormholeSphere = new THREE.Sphere(wormholePosition, wormholeSize);
59
-
60
- if (this.velocity.lengthSq() > 0.00001)
61
- {
62
- prevPosition.copy(this.object.position);
63
-
64
- // 1. Compute wormhole curvature/gravity.
65
- gravityVector.subVectors(wormholePosition, prevPosition);
66
- var rayDistance = gravityVector.length() - wormholeSize * (1 - wormholeGravityRatio);
67
- var amount = wormholeGravityRatio / rayDistance;
68
- acceleration.copy(gravityVector.normalize()).multiplyScalar(wormholeSize * this.velocity.lengthSq() * amount * amount);
69
-
70
- // Apply curvature to velocity
71
- newVelocity.copy(this.velocity).add(acceleration.multiplyScalar(delta));
72
-
73
- // Adjust new velocity (keep magnitude of old velocity)
74
- newVelocity.normalize().multiplyScalar(this.velocity.length());
75
-
76
- // Update the player accordingly
77
- this.object.position.addVectors(prevPosition, newVelocity.multiplyScalar(delta));
78
-
79
- // ...and orientation
80
- rotation.setFromUnitVectors(this.velocity.normalize(), newVelocity.normalize());
81
- this.object.quaternion.multiplyQuaternions(rotation, this.object.quaternion);
82
-
83
- this.velocity.copy(newVelocity);
84
-
85
- // 2. Check if we're going through the wormhole
86
- direction.copy(this.velocity).normalize();
87
-
88
- ray.set(prevPosition, direction);
89
-
90
- var distanceTravelledSq = direction.subVectors(this.object.position, prevPosition).lengthSq();
91
-
92
- var at = ray.intersectSphere(wormholeSphere, intersection);
93
- if (at && at.distanceToSquared(prevPosition) <= distanceTravelledSq)
94
- {
95
- // Rotate 180 degrees around axis pointing at exit point
96
- axis.subVectors(intersection, wormholePosition).normalize();
97
- rotation.setFromAxisAngle(axis, Math.PI);
98
- this.object.quaternion.multiplyQuaternions(rotation, this.object.quaternion);
99
- this.velocity.reflect(axis).multiplyScalar(-1);
100
-
101
- // Set new position a tiny bit outside mirrored intersection point
102
- this.object.position.copy(wormholePosition).add(temp.subVectors(wormholePosition, intersection).multiplyScalar(1.0001));
103
-
104
- this.galaxy = 1 - this.galaxy;
105
- }
106
- }
107
-
108
- rotation.set( this.eyeAngularVelocity.x * delta, this.eyeAngularVelocity.y * delta, this.eyeAngularVelocity.z * delta, 1 ).normalize();
109
- this.eyes.quaternion.multiply( rotation );
110
- };
111
- })(),
112
-
113
- update: function(delta)
114
- {
115
- this.handleInput();
116
- this.step(delta);
117
-
118
- // Object isn't actually part of a rendered scene, so we need to call this manually
119
- this.object.updateMatrixWorld(true);
120
- },
121
-
122
- getClosestTeleportIndex: function() {
123
- var minDistance = Infinity;
124
- var result = null;
125
- for (var i = 0; i < this.teleportTargets.length; i++) {
126
- var distance;
127
-
128
- if (this.teleportTargets[i].galaxy != this.galaxy) {
129
- distance =
130
- this.object.position.distanceTo(Simulation.wormholePositionSize) +
131
- this.teleportTargets[i].position.distanceTo(Simulation.wormholePositionSize);
132
- } else {
133
- distance = this.object.position.distanceTo(this.teleportTargets[i].position);
134
- }
135
-
136
- if (distance < minDistance) {
137
- minDistance = distance;
138
- result = i;
139
- }
140
- }
141
- return result;
142
- },
143
-
144
- teleport: function() {
145
- var nextIndex = (this.getClosestTeleportIndex() + 1) % this.teleportTargets.length;
146
- var teleportTarget = this.teleportTargets[nextIndex];
147
-
148
- this.object.position.copy(teleportTarget.position);
149
- this.lookAt(teleportTarget.lookAt);
150
- this.galaxy = teleportTarget.galaxy;
151
- }
152
-
153
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/Simulation.js DELETED
@@ -1,345 +0,0 @@
1
- var Simulation = {
2
- init: function init()
3
- {
4
- this.initScene();
5
- this.initGL();
6
- this.initDom();
7
- this.initPlayer();
8
- },
9
-
10
- initGL: function()
11
- {
12
- var self = this;
13
-
14
- // Init THREE.js stuff
15
- this.renderer = new THREE.WebGLRenderer();
16
- this.renderer.setSize(window.innerWidth, window.innerHeight);
17
- this.renderer.sortObjects = false;
18
-
19
- this.renderer.autoClear = false;
20
-
21
- this.quadScene = new THREE.Scene();
22
- this.quadCam = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
23
-
24
- this.quadScene.add(new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2), new THREE.ShaderMaterial({
25
- uniforms: this.uniforms,
26
-
27
- vertexShader: document.getElementById("vertexShader").textContent,
28
- fragmentShader: document.getElementById("fragmentShader").textContent,
29
- })));
30
-
31
- this.renderPasses = [
32
- new THREE.RenderPass(this.quadScene, this.quadCam),
33
- new THREE.BloomPass(1.25),
34
- new THREE.ShaderPass(THREE.CopyShader)
35
- ];
36
-
37
- this.renderPasses[this.renderPasses.length - 1].renderToScreen = true;
38
-
39
- this.composer = new THREE.EffectComposer(this.renderer);
40
-
41
- this.renderPasses.forEach(function(pass) {
42
- self.composer.addPass(pass);
43
- });
44
-
45
- this.rayMatrix = new THREE.Matrix4();
46
- },
47
-
48
- initDom: function()
49
- {
50
- var self = this;
51
-
52
- // Init some DOM stuff
53
- this.container = document.getElementById("container");
54
- this.container.appendChild(this.renderer.domElement);
55
-
56
- var uiToggle = document.querySelector(".ui-toggle input");
57
-
58
- var onUIToggle = function() {
59
- if (uiToggle.checked) {
60
- document.body.classList.add("no-ui");
61
- }
62
- else {
63
- document.body.classList.remove("no-ui");
64
- }
65
- uiToggle.blur();
66
- };
67
-
68
- uiToggle.addEventListener("change", onUIToggle);
69
-
70
- onUIToggle();
71
-
72
- var updateResolution = function()
73
- {
74
- var size = parseInt(document.querySelector("[name=resolution]:checked").value),
75
- width = Math.floor(window.innerWidth / size),
76
- height = Math.floor(window.innerHeight / size);
77
-
78
- self.composer.setSize(width, height);
79
- };
80
-
81
- this.zoom = 1;
82
-
83
- var onWindowResize = function()
84
- {
85
- self.renderer.setSize(window.innerWidth, window.innerHeight);
86
-
87
- self.updateView();
88
-
89
- updateResolution();
90
- };
91
-
92
- var onWheel = function(e) {
93
- e.preventDefault();
94
-
95
- var delta = e.delta || (e.deltaX + e.deltaY + e.deltaZ);
96
- if (delta < 0)
97
- {
98
- self.zoom *= 1.06;
99
- }
100
- else
101
- {
102
- self.zoom /= 1.06;
103
- }
104
-
105
- self.updateView();
106
- };
107
-
108
- window.addEventListener("resize", onWindowResize, false);
109
- onWindowResize();
110
-
111
- document.querySelector("#resolution").addEventListener("change", function(event) {
112
- updateResolution();
113
- event.target.blur();
114
- }, false);
115
-
116
- window.addEventListener("wheel", onWheel, false);
117
- },
118
-
119
- initScene: function()
120
- {
121
- this.wormholePositionSize = new THREE.Vector4(10, 0.0, -32, 0.8);
122
- this.blackholePositionSize = new THREE.Vector4(0.0, -250.0, 250.0, 12.5);
123
- this.saturnPositionSize = new THREE.Vector4(-14, 5, -40, 8.0);
124
- this.planetPositionSize = new THREE.Vector4(7.6, 62, -50, 0.08);
125
-
126
- this.planetPositionSize.x += this.blackholePositionSize.x;
127
- this.planetPositionSize.y += this.blackholePositionSize.y;
128
- this.planetPositionSize.z += this.blackholePositionSize.z;
129
-
130
- this.teleportTargets = [
131
- { position: new THREE.Vector3(10, -307, 454), lookAt: this.blackholePositionSize, galaxy: 1 },
132
- { position: new THREE.Vector3(7.2, -188, 199.6), lookAt: this.planetPositionSize, galaxy: 1 },
133
- { position: new THREE.Vector3(12.4, 3.3, -35.1), lookAt: this.wormholePositionSize, galaxy: 1 },
134
- { position: new THREE.Vector3(9.8, -4.6, -3.1), lookAt: this.wormholePositionSize, galaxy: 0 }
135
- ];
136
-
137
- // Ring definition - xyz is normal going through ring. Its magnitude determines inner radius.
138
- // w component determines outer radius
139
- this.blackholeDisk = new THREE.Vector4(-12, 12, 6, 150.0);
140
- this.saturnRings = new THREE.Vector4(0, 9.22, 0, 17.1);
141
-
142
- var numTexturesLoaded = 0;
143
- var textureCount = 0;
144
- var updateProgress = function() {
145
- numTexturesLoaded++;
146
- if (numTexturesLoaded == textureCount)
147
- {
148
- var el = document.getElementById("loading");
149
- el.parentElement.removeChild(el);
150
- Simulation.inited = true;
151
- }
152
- };
153
-
154
- this.wormholeGravityRatio = 0.25;
155
-
156
- this.uniforms = {
157
- "wormhole": { type: "v4", value: this.wormholePositionSize },
158
- "wormholeGravityRatio": { type: "f", value: this.wormholeGravityRatio },
159
- // 1 = like a black hole but with the mouth at the event horizon (big gravitational well)
160
- // 0 = completely flat space (no gravity at all)
161
- "blackhole": { type: "v4", value: this.blackholePositionSize },
162
-
163
- "saturn": { type: "v4", value: this.saturnPositionSize },
164
- "planet": { type: "v4", value: this.planetPositionSize },
165
-
166
- "blackholeDisk": { type: "v4", value: this.blackholeDisk },
167
- "saturnRings": { type: "v4", value: this.saturnRings },
168
-
169
- "planetDiffuse": { type: "v3", value: new THREE.Vector3(0.58,0.85,0.96) },
170
- "planetSpecular": { type: "v3", value: new THREE.Vector3(0.1,0.1,0.1) },
171
- "texSaturn": { type: "t", value: THREE.ImageUtils.loadTexture("saturn.jpg", null, updateProgress) },
172
- "texSaturnRings": { type: "t", value: THREE.ImageUtils.loadTexture("saturnrings.png", null, updateProgress) },
173
- "texGalaxy1": { type: "t", value: THREE.ImageUtils.loadTexture("galaxy1.png", null, updateProgress) },
174
- "texGalaxy2": { type: "t", value: THREE.ImageUtils.loadTexture("galaxy2.png", null, updateProgress) },
175
- "texAccretionDisk": { type: "t", value: THREE.ImageUtils.loadTexture("accretion_disk.png", null, updateProgress) },
176
-
177
- "lightDirection": { type: "v3", value: (new THREE.Vector3(-4, 2, 3)).normalize() },
178
-
179
- "rayMatrix": { type: "m4", value: new THREE.Matrix4() },
180
-
181
- "startGalaxy": { type: "i", value: 0 },
182
- "cameraPosition": { type: "v3" },
183
- };
184
-
185
- for (var uniform in this.uniforms)
186
- {
187
- if (this.uniforms[uniform].type == "t") textureCount++;
188
- }
189
-
190
- this.uniforms.texAccretionDisk.value.wrapS = THREE.RepeatWrapping
191
-
192
- // Some entities to calculate with
193
- this.wormholeSphere = new THREE.Sphere(this.wormholePositionSize, this.wormholePositionSize.w);
194
- },
195
-
196
- updateView: function()
197
- {
198
- var vx, vy;
199
- if (window.innerWidth > window.innerHeight)
200
- {
201
- vx = 1;
202
- vy = window.innerHeight / window.innerWidth;
203
- }
204
- else
205
- {
206
- vx = window.innerWidth / window.innerHeight;
207
- vy = 1;
208
- }
209
-
210
- this.rayMatrix.set(vx, 0, 0, 0,
211
- 0, vy, 0, 0,
212
- 0, 0, -this.zoom, 0,
213
- 0, 0, 0, 1);
214
-
215
- if (this.mobileDeviceControls)
216
- {
217
- this.mobileDeviceControls.fovy = 2 * Math.atan(vy / this.zoom);
218
- }
219
-
220
- THREE.BloomPass.blurX.set( 1 / (512 * vx), 0.0 );
221
- THREE.BloomPass.blurY.set( 0.0, 1 / (512 * vy) );
222
- },
223
-
224
- initPlayer: function()
225
- {
226
- var self = this;
227
-
228
- this.player = new Player;
229
- this.player.lookAt(this.wormholePositionSize);
230
-
231
- for (var i = 0; i < this.teleportTargets.length; i++) {
232
- this.player.addTeleportTarget(this.teleportTargets[i]);
233
- }
234
-
235
- // Add keyboard controls to the player
236
- this.keyboardControls = new KeyboardControls(this.player, this.container);
237
- this.keyboardControls.movementSpeed = 1;
238
- this.keyboardControls.rollSpeed = Math.PI / 3;
239
- this.keyboardControls.autoForward = false;
240
- this.keyboardControls.dragToLook = false;
241
- this.keyboardControls.connect();
242
-
243
- this.player.controls.push(this.keyboardControls);
244
-
245
- // Add mobile device controls (touch + accelerometer) to the player
246
- this.mobileDeviceControls = new MobileDeviceControls(this.player, this.container);
247
- this.mobileDeviceControls.movementSpeed = 1.3;
248
-
249
- this.player.controls.push(this.mobileDeviceControls);
250
-
251
- // Pretty sure we don't need mobile device controls when a keyboard event is triggered.
252
- var keypress = function(event) {
253
-
254
- if (event.charCode == 32)
255
- {
256
- self.keyboardControls.dragToLook = !self.keyboardControls.dragToLook;
257
- }
258
- else if (event.keyCode == 27)
259
- {
260
- self.keyboardControls.dragToLook = true;
261
- }
262
-
263
- if (self.keyboardControls.dragToLook)
264
- {
265
- self.keyboardControls.moveState.yawLeft = 0;
266
- self.keyboardControls.moveState.pitchDown = 0;
267
- }
268
-
269
- self.mobileDeviceControls.disconnect();
270
-
271
- document.body.classList.remove("mobile-device");
272
- };
273
-
274
- window.addEventListener("keypress", keypress, false);
275
-
276
- var deviceListener = function(event) {
277
- if (event.alpha === null) return;
278
-
279
- self.mobileDeviceControls.connect();
280
-
281
- // The player will probably not be looking with their device in the right direction, so fix that
282
- requestAnimationFrame(function () {
283
- self.player.object.quaternion.multiply(self.player.eyes.quaternion.clone().inverse())
284
- })
285
-
286
- window.removeEventListener("deviceorientation", deviceListener, false);
287
-
288
- document.body.classList.add("mobile-device");
289
- };
290
-
291
- window.addEventListener("deviceorientation", deviceListener, false);
292
-
293
- this.updateView();
294
- },
295
-
296
- step: function()
297
- {
298
- if (this.inited)
299
- {
300
- this.update();
301
- }
302
- this.render();
303
- },
304
-
305
- start: function()
306
- {
307
- var self = this;
308
-
309
- this.clock = new THREE.Clock();
310
-
311
- function animate()
312
- {
313
- requestAnimationFrame(animate);
314
-
315
- self.step();
316
- }
317
-
318
- animate();
319
- },
320
-
321
- update: function()
322
- {
323
- var delta = this.clock.getDelta();
324
-
325
- // TODO: figure out why delta can become so small
326
- if (delta < 0.001)
327
- {
328
- delta = 0.001;
329
- }
330
-
331
- this.player.update(delta);
332
- },
333
-
334
- render: function()
335
- {
336
- this.uniforms.rayMatrix.value.makeRotationFromQuaternion(this.player.eyes.getWorldQuaternion());
337
- this.uniforms.rayMatrix.value.multiply(this.rayMatrix);
338
-
339
- this.uniforms.cameraPosition.value = this.player.eyes.getWorldPosition();
340
- this.uniforms.startGalaxy.value = this.player.galaxy;
341
-
342
- this.renderer.clear();
343
- this.composer.render();
344
- },
345
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/controls/DeviceOrientationControls.js DELETED
@@ -1,88 +0,0 @@
1
- /**
2
- * @author richt / http://richt.me
3
- * @author WestLangley / http://github.com/WestLangley
4
- *
5
- * W3C Device Orientation control (http://w3c.github.io/deviceorientation/spec-source-orientation.html)
6
- */
7
-
8
- THREE.DeviceOrientationControls = function ( object ) {
9
-
10
- var scope = this;
11
-
12
- this.object = object;
13
- this.object.rotation.reorder( "YXZ" );
14
-
15
- this.enabled = true;
16
-
17
- var onDeviceOrientationChangeEvent = function ( event ) {
18
-
19
- scope.deviceOrientation = event;
20
-
21
- };
22
-
23
- var onScreenOrientationChangeEvent = function () {
24
-
25
- scope.screenOrientation = window.orientation || 0;
26
-
27
- };
28
-
29
- // The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y''
30
-
31
- var setObjectQuaternion = function () {
32
-
33
- var zee = new THREE.Vector3( 0, 0, 1 );
34
-
35
- var euler = new THREE.Euler();
36
-
37
- var q0 = new THREE.Quaternion();
38
-
39
- var q1 = new THREE.Quaternion( - Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis
40
-
41
- return function ( quaternion, alpha, beta, gamma, orient ) {
42
-
43
- euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us
44
-
45
- quaternion.setFromEuler( euler ); // orient the device
46
-
47
- quaternion.multiply( q1 ); // camera looks out the back of the device, not the top
48
-
49
- quaternion.multiply( q0.setFromAxisAngle( zee, - orient ) ); // adjust for screen orientation
50
-
51
- }
52
-
53
- }();
54
-
55
- this.connect = function() {
56
-
57
- onScreenOrientationChangeEvent(); // run once on load
58
-
59
- window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent, false );
60
- window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false );
61
-
62
- scope.enabled = true;
63
-
64
- };
65
-
66
- this.disconnect = function() {
67
-
68
- window.removeEventListener( 'orientationchange', onScreenOrientationChangeEvent, false );
69
- window.removeEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false );
70
-
71
- scope.enabled = false;
72
-
73
- };
74
-
75
- this.update = function () {
76
-
77
- if ( scope.enabled === false || !scope.deviceOrientation || scope.deviceOrientation.alpha === null ) return;
78
-
79
- var alpha = scope.deviceOrientation.alpha ? THREE.Math.degToRad( scope.deviceOrientation.alpha ) : 0; // Z
80
- var beta = scope.deviceOrientation.beta ? THREE.Math.degToRad( scope.deviceOrientation.beta ) : 0; // X'
81
- var gamma = scope.deviceOrientation.gamma ? THREE.Math.degToRad( scope.deviceOrientation.gamma ) : 0; // Y''
82
- var orient = scope.screenOrientation ? THREE.Math.degToRad( scope.screenOrientation ) : 0; // O
83
-
84
- setObjectQuaternion( scope.object.quaternion, alpha, beta, gamma, orient );
85
-
86
- };
87
-
88
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/controls/KeyboardControls.js DELETED
@@ -1,324 +0,0 @@
1
- function KeyboardControls(player, element)
2
- {
3
- this.player = player;
4
- this.teleport = this.player.teleport.bind(this.player);
5
-
6
- element.setAttribute('tabindex', -1);
7
-
8
- this.movementSpeedMultiplier = 1;
9
- this.movementSpeed = 1.0;
10
- this.rollSpeed = 0.005;
11
-
12
- this.dragToLook = false;
13
- this.autoForward = false;
14
-
15
- // Internals
16
- this.tmpQuaternion = new THREE.Quaternion();
17
-
18
- this.mouseStatus = 0;
19
-
20
- this.moveState = {
21
- up: 0,
22
- down: 0,
23
- left: 0,
24
- right: 0,
25
- forward: 0,
26
- back: 0,
27
- pitchUp: 0,
28
- pitchDown: 0,
29
- yawLeft: 0,
30
- yawRight: 0,
31
- rollLeft: 0,
32
- rollRight: 0,
33
- };
34
-
35
- this.moveVector = new THREE.Vector3( 0, 0, 0 );
36
- this.rotationVector = new THREE.Vector3( 0, 0, 0 );
37
-
38
- var self = this;
39
-
40
- function keydown(event)
41
- {
42
- if (event.altKey)
43
- {
44
- return;
45
- }
46
-
47
- switch (event.keyCode)
48
- {
49
- case 16: // Shift
50
- self.movementSpeedMultiplier = 10;
51
- break;
52
-
53
- case 87: // W
54
- self.moveState.forward = 1;
55
- break;
56
-
57
- case 83: // S
58
- self.moveState.back = 1;
59
- break;
60
-
61
- case 65: // A
62
- self.moveState.left = 1;
63
- break;
64
-
65
- case 68: // D
66
- self.moveState.right = 1;
67
- break;
68
-
69
- case 82: // R
70
- self.moveState.up = 1;
71
- break;
72
-
73
- case 70: // F
74
- self.moveState.down = 1;
75
- break;
76
-
77
- case 38: // Up
78
- self.moveState.pitchUp = 1;
79
- break;
80
-
81
- case 40: // Down
82
- self.moveState.pitchDown = 1;
83
- break;
84
-
85
- case 37: // Left
86
- self.moveState.yawLeft = 1;
87
- break;
88
-
89
- case 39: // Right
90
- self.moveState.yawRight = 1;
91
- break;
92
-
93
- case 81: // Q
94
- self.moveState.rollLeft = 1;
95
- break;
96
-
97
- case 69: // E
98
- self.moveState.rollRight = 1;
99
- break;
100
-
101
- case 84: // T
102
- self.teleport();
103
- break;
104
- }
105
-
106
- updateMovementVector();
107
- updateRotationVector();
108
- };
109
-
110
- function keyup(event)
111
- {
112
- switch (event.keyCode)
113
- {
114
- case 16: // Shift
115
- self.movementSpeedMultiplier = 1;
116
- break;
117
-
118
- case 87: // W
119
- self.moveState.forward = 0;
120
- break;
121
-
122
- case 83: // S
123
- self.moveState.back = 0;
124
- break;
125
-
126
- case 65: // A
127
- self.moveState.left = 0;
128
- break;
129
-
130
- case 68: // D
131
- self.moveState.right = 0;
132
- break;
133
-
134
- case 82: // R
135
- self.moveState.up = 0;
136
- break;
137
-
138
- case 70: // F
139
- self.moveState.down = 0;
140
- break;
141
-
142
- case 38: // Up
143
- self.moveState.pitchUp = 0;
144
- break;
145
-
146
- case 40: // Down
147
- self.moveState.pitchDown = 0;
148
- break;
149
-
150
- case 37: // Left
151
- self.moveState.yawLeft = 0;
152
- break;
153
-
154
- case 39: // Right
155
- self.moveState.yawRight = 0;
156
- break;
157
-
158
- case 81: // Q
159
- self.moveState.rollLeft = 0;
160
- break;
161
-
162
- case 69: // E
163
- self.moveState.rollRight = 0;
164
- break;
165
- }
166
-
167
- updateMovementVector();
168
- updateRotationVector();
169
- }
170
-
171
- function mousedown(event)
172
- {
173
- if ( element !== document )
174
- {
175
- element.focus();
176
- }
177
-
178
- event.preventDefault();
179
- event.stopPropagation();
180
-
181
- if (self.dragToLook)
182
- {
183
- self.mouseStatus++;
184
- }
185
- else
186
- {
187
- switch (event.button)
188
- {
189
- case 0:
190
- self.moveState.forward = 1;
191
- break;
192
-
193
- case 2:
194
- self.moveState.back = 1;
195
- break;
196
- }
197
-
198
- updateMovementVector();
199
- }
200
- }
201
-
202
- function mousemove(event)
203
- {
204
- if (self.dragToLook && self.mouseStatus == 0)
205
- {
206
- return;
207
- }
208
-
209
- var container = getContainerDimensions();
210
- var halfWidth = container.size[0] / 2;
211
- var halfHeight = container.size[1] / 2;
212
-
213
- self.moveState.yawLeft = - ((event.pageX - container.offset[0]) - halfWidth ) / halfWidth;
214
- self.moveState.pitchDown = ((event.pageY - container.offset[1]) - halfHeight) / halfHeight;
215
-
216
- updateRotationVector();
217
- }
218
-
219
- function mouseup(event)
220
- {
221
- event.preventDefault();
222
- event.stopPropagation();
223
-
224
- if ( self.dragToLook )
225
- {
226
- self.mouseStatus --;
227
- self.moveState.yawLeft = self.moveState.pitchDown = 0;
228
- }
229
- else
230
- {
231
- switch (event.button)
232
- {
233
- case 0:
234
- self.moveState.forward = 0;
235
- break;
236
-
237
- case 2:
238
- self.moveState.back = 0;
239
- break;
240
- }
241
-
242
- updateMovementVector();
243
- }
244
-
245
- updateRotationVector();
246
- };
247
-
248
- function updateMovementVector()
249
- {
250
- var forward = (self.moveState.forward || (self.autoForward && !self.moveState.back)) ? 1 : 0;
251
-
252
- self.moveVector.x = ( -self.moveState.left + self.moveState.right );
253
- self.moveVector.y = ( -self.moveState.down + self.moveState.up );
254
- self.moveVector.z = ( -forward + self.moveState.back );
255
- };
256
-
257
- function updateRotationVector()
258
- {
259
- self.rotationVector.x = ( -self.moveState.pitchDown + self.moveState.pitchUp );
260
- self.rotationVector.y = ( -self.moveState.yawRight + self.moveState.yawLeft );
261
- self.rotationVector.z = ( -self.moveState.rollRight + self.moveState.rollLeft );
262
- };
263
-
264
- function getContainerDimensions()
265
- {
266
- if (element != document)
267
- {
268
- return {
269
- size: [element.offsetWidth, element.offsetHeight],
270
- offset: [element.offsetLeft, element.offsetTop]
271
- };
272
- }
273
- else
274
- {
275
- return {
276
- size: [window.innerWidth, window.innerHeight],
277
- offset: [0, 0]
278
- };
279
- }
280
- }
281
-
282
- function contextmenu(event)
283
- {
284
- event.preventDefault();
285
- }
286
-
287
- this.connect = function() {
288
- updateMovementVector();
289
- updateRotationVector();
290
-
291
- element.addEventListener('contextmenu', contextmenu, false);
292
-
293
- element.addEventListener('mousemove', mousemove, false);
294
- element.addEventListener('mousedown', mousedown, false);
295
- element.addEventListener('mouseup', mouseup, false);
296
-
297
- window.addEventListener('keydown', keydown, false);
298
- window.addEventListener('keyup', keyup, false);
299
-
300
- this.enabled = true;
301
- };
302
-
303
- this.disconnect = function() {
304
- element.removeEventListener('contextmenu', contextmenu, false);
305
-
306
- element.removeEventListener('mousemove', mousemove, false);
307
- element.removeEventListener('mousedown', mousedown, false);
308
- element.removeEventListener('mouseup', mouseup, false);
309
-
310
- window.removeEventListener('keydown', keydown, false);
311
- window.removeEventListener('keyup', keyup, false);
312
-
313
- this.enabled = false;
314
- };
315
-
316
- this.update = function()
317
- {
318
- var moveMult = this.movementSpeed * this.movementSpeedMultiplier;
319
- var rotMult = this.rollSpeed;
320
-
321
- this.player.velocity.copy(this.moveVector).multiplyScalar(moveMult).applyQuaternion(this.player.eyes.getWorldQuaternion());
322
- this.player.eyeAngularVelocity.copy(this.rotationVector).multiplyScalar(rotMult);
323
- };
324
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/controls/MobileDeviceControls.js DELETED
@@ -1,89 +0,0 @@
1
- function MobileDeviceControls(player, element)
2
- {
3
- this.deviceOrientationControls = new THREE.DeviceOrientationControls(player.eyes);
4
-
5
- this.player = player;
6
-
7
- this.velocity = new THREE.Vector3(0, 0, 0);
8
-
9
- var self = this;
10
-
11
- function teleport(event)
12
- {
13
- event.preventDefault();
14
-
15
- player.teleport();
16
- }
17
-
18
- function onTouchEvent(event)
19
- {
20
- event.preventDefault();
21
-
22
- var touches = event.touches;
23
-
24
- if (touches.length == 0)
25
- {
26
- self.velocity.set(0, 0, 0);
27
- return;
28
- }
29
-
30
- var avgX = 0, avgY = 0;
31
- for (var i = 0; i < touches.length; i++)
32
- {
33
- avgX += touches[i].pageX;
34
- avgY += touches[i].pageY;
35
- }
36
- avgX /= touches.length;
37
- avgY /= touches.length;
38
-
39
- var vy = Math.tan(0.5 * self.fovy);
40
- var vx = window.innerWidth / window.innerHeight * vy;
41
-
42
- self.velocity.set(
43
- vx * (avgX * 2.0 / window.innerWidth - 1.0),
44
- -vy * (avgY * 2.0 / window.innerHeight - 1.0),
45
- -1
46
- ).normalize();
47
-
48
- self.velocity.multiplyScalar(touches.length > 1 ? 10 : 1);
49
- }
50
-
51
- this.connect = function() {
52
- this.deviceOrientationControls.connect();
53
-
54
- element.addEventListener( 'touchstart', onTouchEvent, false );
55
- element.addEventListener( 'touchmove', onTouchEvent, false );
56
- element.addEventListener( 'touchend', onTouchEvent, false );
57
-
58
- document.querySelector( '#teleport' ).addEventListener(
59
- 'touchstart',
60
- teleport,
61
- false
62
- );
63
-
64
- this.enabled = true;
65
- };
66
-
67
- this.disconnect = function() {
68
- this.deviceOrientationControls.disconnect();
69
-
70
- element.removeEventListener( 'touchstart', onTouchEvent, false );
71
- element.removeEventListener( 'touchmove', onTouchEvent, false );
72
- element.removeEventListener( 'touchend', onTouchEvent, false );
73
-
74
- document.querySelector( '#teleport' ).removeEventListener(
75
- 'touchstart',
76
- teleport,
77
- false
78
- );
79
-
80
- this.enabled = false;
81
- };
82
-
83
- this.update = function () {
84
- if (this.enabled === false) return;
85
-
86
- this.player.velocity.copy(this.velocity).applyQuaternion(this.player.eyes.getWorldQuaternion());
87
- this.deviceOrientationControls.update();
88
- };
89
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/main.js DELETED
@@ -1,13 +0,0 @@
1
- (function() {
2
-
3
- if (!Detector.webgl)
4
- {
5
- document.body.classList.add("no-ui");
6
- Detector.addGetWebGLMessage({id: "webgl-error"});
7
- return;
8
- }
9
-
10
- Simulation.init();
11
- Simulation.start();
12
-
13
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/postprocessing/BloomPass.js DELETED
@@ -1,115 +0,0 @@
1
- /**
2
- * @author alteredq / http://alteredqualia.com/
3
- */
4
-
5
- THREE.BloomPass = function ( strength, kernelSize, sigma, resolution ) {
6
-
7
- strength = ( strength !== undefined ) ? strength : 1;
8
- kernelSize = ( kernelSize !== undefined ) ? kernelSize : 25;
9
- sigma = ( sigma !== undefined ) ? sigma : 4.0;
10
- resolution = ( resolution !== undefined ) ? resolution : 256;
11
-
12
- // render targets
13
-
14
- var pars = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat };
15
-
16
- this.renderTargetX = new THREE.WebGLRenderTarget( resolution, resolution, pars );
17
- this.renderTargetY = new THREE.WebGLRenderTarget( resolution, resolution, pars );
18
-
19
- // copy material
20
-
21
- if ( THREE.CopyShader === undefined )
22
- console.error( "THREE.BloomPass relies on THREE.CopyShader" );
23
-
24
- var copyShader = THREE.CopyShader;
25
-
26
- this.copyUniforms = THREE.UniformsUtils.clone( copyShader.uniforms );
27
-
28
- this.copyUniforms[ "opacity" ].value = strength;
29
-
30
- this.materialCopy = new THREE.ShaderMaterial( {
31
-
32
- uniforms: this.copyUniforms,
33
- vertexShader: copyShader.vertexShader,
34
- fragmentShader: copyShader.fragmentShader,
35
- blending: THREE.AdditiveBlending,
36
- transparent: true
37
-
38
- } );
39
-
40
- // convolution material
41
-
42
- if ( THREE.ConvolutionShader === undefined )
43
- console.error( "THREE.BloomPass relies on THREE.ConvolutionShader" );
44
-
45
- var convolutionShader = THREE.ConvolutionShader;
46
-
47
- this.convolutionUniforms = THREE.UniformsUtils.clone( convolutionShader.uniforms );
48
-
49
- this.convolutionUniforms[ "uImageIncrement" ].value = THREE.BloomPass.blurx;
50
- this.convolutionUniforms[ "cKernel" ].value = THREE.ConvolutionShader.buildKernel( sigma );
51
-
52
- this.materialConvolution = new THREE.ShaderMaterial( {
53
-
54
- uniforms: this.convolutionUniforms,
55
- vertexShader: convolutionShader.vertexShader,
56
- fragmentShader: convolutionShader.fragmentShader,
57
- defines: {
58
- "KERNEL_SIZE_FLOAT": kernelSize.toFixed( 1 ),
59
- "KERNEL_SIZE_INT": kernelSize.toFixed( 0 )
60
- }
61
-
62
- } );
63
-
64
- this.enabled = true;
65
- this.needsSwap = false;
66
- this.clear = false;
67
-
68
-
69
- this.camera = new THREE.OrthographicCamera( -1, 1, 1, -1, 0, 1 );
70
- this.scene = new THREE.Scene();
71
-
72
- this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null );
73
- this.scene.add( this.quad );
74
-
75
- };
76
-
77
- THREE.BloomPass.prototype = {
78
-
79
- render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) {
80
-
81
- if ( maskActive ) renderer.context.disable( renderer.context.STENCIL_TEST );
82
-
83
- // Render quad with blured scene into texture (convolution pass 1)
84
-
85
- this.quad.material = this.materialConvolution;
86
-
87
- this.convolutionUniforms[ "tDiffuse" ].value = readBuffer;
88
- this.convolutionUniforms[ "uImageIncrement" ].value = THREE.BloomPass.blurX;
89
-
90
- renderer.render( this.scene, this.camera, this.renderTargetX, true );
91
-
92
-
93
- // Render quad with blured scene into texture (convolution pass 2)
94
-
95
- this.convolutionUniforms[ "tDiffuse" ].value = this.renderTargetX;
96
- this.convolutionUniforms[ "uImageIncrement" ].value = THREE.BloomPass.blurY;
97
-
98
- renderer.render( this.scene, this.camera, this.renderTargetY, true );
99
-
100
- // Render original scene with superimposed blur to texture
101
-
102
- this.quad.material = this.materialCopy;
103
-
104
- this.copyUniforms[ "tDiffuse" ].value = this.renderTargetY;
105
-
106
- if ( maskActive ) renderer.context.enable( renderer.context.STENCIL_TEST );
107
-
108
- renderer.render( this.scene, this.camera, readBuffer, this.clear );
109
-
110
- }
111
-
112
- };
113
-
114
- THREE.BloomPass.blurX = new THREE.Vector2( 0.001953125, 0.0 );
115
- THREE.BloomPass.blurY = new THREE.Vector2( 0.0, 0.001953125 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/postprocessing/ConvolutionShader.js DELETED
@@ -1,101 +0,0 @@
1
- /**
2
- * @author alteredq / http://alteredqualia.com/
3
- *
4
- * Convolution shader
5
- * ported from o3d sample to WebGL / GLSL
6
- * http://o3d.googlecode.com/svn/trunk/samples/convolution.html
7
- */
8
-
9
- THREE.ConvolutionShader = {
10
-
11
- defines: {
12
-
13
- "KERNEL_SIZE_FLOAT": "25.0",
14
- "KERNEL_SIZE_INT": "25",
15
-
16
- },
17
-
18
- uniforms: {
19
-
20
- "tDiffuse": { type: "t", value: null },
21
- "uImageIncrement": { type: "v2", value: new THREE.Vector2( 0.001953125, 0.0 ) },
22
- "cKernel": { type: "fv1", value: [] }
23
-
24
- },
25
-
26
- vertexShader: [
27
-
28
- "uniform vec2 uImageIncrement;",
29
-
30
- "varying vec2 vUv;",
31
-
32
- "void main() {",
33
-
34
- "vUv = uv - ( ( KERNEL_SIZE_FLOAT - 1.0 ) / 2.0 ) * uImageIncrement;",
35
- "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
36
-
37
- "}"
38
-
39
- ].join("\n"),
40
-
41
- fragmentShader: [
42
-
43
- "uniform float cKernel[ KERNEL_SIZE_INT ];",
44
-
45
- "uniform sampler2D tDiffuse;",
46
- "uniform vec2 uImageIncrement;",
47
-
48
- "varying vec2 vUv;",
49
-
50
- "void main() {",
51
-
52
- "vec2 imageCoord = vUv;",
53
- "vec4 sum = vec4( 0.0, 0.0, 0.0, 0.0 );",
54
-
55
- "for( int i = 0; i < KERNEL_SIZE_INT; i ++ ) {",
56
-
57
- "sum += texture2D( tDiffuse, imageCoord ) * cKernel[ i ];",
58
- "imageCoord += uImageIncrement;",
59
-
60
- "}",
61
-
62
- "gl_FragColor = sum;",
63
-
64
- "}"
65
-
66
-
67
- ].join("\n"),
68
-
69
- buildKernel: function ( sigma ) {
70
-
71
- // We lop off the sqrt(2 * pi) * sigma term, since we're going to normalize anyway.
72
-
73
- function gauss( x, sigma ) {
74
-
75
- return Math.exp( - ( x * x ) / ( 2.0 * sigma * sigma ) );
76
-
77
- }
78
-
79
- var i, values, sum, halfWidth, kMaxKernelSize = 25, kernelSize = 2 * Math.ceil( sigma * 3.0 ) + 1;
80
-
81
- if ( kernelSize > kMaxKernelSize ) kernelSize = kMaxKernelSize;
82
- halfWidth = ( kernelSize - 1 ) * 0.5;
83
-
84
- values = new Array( kernelSize );
85
- sum = 0.0;
86
- for ( i = 0; i < kernelSize; ++i ) {
87
-
88
- values[ i ] = gauss( i - halfWidth, sigma );
89
- sum += values[ i ];
90
-
91
- }
92
-
93
- // normalize the kernel
94
-
95
- for ( i = 0; i < kernelSize; ++i ) values[ i ] /= sum;
96
-
97
- return values;
98
-
99
- }
100
-
101
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/postprocessing/CopyShader.js DELETED
@@ -1,46 +0,0 @@
1
- /**
2
- * @author alteredq / http://alteredqualia.com/
3
- *
4
- * Full-screen textured quad shader
5
- */
6
-
7
- THREE.CopyShader = {
8
-
9
- uniforms: {
10
-
11
- "tDiffuse": { type: "t", value: null },
12
- "opacity": { type: "f", value: 1.0 }
13
-
14
- },
15
-
16
- vertexShader: [
17
-
18
- "varying vec2 vUv;",
19
-
20
- "void main() {",
21
-
22
- "vUv = uv;",
23
- "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
24
-
25
- "}"
26
-
27
- ].join("\n"),
28
-
29
- fragmentShader: [
30
-
31
- "uniform float opacity;",
32
-
33
- "uniform sampler2D tDiffuse;",
34
-
35
- "varying vec2 vUv;",
36
-
37
- "void main() {",
38
-
39
- "vec4 texel = texture2D( tDiffuse, vUv );",
40
- "gl_FragColor = opacity * texel;",
41
-
42
- "}"
43
-
44
- ].join("\n")
45
-
46
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/postprocessing/EffectComposer.js DELETED
@@ -1,135 +0,0 @@
1
- /**
2
- * @author alteredq / http://alteredqualia.com/
3
- */
4
-
5
- THREE.EffectComposer = function ( renderer, renderTarget ) {
6
-
7
- this.renderer = renderer;
8
-
9
- if ( renderTarget === undefined ) {
10
-
11
- var width = window.innerWidth || 1;
12
- var height = window.innerHeight || 1;
13
- var parameters = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat, stencilBuffer: false };
14
-
15
- renderTarget = new THREE.WebGLRenderTarget( width, height, parameters );
16
-
17
- }
18
-
19
- this.renderTarget1 = renderTarget;
20
- this.renderTarget2 = renderTarget.clone();
21
-
22
- this.writeBuffer = this.renderTarget1;
23
- this.readBuffer = this.renderTarget2;
24
-
25
- this.passes = [];
26
-
27
- if ( THREE.CopyShader === undefined )
28
- console.error( "THREE.EffectComposer relies on THREE.CopyShader" );
29
-
30
- this.copyPass = new THREE.ShaderPass( THREE.CopyShader );
31
-
32
- };
33
-
34
- THREE.EffectComposer.prototype = {
35
-
36
- swapBuffers: function() {
37
-
38
- var tmp = this.readBuffer;
39
- this.readBuffer = this.writeBuffer;
40
- this.writeBuffer = tmp;
41
-
42
- },
43
-
44
- addPass: function ( pass ) {
45
-
46
- this.passes.push( pass );
47
-
48
- },
49
-
50
- insertPass: function ( pass, index ) {
51
-
52
- this.passes.splice( index, 0, pass );
53
-
54
- },
55
-
56
- render: function ( delta ) {
57
-
58
- this.writeBuffer = this.renderTarget1;
59
- this.readBuffer = this.renderTarget2;
60
-
61
- var maskActive = false;
62
-
63
- var pass, i, il = this.passes.length;
64
-
65
- for ( i = 0; i < il; i ++ ) {
66
-
67
- pass = this.passes[ i ];
68
-
69
- if ( !pass.enabled ) continue;
70
-
71
- pass.render( this.renderer, this.writeBuffer, this.readBuffer, delta, maskActive );
72
-
73
- if ( pass.needsSwap ) {
74
-
75
- if ( maskActive ) {
76
-
77
- var context = this.renderer.context;
78
-
79
- context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff );
80
-
81
- this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, delta );
82
-
83
- context.stencilFunc( context.EQUAL, 1, 0xffffffff );
84
-
85
- }
86
-
87
- this.swapBuffers();
88
-
89
- }
90
-
91
- if ( pass instanceof THREE.MaskPass ) {
92
-
93
- maskActive = true;
94
-
95
- } else if ( pass instanceof THREE.ClearMaskPass ) {
96
-
97
- maskActive = false;
98
-
99
- }
100
-
101
- }
102
-
103
- },
104
-
105
- reset: function ( renderTarget ) {
106
-
107
- if ( renderTarget === undefined ) {
108
-
109
- renderTarget = this.renderTarget1.clone();
110
-
111
- renderTarget.width = window.innerWidth;
112
- renderTarget.height = window.innerHeight;
113
-
114
- }
115
-
116
- this.renderTarget1 = renderTarget;
117
- this.renderTarget2 = renderTarget.clone();
118
-
119
- this.writeBuffer = this.renderTarget1;
120
- this.readBuffer = this.renderTarget2;
121
-
122
- },
123
-
124
- setSize: function ( width, height ) {
125
-
126
- var renderTarget = this.renderTarget1.clone();
127
-
128
- renderTarget.width = width;
129
- renderTarget.height = height;
130
-
131
- this.reset( renderTarget );
132
-
133
- }
134
-
135
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/postprocessing/MaskPass.js DELETED
@@ -1,86 +0,0 @@
1
- /**
2
- * @author alteredq / http://alteredqualia.com/
3
- */
4
-
5
- THREE.MaskPass = function ( scene, camera ) {
6
-
7
- this.scene = scene;
8
- this.camera = camera;
9
-
10
- this.enabled = true;
11
- this.clear = true;
12
- this.needsSwap = false;
13
-
14
- this.inverse = false;
15
-
16
- };
17
-
18
- THREE.MaskPass.prototype = {
19
-
20
- render: function ( renderer, writeBuffer, readBuffer, delta ) {
21
-
22
- var context = renderer.context;
23
-
24
- // don't update color or depth
25
-
26
- context.colorMask( false, false, false, false );
27
- context.depthMask( false );
28
-
29
- // set up stencil
30
-
31
- var writeValue, clearValue;
32
-
33
- if ( this.inverse ) {
34
-
35
- writeValue = 0;
36
- clearValue = 1;
37
-
38
- } else {
39
-
40
- writeValue = 1;
41
- clearValue = 0;
42
-
43
- }
44
-
45
- context.enable( context.STENCIL_TEST );
46
- context.stencilOp( context.REPLACE, context.REPLACE, context.REPLACE );
47
- context.stencilFunc( context.ALWAYS, writeValue, 0xffffffff );
48
- context.clearStencil( clearValue );
49
-
50
- // draw into the stencil buffer
51
-
52
- renderer.render( this.scene, this.camera, readBuffer, this.clear );
53
- renderer.render( this.scene, this.camera, writeBuffer, this.clear );
54
-
55
- // re-enable update of color and depth
56
-
57
- context.colorMask( true, true, true, true );
58
- context.depthMask( true );
59
-
60
- // only render where stencil is set to 1
61
-
62
- context.stencilFunc( context.EQUAL, 1, 0xffffffff ); // draw if == 1
63
- context.stencilOp( context.KEEP, context.KEEP, context.KEEP );
64
-
65
- }
66
-
67
- };
68
-
69
-
70
- THREE.ClearMaskPass = function () {
71
-
72
- this.enabled = true;
73
-
74
- };
75
-
76
- THREE.ClearMaskPass.prototype = {
77
-
78
- render: function ( renderer, writeBuffer, readBuffer, delta ) {
79
-
80
- var context = renderer.context;
81
-
82
- context.disable( context.STENCIL_TEST );
83
-
84
- }
85
-
86
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/postprocessing/RenderPass.js DELETED
@@ -1,51 +0,0 @@
1
- /**
2
- * @author alteredq / http://alteredqualia.com/
3
- */
4
-
5
- THREE.RenderPass = function ( scene, camera, overrideMaterial, clearColor, clearAlpha ) {
6
-
7
- this.scene = scene;
8
- this.camera = camera;
9
-
10
- this.overrideMaterial = overrideMaterial;
11
-
12
- this.clearColor = clearColor;
13
- this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 1;
14
-
15
- this.oldClearColor = new THREE.Color();
16
- this.oldClearAlpha = 1;
17
-
18
- this.enabled = true;
19
- this.clear = true;
20
- this.needsSwap = false;
21
-
22
- };
23
-
24
- THREE.RenderPass.prototype = {
25
-
26
- render: function ( renderer, writeBuffer, readBuffer, delta ) {
27
-
28
- this.scene.overrideMaterial = this.overrideMaterial;
29
-
30
- if ( this.clearColor ) {
31
-
32
- this.oldClearColor.copy( renderer.getClearColor() );
33
- this.oldClearAlpha = renderer.getClearAlpha();
34
-
35
- renderer.setClearColor( this.clearColor, this.clearAlpha );
36
-
37
- }
38
-
39
- renderer.render( this.scene, this.camera, readBuffer, this.clear );
40
-
41
- if ( this.clearColor ) {
42
-
43
- renderer.setClearColor( this.oldClearColor, this.oldClearAlpha );
44
-
45
- }
46
-
47
- this.scene.overrideMaterial = null;
48
-
49
- }
50
-
51
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/postprocessing/ShaderPass.js DELETED
@@ -1,58 +0,0 @@
1
- /**
2
- * @author alteredq / http://alteredqualia.com/
3
- */
4
-
5
- THREE.ShaderPass = function ( shader, textureID ) {
6
-
7
- this.textureID = ( textureID !== undefined ) ? textureID : "tDiffuse";
8
-
9
- this.uniforms = THREE.UniformsUtils.clone( shader.uniforms );
10
-
11
- this.material = new THREE.ShaderMaterial( {
12
-
13
- uniforms: this.uniforms,
14
- vertexShader: shader.vertexShader,
15
- fragmentShader: shader.fragmentShader
16
-
17
- } );
18
-
19
- this.renderToScreen = false;
20
-
21
- this.enabled = true;
22
- this.needsSwap = true;
23
- this.clear = false;
24
-
25
-
26
- this.camera = new THREE.OrthographicCamera( -1, 1, 1, -1, 0, 1 );
27
- this.scene = new THREE.Scene();
28
-
29
- this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null );
30
- this.scene.add( this.quad );
31
-
32
- };
33
-
34
- THREE.ShaderPass.prototype = {
35
-
36
- render: function ( renderer, writeBuffer, readBuffer, delta ) {
37
-
38
- if ( this.uniforms[ this.textureID ] ) {
39
-
40
- this.uniforms[ this.textureID ].value = readBuffer;
41
-
42
- }
43
-
44
- this.quad.material = this.material;
45
-
46
- if ( this.renderToScreen ) {
47
-
48
- renderer.render( this.scene, this.camera );
49
-
50
- } else {
51
-
52
- renderer.render( this.scene, this.camera, writeBuffer, this.clear );
53
-
54
- }
55
-
56
- }
57
-
58
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/three.min.js DELETED
The diff for this file is too large to render. See raw diff
 
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "interstellar-demo",
3
+ "version": "1.0.0",
4
+ "description": "Interstellar wormhole & blackhole simulator",
5
+ "scripts": {
6
+ "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
7
+ "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/sirxemic/Interstellar.git"
12
+ },
13
+ "keywords": [
14
+ "interstellar",
15
+ "demo",
16
+ "black hole",
17
+ "wormhole"
18
+ ],
19
+ "author": "Pim Schreurs",
20
+ "license": "MIT",
21
+ "bugs": {
22
+ "url": "https://github.com/sirxemic/Interstellar/issues"
23
+ },
24
+ "homepage": "https://github.com/sirxemic/Interstellar#readme",
25
+ "devDependencies": {
26
+ "babel-core": "^6.26.3",
27
+ "babel-eslint": "^8.2.3",
28
+ "babel-loader": "^7.1.4",
29
+ "babel-preset-env": "^1.7.0",
30
+ "babel-preset-stage-3": "^6.24.1",
31
+ "cross-env": "^5.1.5",
32
+ "eslint": "^4.19.1",
33
+ "eslint-config-standard": "^11.0.0",
34
+ "eslint-loader": "^2.0.0",
35
+ "eslint-plugin-import": "^2.12.0",
36
+ "eslint-plugin-node": "^6.0.1",
37
+ "eslint-plugin-promise": "^3.7.0",
38
+ "eslint-plugin-standard": "^3.1.0",
39
+ "raw-loader": "^0.5.1",
40
+ "three": "^0.92.0",
41
+ "webpack": "^4.8.3",
42
+ "webpack-cli": "^2.1.3",
43
+ "webpack-dev-server": "^3.1.4"
44
+ },
45
+ "browserslist": [
46
+ "> 1%",
47
+ "last 2 versions",
48
+ "not ie <= 8"
49
+ ]
50
+ }
src/Player.js ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Object3D,
3
+ PerspectiveCamera,
4
+ Vector3,
5
+ Quaternion,
6
+ Matrix4,
7
+ Ray,
8
+ Sphere
9
+ } from 'three'
10
+
11
+ import Simulation from './Simulation'
12
+
13
+ // For improved performance, we initialize certain variables only once instead of in the step function
14
+ const __prevPosition = new Vector3()
15
+ const __newVelocity = new Vector3()
16
+ const __acceleration = new Vector3()
17
+ const __gravityVector = new Vector3()
18
+ const __direction = new Vector3()
19
+ const __intersection = new Vector3()
20
+ const __axis = new Vector3()
21
+ const __rotation = new Quaternion()
22
+ const __temp = new Vector3()
23
+ const __ray = new Ray()
24
+
25
+ export default class Player {
26
+
27
+ constructor () {
28
+ this.object = new Object3D()
29
+ this.eyes = new PerspectiveCamera()
30
+ this.object.add(this.eyes)
31
+
32
+ this.velocity = new Vector3()
33
+ this.eyeAngularVelocity = new Vector3()
34
+
35
+ this.galaxy = 0
36
+
37
+ this.controllers = []
38
+ }
39
+
40
+ lookAt (position) {
41
+ // this.object.lookAt makes it look in the exact opposite __direction, for some reason
42
+ const lookAtMatrix = new Matrix4()
43
+ lookAtMatrix.lookAt(this.object.position, position, this.object.up)
44
+ this.object.quaternion.setFromRotationMatrix(lookAtMatrix)
45
+ this.object.quaternion.multiply(this.eyes.quaternion.clone().inverse())
46
+ }
47
+
48
+ addController (controller) {
49
+ this.controllers.push(controller)
50
+ }
51
+
52
+ handleInput () {
53
+ for (let i = 0; i < this.controllers.length; i++) {
54
+ this.controllers[i].update()
55
+ }
56
+ }
57
+
58
+ step (delta) {
59
+ const wormhole = Simulation.config.wormhole
60
+ const wormholeSphere = new Sphere(wormhole.position, wormhole.radius)
61
+
62
+ if (this.velocity.lengthSq() > 0.00001) {
63
+ __prevPosition.copy(this.object.position)
64
+
65
+ // 1. Compute wormhole curvature/gravity.
66
+ __gravityVector.subVectors(wormhole.position, __prevPosition)
67
+ const rayDistance = __gravityVector.length() - wormhole.radius * (1 - wormhole.gravityRatio)
68
+ const amount = wormhole.gravityRatio / rayDistance
69
+ __acceleration.copy(__gravityVector.normalize()).multiplyScalar(wormhole.radius * this.velocity.lengthSq() * amount * amount)
70
+
71
+ // Apply curvature to velocity
72
+ __newVelocity.copy(this.velocity).add(__acceleration.multiplyScalar(delta))
73
+
74
+ // Adjust new velocity (keep magnitude of old velocity)
75
+ __newVelocity.normalize().multiplyScalar(this.velocity.length())
76
+
77
+ // Update the player's position and orientation accordingly
78
+ this.object.position.addVectors(__prevPosition, __newVelocity.multiplyScalar(delta))
79
+ this.object.quaternion.multiplyQuaternions(
80
+ __rotation.setFromUnitVectors(this.velocity.normalize(), __newVelocity.normalize()),
81
+ this.object.quaternion
82
+ )
83
+
84
+ this.velocity.copy(__newVelocity)
85
+
86
+ // 2. Check if we're going through the wormhole
87
+ __direction.copy(this.velocity).normalize()
88
+
89
+ __ray.set(__prevPosition, __direction)
90
+
91
+ const distanceTravelledSq = __direction.subVectors(this.object.position, __prevPosition).lengthSq()
92
+
93
+ const at = __ray.intersectSphere(wormholeSphere, __intersection)
94
+ if (at && at.distanceToSquared(__prevPosition) <= distanceTravelledSq) {
95
+ // Rotate 180 degrees around __axis pointing at exit point
96
+ __axis.subVectors(__intersection, wormhole.position).normalize()
97
+ __rotation.setFromAxisAngle(__axis, Math.PI)
98
+ this.object.quaternion.multiplyQuaternions(__rotation, this.object.quaternion)
99
+ this.velocity.reflect(__axis).multiplyScalar(-1)
100
+
101
+ // Set new position a tiny bit outside mirrored __intersection point
102
+ this.object.position
103
+ .copy(wormhole.position)
104
+ .add(
105
+ __temp.subVectors(wormhole.position, __intersection)
106
+ .multiplyScalar(1.0001)
107
+ )
108
+
109
+ this.galaxy = 1 - this.galaxy
110
+ }
111
+ }
112
+
113
+ __rotation.set(
114
+ this.eyeAngularVelocity.x * delta,
115
+ this.eyeAngularVelocity.y * delta,
116
+ this.eyeAngularVelocity.z * delta,
117
+ 1
118
+ ).normalize()
119
+ this.eyes.quaternion.multiply(__rotation)
120
+ }
121
+
122
+ update (delta) {
123
+ this.handleInput()
124
+ this.step(delta)
125
+
126
+ // Object isn't actually part of a rendered scene, so we need to call this manually
127
+ this.object.updateMatrixWorld(true)
128
+ }
129
+
130
+ }
src/Simulation.js ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Vector3,
3
+ Vector4,
4
+ Clock
5
+ } from 'three'
6
+
7
+ import Ui from './Ui'
8
+
9
+ import Player from './Player'
10
+ import KeyboardControls from './controls/KeyboardControls'
11
+ import MobileDeviceControls from './controls/MobileDeviceControls'
12
+ import ControlsManager from './controls/ControlsManager'
13
+
14
+ import SimulationRenderer from './SimulationRenderer'
15
+ import Teleporter from './Teleporter'
16
+
17
+ class Simulation {
18
+
19
+ init () {
20
+ this.config = {
21
+ wormhole: {
22
+ position: new Vector3(10, 0.0, -32),
23
+ radius: 0.8,
24
+ gravityRatio: 0.25
25
+ },
26
+
27
+ blackhole: {
28
+ position: new Vector3(0.0, -250.0, 250.0),
29
+ radius: 12.5,
30
+
31
+ // Ring definition - xyz is normal going through ring. Its magnitude determines inner radius.
32
+ // w component determines outer radius
33
+ disk: new Vector4(-12, 12, 6, 150.0),
34
+ diskTexture: 'assets/accretion_disk.png'
35
+ },
36
+
37
+ saturn: {
38
+ position: new Vector3(-14, 5, -40),
39
+ radius: 8.0,
40
+
41
+ // Ring definition - xyz is normal going through ring. Its magnitude determines inner radius.
42
+ // w component determines outer radius
43
+ rings: new Vector4(0, 9.22, 0, 17.1),
44
+ texture: 'assets/saturn.jpg',
45
+ ringsTexture: 'assets/saturnrings.png',
46
+ lightDirection: (new Vector3(-4, 2, 3)).normalize()
47
+ },
48
+
49
+ planet: {
50
+ position: new Vector3(7.6, -188.0, 200),
51
+ radius: 0.08,
52
+ diffuse: new Vector3(0.58, 0.85, 0.96),
53
+ specular: new Vector3(0.1, 0.1, 0.1)
54
+ },
55
+
56
+ galaxy1: { texture: 'assets/galaxy1.png' },
57
+ galaxy2: { texture: 'assets/galaxy2.png' }
58
+ }
59
+
60
+ this.teleportTargets = [
61
+ { position: new Vector3(10, -307, 454), lookAt: this.config.blackhole.position, galaxy: 1 },
62
+ { position: new Vector3(7.2, -188, 199.6), lookAt: this.config.planet.position, galaxy: 1 },
63
+ { position: new Vector3(12.4, 3.3, -35.1), lookAt: this.config.wormhole.position, galaxy: 1 },
64
+ { position: new Vector3(9.8, -4.6, -3.1), lookAt: this.config.wormhole.position, galaxy: 0 }
65
+ ]
66
+
67
+ this.initPlayer()
68
+ this.initTeleporter()
69
+ this.initRenderer()
70
+ this.initControls()
71
+ }
72
+
73
+ initRenderer () {
74
+ this.renderer = new SimulationRenderer(this.config, this.player)
75
+ this.renderer.onTexturesLoaded = () => {
76
+ Ui.removeLoadingScreen()
77
+ this.inited = true
78
+ }
79
+
80
+ this.container = document.getElementById('container')
81
+ this.container.appendChild(this.renderer.domElement)
82
+
83
+ Ui.onPixelSizeChange = pixelSize => {
84
+ this.renderer.setPixelSize(pixelSize)
85
+ }
86
+
87
+ window.addEventListener(
88
+ 'resize', e => {
89
+ this.renderer.setSize(window.innerWidth, window.innerHeight)
90
+ },
91
+ false
92
+ )
93
+
94
+ this.renderer.setSize(window.innerWidth, window.innerHeight, Ui.getSelectedPixelSize())
95
+
96
+ window.addEventListener('wheel', e => {
97
+ e.preventDefault()
98
+
99
+ const delta = e.delta || (e.deltaX + e.deltaY + e.deltaZ)
100
+ if (delta < 0) {
101
+ this.renderer.setZoom(this.renderer.zoom * 1.06)
102
+ }
103
+ else {
104
+ this.renderer.setZoom(this.renderer.zoom / 1.06)
105
+ }
106
+ }, false)
107
+ }
108
+
109
+ initPlayer () {
110
+ this.player = new Player()
111
+ this.player.lookAt(this.config.wormhole.position)
112
+ }
113
+
114
+ initControls () {
115
+ // Add keyboard controls to the player
116
+ this.keyboardControls = new KeyboardControls(this.player, this.container)
117
+ this.keyboardControls.movementSpeed = 1
118
+ this.keyboardControls.rollSpeed = Math.PI / 3
119
+ this.keyboardControls.autoForward = false
120
+ this.keyboardControls.dragToLook = false
121
+
122
+ // Add mobile device controls (touch + accelerometer) to the player
123
+ this.mobileDeviceControls = new MobileDeviceControls(this.player, this.container)
124
+
125
+ this.controlsManager = new ControlsManager(this.keyboardControls, this.mobileDeviceControls)
126
+ this.controlsManager.onChange = device => {
127
+ if (device !== 'mobile') {
128
+ return
129
+ }
130
+
131
+ // The player will probably not be looking with their device in the right direction, so fix that
132
+ requestAnimationFrame(() => {
133
+ this.player.object.quaternion.multiply(this.player.eyes.quaternion.clone().inverse())
134
+ })
135
+ }
136
+
137
+ this.player.addController(this.keyboardControls)
138
+ this.player.addController(this.mobileDeviceControls)
139
+ }
140
+
141
+ initTeleporter () {
142
+ this.teleporter = new Teleporter(this.player)
143
+ this.teleportTargets.forEach(target => {
144
+ this.teleporter.addTarget(target)
145
+ })
146
+
147
+ Ui.onTeleportClick = () => {
148
+ this.teleporter.teleportNext()
149
+ }
150
+
151
+ document.addEventListener('keydown', e => {
152
+ if (e.keyCode === 84) {
153
+ e.preventDefault()
154
+
155
+ this.teleporter.teleportNext()
156
+ }
157
+ })
158
+ }
159
+
160
+ step () {
161
+ if (this.inited) {
162
+ this.update()
163
+ }
164
+
165
+ this.render()
166
+ }
167
+
168
+ start () {
169
+ this.clock = new Clock()
170
+
171
+ const animate = () => {
172
+ requestAnimationFrame(animate)
173
+
174
+ this.step()
175
+ }
176
+
177
+ animate()
178
+ }
179
+
180
+ update () {
181
+ const delta = Math.max(0.001, this.clock.getDelta())
182
+
183
+ this.player.update(delta)
184
+ }
185
+
186
+ render () {
187
+ this.renderer.render()
188
+ }
189
+
190
+ }
191
+
192
+ export default new Simulation()
src/SimulationRenderer.js ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import vertexShader from './shaders/wormholeVertex.glsl'
2
+ import fragmentShader from './shaders/wormholeFragment.glsl'
3
+
4
+ import {
5
+ Math as MathExtra,
6
+ Scene,
7
+ OrthographicCamera,
8
+ Mesh,
9
+ PlaneBufferGeometry,
10
+ ShaderMaterial,
11
+
12
+ Vector3,
13
+ Vector4,
14
+ Quaternion,
15
+ Matrix4,
16
+
17
+ RepeatWrapping,
18
+ LinearFilter,
19
+
20
+ WebGLRenderer,
21
+ TextureLoader
22
+ } from 'three'
23
+
24
+ import EffectComposer from './postprocessing/EffectComposer'
25
+ import RenderPass from './postprocessing/RenderPass'
26
+ import BloomPass from './postprocessing/UnrealBloomPass'
27
+
28
+ const textureLoader = new TextureLoader()
29
+
30
+ export default class SimulationRenderer {
31
+
32
+ constructor (config, player) {
33
+ this.loadConfig(config)
34
+
35
+ this.player = player
36
+
37
+ this.zoom = 1
38
+ this.pixelSize = 4
39
+ this.width = window.innerWidth
40
+ this.height = window.innerHeight
41
+
42
+ this.renderer = new WebGLRenderer()
43
+ this.renderer.setSize(this.width, this.height)
44
+ this.renderer.sortObjects = false
45
+ this.renderer.autoClear = false
46
+ this.domElement = this.renderer.domElement
47
+
48
+ const material = new ShaderMaterial(
49
+ {
50
+ uniforms: this.uniforms,
51
+
52
+ vertexShader,
53
+ fragmentShader,
54
+
55
+ extensions: {
56
+ shaderTextureLOD: true
57
+ }
58
+ }
59
+ )
60
+
61
+ this.scene = new Scene()
62
+ this.camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1)
63
+
64
+ this.quad = new Mesh(new PlaneBufferGeometry(2, 2), material)
65
+ this.quad.frustumCulled = false
66
+ this.scene.add(this.quad)
67
+
68
+ this.renderPasses = [
69
+ new RenderPass(this.scene, this.camera),
70
+ new BloomPass(1024, 2.7, 0.7, 0.8)
71
+ ]
72
+
73
+ this.renderPasses[this.renderPasses.length - 1].renderToScreen = true
74
+
75
+ this.composer = new EffectComposer(this.renderer)
76
+
77
+ this.renderPasses.forEach(pass => {
78
+ this.composer.addPass(pass)
79
+ })
80
+ }
81
+
82
+ loadConfig (config) {
83
+ this.wormholePositionSize = new Vector4(
84
+ config.wormhole.position.x,
85
+ config.wormhole.position.y,
86
+ config.wormhole.position.z,
87
+ config.wormhole.radius
88
+ )
89
+
90
+ this.blackholePositionSize = new Vector4(
91
+ config.blackhole.position.x,
92
+ config.blackhole.position.y,
93
+ config.blackhole.position.z,
94
+ config.blackhole.radius
95
+ )
96
+
97
+ this.saturnPositionSize = new Vector4(
98
+ config.saturn.position.x,
99
+ config.saturn.position.y,
100
+ config.saturn.position.z,
101
+ config.saturn.radius
102
+ )
103
+
104
+ this.planetPositionSize = new Vector4(
105
+ config.planet.position.x,
106
+ config.planet.position.y,
107
+ config.planet.position.z,
108
+ config.planet.radius
109
+ )
110
+
111
+ this.blackholeDisk = config.blackhole.disk
112
+ this.saturnRings = config.saturn.rings
113
+
114
+ this.wormholeGravityRatio = config.wormhole.gravityRatio
115
+
116
+ this.uniforms = {
117
+ wormhole: { type: 'v4', value: this.wormholePositionSize },
118
+ wormholeGravityRatio: { type: 'f', value: this.wormholeGravityRatio },
119
+ blackhole: { type: 'v4', value: this.blackholePositionSize },
120
+
121
+ saturn: { type: 'v4', value: this.saturnPositionSize },
122
+ planet: { type: 'v4', value: this.planetPositionSize },
123
+
124
+ blackholeDisk: { type: 'v4', value: this.blackholeDisk },
125
+ saturnRings: { type: 'v4', value: this.saturnRings },
126
+
127
+ planetDiffuse: { type: 'v3', value: config.planet.diffuse },
128
+ planetSpecular: { type: 'v3', value: config.planet.specular },
129
+ texSaturn: { type: 't', value: this.loadTexture(config.saturn.texture) },
130
+ texSaturnRings: { type: 't', value: this.loadTexture(config.saturn.ringsTexture) },
131
+ texGalaxy1: { type: 't', value: this.loadTexture(config.galaxy1.texture) },
132
+ texGalaxy2: { type: 't', value: this.loadTexture(config.galaxy2.texture) },
133
+ texAccretionDisk: { type: 't', value: this.loadTexture(config.blackhole.diskTexture) },
134
+
135
+ lightDirection: { type: 'v3', value: config.saturn.lightDirection },
136
+
137
+ cameraMatrix: { type: 'm4', value: new Matrix4() },
138
+ cameraGalaxy: { type: 'i', value: 0 }
139
+ }
140
+
141
+ this.uniforms.texSaturnRings.value.wrapS = RepeatWrapping
142
+ this.uniforms.texAccretionDisk.value.wrapS = RepeatWrapping
143
+
144
+ this.uniforms.texSaturn.value.minFilter = LinearFilter
145
+ this.uniforms.texGalaxy1.value.minFilter = LinearFilter
146
+ this.uniforms.texGalaxy2.value.minFilter = LinearFilter
147
+ }
148
+
149
+ loadTexture (path) {
150
+ this._textureCount = (this._textureCount || 0) + 1
151
+
152
+ return textureLoader.load(path, () => {
153
+ this._loadedTexturesCount = (this._loadedTexturesCount || 0) + 1
154
+
155
+ if (this._loadedTexturesCount === this._textureCount) {
156
+ this.onTexturesLoaded && this.onTexturesLoaded()
157
+ }
158
+ })
159
+ }
160
+
161
+ updateEffectComposer () {
162
+ this.composer.setSize(
163
+ Math.floor(this.width / this.pixelSize),
164
+ Math.floor(this.height / this.pixelSize)
165
+ )
166
+ }
167
+
168
+ updateCamera () {
169
+ let vx, vy
170
+ if (this.width > this.height) {
171
+ vx = 1
172
+ vy = this.height / this.width
173
+ }
174
+ else {
175
+ vx = this.width / this.height
176
+ vy = 1
177
+ }
178
+
179
+ this.player.eyes.aspect = vx / vy
180
+ this.player.eyes.fov = MathExtra.RAD2DEG * 2 * Math.atan(vy / this.zoom)
181
+ }
182
+
183
+ setPixelSize (pixelSize) {
184
+ this.pixelSize = pixelSize
185
+
186
+ this.updateEffectComposer()
187
+ }
188
+
189
+ setZoom (zoom) {
190
+ this.zoom = zoom
191
+
192
+ this.updateCamera()
193
+ }
194
+
195
+ setSize (width, height, pixelSize = null) {
196
+ this.width = width
197
+ this.height = height
198
+
199
+ if (pixelSize) {
200
+ this.pixelSize = pixelSize
201
+ }
202
+
203
+ this.renderer.setSize(this.width, this.height)
204
+
205
+ this.updateCamera()
206
+ this.updateEffectComposer()
207
+ }
208
+
209
+ render () {
210
+ const rayScale = new Vector3(
211
+ this.player.eyes.aspect,
212
+ 1,
213
+ 1 / Math.tan(MathExtra.DEG2RAD * this.player.eyes.fov / 2)
214
+ )
215
+
216
+ const worldPosition = new Vector3()
217
+ const worldQuaternion = new Quaternion()
218
+
219
+ this.uniforms.cameraMatrix.value.compose(
220
+ this.player.eyes.getWorldPosition(worldPosition),
221
+ this.player.eyes.getWorldQuaternion(worldQuaternion),
222
+ rayScale
223
+ )
224
+
225
+ this.uniforms.cameraGalaxy.value = this.player.galaxy
226
+
227
+ this.renderer.clear()
228
+ this.composer.render()
229
+ }
230
+
231
+ }
src/Teleporter.js ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Simulation from './Simulation'
2
+
3
+ export default class Teleporter {
4
+
5
+ constructor (player) {
6
+ this.player = player
7
+ this.targets = []
8
+ }
9
+
10
+ addTarget ({ position, lookAt, galaxy }) {
11
+ this.targets.push({
12
+ position,
13
+ lookAt,
14
+ galaxy
15
+ })
16
+ }
17
+
18
+ getClosestTeleportIndex () {
19
+ let minDistance = Infinity
20
+ let result = null
21
+
22
+ for (let i = 0; i < this.targets.length; i++) {
23
+ let distance
24
+
25
+ if (this.targets[i].galaxy !== this.player.galaxy) {
26
+ distance =
27
+ this.player.object.position.distanceTo(Simulation.config.wormhole.position) +
28
+ this.targets[i].position.distanceTo(Simulation.config.wormhole.position)
29
+ }
30
+ else {
31
+ distance = this.player.object.position.distanceTo(this.targets[i].position)
32
+ }
33
+
34
+ if (distance < minDistance) {
35
+ minDistance = distance
36
+ result = i
37
+ }
38
+ }
39
+ return result
40
+ }
41
+
42
+ teleportNext () {
43
+ const nextIndex = (this.getClosestTeleportIndex() + 1) % this.targets.length
44
+
45
+ this.teleportTo(this.targets[nextIndex])
46
+ }
47
+
48
+ teleportTo (target) {
49
+ this.player.object.position.copy(target.position)
50
+ this.player.galaxy = target.galaxy
51
+
52
+ this.player.lookAt(target.lookAt)
53
+ }
54
+
55
+ }
src/Ui.js ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class UiController {
2
+
3
+ constructor () {
4
+ this.initUiToggle()
5
+ this.initTeleportButton()
6
+ this.initRadioButtons()
7
+ }
8
+
9
+ initUiToggle () {
10
+ const uiToggle = document.querySelector('.ui-toggle input')
11
+
12
+ const onUIToggle = () => {
13
+ if (uiToggle.checked) {
14
+ document.body.classList.add('no-ui')
15
+ }
16
+ else {
17
+ document.body.classList.remove('no-ui')
18
+ }
19
+ uiToggle.blur()
20
+ }
21
+
22
+ uiToggle.addEventListener('change', onUIToggle)
23
+
24
+ onUIToggle()
25
+ }
26
+
27
+ initTeleportButton () {
28
+ document.querySelector('#teleport')
29
+ .addEventListener('touchstart', () => {
30
+ this.onTeleportClick && this.onTeleportClick()
31
+ }, false)
32
+ }
33
+
34
+ initRadioButtons () {
35
+ document.querySelector('#resolution')
36
+ .addEventListener('change', event => {
37
+ event.target.blur()
38
+
39
+ const pixelSize = this.getSelectedPixelSize()
40
+ this.onPixelSizeChange && this.onPixelSizeChange(pixelSize)
41
+ }, false)
42
+ }
43
+
44
+ getSelectedPixelSize () {
45
+ return parseInt(document.querySelector('[name=resolution]:checked').value)
46
+ }
47
+
48
+ showWebGLError () {
49
+ document.querySelector('#webgl-error').style.display = 'block'
50
+ this.removeLoadingScreen()
51
+ }
52
+
53
+ removeLoadingScreen () {
54
+ const el = document.getElementById('loading')
55
+ el.parentElement.removeChild(el)
56
+ }
57
+
58
+ setUiForDesktop () {
59
+ document.body.classList.remove('mobile-device')
60
+ }
61
+
62
+ setUiForMobile () {
63
+ document.body.classList.add('mobile-device')
64
+ }
65
+
66
+ }
67
+
68
+ export default new UiController()
src/controls/ControlsBase.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ export default class ControlsBase {
2
+
3
+ constructor (player, element) {
4
+ this.player = player
5
+ this.element = element
6
+
7
+ this.enabled = false
8
+ }
9
+
10
+ }
src/controls/ControlsManager.js ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Ui from '../Ui'
2
+
3
+ export default class ControlsManager {
4
+
5
+ constructor (keyboardControls, mobileDeviceControls) {
6
+ this.keyboardControls = keyboardControls
7
+ this.mobileDeviceControls = mobileDeviceControls
8
+
9
+ this.onKeyPress = this.onKeyPress.bind(this)
10
+ this.onDeviceOrientation = this.onDeviceOrientation.bind(this)
11
+
12
+ this.setDesktop()
13
+ }
14
+
15
+ setDesktop () {
16
+ window.addEventListener('deviceorientation', this.onDeviceOrientation, false)
17
+
18
+ this.keyboardControls.enable()
19
+ this.mobileDeviceControls.disable()
20
+
21
+ Ui.setUiForDesktop()
22
+
23
+ this.onChange && this.onChange('desktop')
24
+ }
25
+
26
+ setMobile () {
27
+ this.controls = 'mobile'
28
+
29
+ window.addEventListener('keypress', this.onKeyPress, false)
30
+ window.removeEventListener('deviceorientation', this.onDeviceOrientation, false)
31
+
32
+ this.keyboardControls.disable()
33
+ this.mobileDeviceControls.enable()
34
+
35
+ Ui.setUiForMobile()
36
+
37
+ this.onChange && this.onChange('mobile')
38
+ }
39
+
40
+ onKeyPress (event) {
41
+ this.setDesktop()
42
+ }
43
+
44
+ onDeviceOrientation (event) {
45
+ if (event.alpha === null) {
46
+ return
47
+ }
48
+
49
+ this.setMobile()
50
+ }
51
+
52
+ }
src/controls/DeviceOrientationControls.js ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Math as MathExtra,
3
+ Vector3,
4
+ Euler,
5
+ Quaternion
6
+ } from 'three'
7
+
8
+ import ControlsBase from './ControlsBase'
9
+
10
+ const zee = new Vector3(0, 0, 1)
11
+ const euler = new Euler()
12
+ const q0 = new Quaternion()
13
+ const q1 = new Quaternion(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5)) // - PI/2 around the x-axis
14
+
15
+ export default class DeviceOrientationControls extends ControlsBase {
16
+
17
+ constructor (player) {
18
+ super(player, null)
19
+
20
+ this.object = player.eyes
21
+ this.object.rotation.reorder('YXZ')
22
+
23
+ // Let's always listen to orientation changes, even before it's enabled
24
+ // For some reason the orientation lags a few frames behind once we start listening
25
+ window.addEventListener('orientationchange', this.onScreenOrientationChangeEvent.bind(this), false)
26
+ window.addEventListener('deviceorientation', this.onDeviceOrientationChangeEvent.bind(this), false)
27
+ }
28
+
29
+ enable () {
30
+ this.onScreenOrientationChangeEvent()
31
+
32
+ this.enabled = true
33
+ }
34
+
35
+ disable () {
36
+ this.enabled = false
37
+ }
38
+
39
+ update () {
40
+ if (this.enabled === false || !this.deviceOrientation || this.deviceOrientation.alpha === null) {
41
+ return
42
+ }
43
+
44
+ const alpha = this.deviceOrientation.alpha ? MathExtra.degToRad(this.deviceOrientation.alpha) : 0 // Z
45
+ const beta = this.deviceOrientation.beta ? MathExtra.degToRad(this.deviceOrientation.beta) : 0 // X'
46
+ const gamma = this.deviceOrientation.gamma ? MathExtra.degToRad(this.deviceOrientation.gamma) : 0 // Y''
47
+ const orient = this.screenOrientation ? MathExtra.degToRad(this.screenOrientation) : 0 // O
48
+
49
+ euler.set(beta, alpha, -gamma, 'YXZ') // 'ZXY' for the device, but 'YXZ' for us
50
+
51
+ this.object.quaternion.setFromEuler(euler) // orient the device
52
+ this.object.quaternion.multiply(q1) // camera looks out the back of the device, not the top
53
+ this.object.quaternion.multiply(q0.setFromAxisAngle(zee, -orient)) // adjust for screen orientation
54
+ }
55
+
56
+ onDeviceOrientationChangeEvent (event) {
57
+ this.deviceOrientation = event
58
+ }
59
+
60
+ onScreenOrientationChangeEvent () {
61
+ this.screenOrientation = window.orientation || 0
62
+ }
63
+
64
+ }
src/controls/KeyboardControls.js ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Vector3,
3
+ Quaternion
4
+ } from 'three'
5
+
6
+ import ControlsBase from './ControlsBase'
7
+
8
+ export default class KeyboardControls extends ControlsBase {
9
+
10
+ constructor (player, element) {
11
+ super(player, element)
12
+
13
+ this.element.setAttribute('tabindex', -1)
14
+
15
+ this.movementSpeedMultiplier = 1
16
+ this.movementSpeed = 1.0
17
+ this.rollSpeed = 0.005
18
+
19
+ this.mouseStatus = 0
20
+ this.dragToLook = false
21
+ this.autoForward = false
22
+
23
+ this.moveState = {
24
+ up: 0,
25
+ down: 0,
26
+ left: 0,
27
+ right: 0,
28
+ forward: 0,
29
+ back: 0,
30
+ pitchUp: 0,
31
+ pitchDown: 0,
32
+ yawLeft: 0,
33
+ yawRight: 0,
34
+ rollLeft: 0,
35
+ rollRight: 0
36
+ }
37
+
38
+ this.moveVector = new Vector3(0, 0, 0)
39
+ this.rotationVector = new Vector3(0, 0, 0)
40
+
41
+ this.contextMenu = this.contextMenu.bind(this)
42
+ this.mouseMove = this.mouseMove.bind(this)
43
+ this.mouseDown = this.mouseDown.bind(this)
44
+ this.mouseUp = this.mouseUp.bind(this)
45
+ this.keyDown = this.keyDown.bind(this)
46
+ this.keyUp = this.keyUp.bind(this)
47
+ }
48
+
49
+ keyDown (event) {
50
+ if (event.altKey) {
51
+ return
52
+ }
53
+
54
+ switch (event.keyCode) {
55
+ case 16: // Shift
56
+ this.movementSpeedMultiplier = 10
57
+ break
58
+
59
+ case 87: // W
60
+ this.moveState.forward = 1
61
+ break
62
+
63
+ case 83: // S
64
+ this.moveState.back = 1
65
+ break
66
+
67
+ case 65: // A
68
+ this.moveState.left = 1
69
+ break
70
+
71
+ case 68: // D
72
+ this.moveState.right = 1
73
+ break
74
+
75
+ case 82: // R
76
+ this.moveState.up = 1
77
+ break
78
+
79
+ case 70: // F
80
+ this.moveState.down = 1
81
+ break
82
+
83
+ case 38: // Up
84
+ this.moveState.pitchUp = 1
85
+ break
86
+
87
+ case 40: // Down
88
+ this.moveState.pitchDown = 1
89
+ break
90
+
91
+ case 37: // Left
92
+ this.moveState.yawLeft = 1
93
+ break
94
+
95
+ case 39: // Right
96
+ this.moveState.yawRight = 1
97
+ break
98
+
99
+ case 81: // Q
100
+ this.moveState.rollLeft = 1
101
+ break
102
+
103
+ case 69: // E
104
+ this.moveState.rollRight = 1
105
+ break
106
+
107
+ // Control-scheme modifiers:
108
+ case 32:
109
+ this.dragToLook = !this.dragToLook
110
+ this.resetDragToLook()
111
+ break
112
+ case 27:
113
+ this.dragToLook = true
114
+ this.resetDragToLook()
115
+ break
116
+ }
117
+
118
+ this.updateMovementVector()
119
+ this.updateRotationVector()
120
+ }
121
+
122
+ keyUp (event) {
123
+ switch (event.keyCode) {
124
+ case 16: // Shift
125
+ this.movementSpeedMultiplier = 1
126
+ break
127
+
128
+ case 87: // W
129
+ this.moveState.forward = 0
130
+ break
131
+
132
+ case 83: // S
133
+ this.moveState.back = 0
134
+ break
135
+
136
+ case 65: // A
137
+ this.moveState.left = 0
138
+ break
139
+
140
+ case 68: // D
141
+ this.moveState.right = 0
142
+ break
143
+
144
+ case 82: // R
145
+ this.moveState.up = 0
146
+ break
147
+
148
+ case 70: // F
149
+ this.moveState.down = 0
150
+ break
151
+
152
+ case 38: // Up
153
+ this.moveState.pitchUp = 0
154
+ break
155
+
156
+ case 40: // Down
157
+ this.moveState.pitchDown = 0
158
+ break
159
+
160
+ case 37: // Left
161
+ this.moveState.yawLeft = 0
162
+ break
163
+
164
+ case 39: // Right
165
+ this.moveState.yawRight = 0
166
+ break
167
+
168
+ case 81: // Q
169
+ this.moveState.rollLeft = 0
170
+ break
171
+
172
+ case 69: // E
173
+ this.moveState.rollRight = 0
174
+ break
175
+ }
176
+
177
+ this.updateMovementVector()
178
+ this.updateRotationVector()
179
+ }
180
+
181
+ resetDragToLook () {
182
+ if (this.dragToLook) {
183
+ this.moveState.yawLeft = 0
184
+ this.moveState.pitchDown = 0
185
+ }
186
+ }
187
+
188
+ mouseDown (event) {
189
+ if (this.element !== document) {
190
+ this.element.focus()
191
+ }
192
+
193
+ event.preventDefault()
194
+ event.stopPropagation()
195
+
196
+ if (this.dragToLook) {
197
+ this.mouseStatus += 1
198
+ }
199
+ else {
200
+ switch (event.button) {
201
+ case 0:
202
+ this.moveState.forward = 1
203
+ break
204
+
205
+ case 2:
206
+ this.moveState.back = 1
207
+ break
208
+ }
209
+
210
+ this.updateMovementVector()
211
+ }
212
+ }
213
+
214
+ mouseMove (event) {
215
+ if (this.dragToLook && this.mouseStatus === 0) {
216
+ return
217
+ }
218
+
219
+ const container = this.getContainerDimensions()
220
+ const halfWidth = container.size[0] / 2
221
+ const halfHeight = container.size[1] / 2
222
+
223
+ this.moveState.yawLeft = -((event.pageX - container.offset[0]) - halfWidth) / halfWidth
224
+ this.moveState.pitchDown = ((event.pageY - container.offset[1]) - halfHeight) / halfHeight
225
+
226
+ this.updateRotationVector()
227
+ }
228
+
229
+ mouseUp (event) {
230
+ event.preventDefault()
231
+ event.stopPropagation()
232
+
233
+ if (this.dragToLook) {
234
+ this.mouseStatus -= 1
235
+ this.moveState.yawLeft = this.moveState.pitchDown = 0
236
+ }
237
+ else {
238
+ switch (event.button) {
239
+ case 0:
240
+ this.moveState.forward = 0
241
+ break
242
+
243
+ case 2:
244
+ this.moveState.back = 0
245
+ break
246
+ }
247
+
248
+ this.updateMovementVector()
249
+ }
250
+
251
+ this.updateRotationVector()
252
+ }
253
+
254
+ updateMovementVector () {
255
+ const forward = (this.moveState.forward || (this.autoForward && !this.moveState.back)) ? 1 : 0
256
+
257
+ this.moveVector.x = (-this.moveState.left + this.moveState.right)
258
+ this.moveVector.y = (-this.moveState.down + this.moveState.up)
259
+ this.moveVector.z = (-forward + this.moveState.back)
260
+ }
261
+
262
+ updateRotationVector () {
263
+ this.rotationVector.x = (-this.moveState.pitchDown + this.moveState.pitchUp)
264
+ this.rotationVector.y = (-this.moveState.yawRight + this.moveState.yawLeft)
265
+ this.rotationVector.z = (-this.moveState.rollRight + this.moveState.rollLeft)
266
+ }
267
+
268
+ getContainerDimensions () {
269
+ if (this.element !== document) {
270
+ return {
271
+ size: [this.element.offsetWidth, this.element.offsetHeight],
272
+ offset: [this.element.offsetLeft, this.element.offsetTop]
273
+ }
274
+ }
275
+ else {
276
+ return {
277
+ size: [window.innerWidth, window.innerHeight],
278
+ offset: [0, 0]
279
+ }
280
+ }
281
+ }
282
+
283
+ contextMenu (event) {
284
+ event.preventDefault()
285
+ }
286
+
287
+ enable () {
288
+ this.updateMovementVector()
289
+ this.updateRotationVector()
290
+
291
+ this.element.addEventListener('contextmenu', this.contextMenu, false)
292
+
293
+ this.element.addEventListener('mousemove', this.mouseMove, false)
294
+ this.element.addEventListener('mousedown', this.mouseDown, false)
295
+ this.element.addEventListener('mouseup', this.mouseUp, false)
296
+
297
+ window.addEventListener('keydown', this.keyDown, false)
298
+ window.addEventListener('keyup', this.keyUp, false)
299
+
300
+ this.enabled = true
301
+ }
302
+
303
+ disable () {
304
+ this.element.removeEventListener('contextmenu', this.contextMenu, false)
305
+
306
+ this.element.removeEventListener('mousemove', this.mouseMove, false)
307
+ this.element.removeEventListener('mousedown', this.mouseDown, false)
308
+ this.element.removeEventListener('mouseup', this.mouseUp, false)
309
+
310
+ window.removeEventListener('keydown', this.keyDown, false)
311
+ window.removeEventListener('keyup', this.keyUp, false)
312
+
313
+ this.enabled = false
314
+ }
315
+
316
+ update () {
317
+ const moveMult = this.movementSpeed * this.movementSpeedMultiplier
318
+ const rotMult = this.rollSpeed
319
+ const worldQuaternion = new Quaternion()
320
+
321
+ this.player.eyes.getWorldQuaternion(worldQuaternion)
322
+
323
+ this.player.velocity
324
+ .copy(this.moveVector)
325
+ .multiplyScalar(moveMult)
326
+ .applyQuaternion(worldQuaternion)
327
+
328
+ this.player.eyeAngularVelocity
329
+ .copy(this.rotationVector)
330
+ .multiplyScalar(rotMult)
331
+ }
332
+
333
+ }
src/controls/MobileDeviceControls.js ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import DeviceOrientationControls from './DeviceOrientationControls'
2
+ import TouchControls from './TouchControls'
3
+
4
+ import ControlsBase from './ControlsBase'
5
+
6
+ export default class MobileDeviceControls extends ControlsBase {
7
+
8
+ constructor (player, element) {
9
+ super(player, element)
10
+
11
+ this.deviceOrientationControls = new DeviceOrientationControls(player)
12
+ this.touchControls = new TouchControls(player, element)
13
+ }
14
+
15
+ enable () {
16
+ this.deviceOrientationControls.enable()
17
+ this.touchControls.enable()
18
+ this.enabled = true
19
+ }
20
+
21
+ disable () {
22
+ this.deviceOrientationControls.disable()
23
+ this.touchControls.disable()
24
+
25
+ this.enabled = false
26
+ }
27
+
28
+ update () {
29
+ if (this.enabled === false) {
30
+ return
31
+ }
32
+
33
+ this.deviceOrientationControls.update()
34
+ this.touchControls.update()
35
+ }
36
+
37
+ }
src/controls/TouchControls.js ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Math as MathExtra,
3
+ Vector3,
4
+ Quaternion
5
+ } from 'three'
6
+
7
+ import ControlsBase from './ControlsBase'
8
+
9
+ export default class TouchControls extends ControlsBase {
10
+
11
+ constructor (player, element) {
12
+ super(player, element)
13
+
14
+ this.velocity = new Vector3(0, 0, 0)
15
+
16
+ this.onTouchEvent = this.onTouchEvent.bind(this)
17
+ }
18
+
19
+ onTouchEvent (event) {
20
+ event.preventDefault()
21
+
22
+ const touches = event.touches
23
+
24
+ if (touches.length === 0) {
25
+ this.velocity.set(0, 0, 0)
26
+ return
27
+ }
28
+
29
+ let avgX = 0
30
+ let avgY = 0
31
+ for (let i = 0; i < touches.length; i++) {
32
+ avgX += touches[i].pageX
33
+ avgY += touches[i].pageY
34
+ }
35
+ avgX /= touches.length
36
+ avgY /= touches.length
37
+
38
+ const vy = Math.tan(0.5 * MathExtra.DEG2RAD * this.player.eyes.fov)
39
+ const vx = this.player.eyes.aspect * vy
40
+
41
+ this.velocity.set(
42
+ vx * (avgX * 2.0 / window.innerWidth - 1.0),
43
+ -vy * (avgY * 2.0 / window.innerHeight - 1.0),
44
+ -1
45
+ ).normalize()
46
+
47
+ this.velocity.multiplyScalar(touches.length > 1 ? 10 : 1)
48
+ }
49
+
50
+ enable () {
51
+ this.element.addEventListener('touchstart', this.onTouchEvent, false)
52
+ this.element.addEventListener('touchmove', this.onTouchEvent, false)
53
+ this.element.addEventListener('touchend', this.onTouchEvent, false)
54
+
55
+ this.enabled = true
56
+ }
57
+
58
+ disable () {
59
+ this.element.removeEventListener('touchstart', this.onTouchEvent, false)
60
+ this.element.removeEventListener('touchmove', this.onTouchEvent, false)
61
+ this.element.removeEventListener('touchend', this.onTouchEvent, false)
62
+
63
+ this.enabled = false
64
+ }
65
+
66
+ update () {
67
+ if (this.enabled === false) {
68
+ return
69
+ }
70
+
71
+ const worldQuaternion = new Quaternion()
72
+
73
+ this.player.eyes.getWorldQuaternion(worldQuaternion)
74
+
75
+ this.player.velocity
76
+ .copy(this.velocity)
77
+ .applyQuaternion(worldQuaternion)
78
+ }
79
+
80
+ }
src/main.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Simulation from './Simulation'
2
+ import Ui from './Ui'
3
+ import { isWebGlSupported } from './utils'
4
+
5
+ if (!isWebGlSupported()) {
6
+ Ui.showWebGLError()
7
+ }
8
+ else {
9
+ Simulation.init()
10
+ Simulation.start()
11
+ }
src/postprocessing/CopyShader.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import simpleVertexCode from '../shaders/simpleVertex.glsl'
2
+ import copyCode from '../shaders/copy.glsl'
3
+
4
+ export default {
5
+
6
+ uniforms: {
7
+ tDiffuse: { value: null },
8
+ opacity: { value: 1.0 }
9
+ },
10
+
11
+ vertexShader: simpleVertexCode,
12
+ fragmentShader: copyCode
13
+
14
+ }
src/postprocessing/EffectComposer.js ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * The source code of this file is heavily based on
3
+ * https://github.com/mrdoob/three.js/blob/dev/examples/js/postprocessing/EffectComposer.js
4
+ */
5
+
6
+ import { WebGLRenderTarget, LinearFilter, RGBAFormat } from 'three'
7
+
8
+ export default class EffectComposer {
9
+
10
+ constructor (renderer, renderTarget) {
11
+ this.renderer = renderer
12
+
13
+ if (renderTarget === undefined) {
14
+ const parameters = {
15
+ minFilter: LinearFilter,
16
+ magFilter: LinearFilter,
17
+ format: RGBAFormat,
18
+ stencilBuffer: false
19
+ }
20
+
21
+ const size = renderer.getDrawingBufferSize()
22
+ renderTarget = new WebGLRenderTarget(size.width, size.height, parameters)
23
+ renderTarget.texture.name = 'EffectComposer.rt1'
24
+ }
25
+
26
+ this.renderTarget1 = renderTarget
27
+ this.renderTarget2 = renderTarget.clone()
28
+ this.renderTarget2.texture.name = 'EffectComposer.rt2'
29
+
30
+ this.writeBuffer = this.renderTarget1
31
+ this.readBuffer = this.renderTarget2
32
+
33
+ this.passes = []
34
+ }
35
+
36
+ swapBuffers () {
37
+ const tmp = this.readBuffer
38
+ this.readBuffer = this.writeBuffer
39
+ this.writeBuffer = tmp
40
+ }
41
+
42
+ addPass (pass) {
43
+ this.passes.push(pass)
44
+
45
+ const size = this.renderer.getDrawingBufferSize()
46
+ pass.setSize(size.width, size.height)
47
+ }
48
+
49
+ render () {
50
+ for (let i = 0; i < this.passes.length; i++) {
51
+ let pass = this.passes[i]
52
+
53
+ pass.render(this.renderer, this.writeBuffer, this.readBuffer)
54
+ }
55
+ }
56
+
57
+ setSize (width, height) {
58
+ this.renderTarget1.setSize(width, height)
59
+ this.renderTarget2.setSize(width, height)
60
+
61
+ for (let i = 0; i < this.passes.length; i++) {
62
+ this.passes[i].setSize(width, height)
63
+ }
64
+ }
65
+
66
+ }
src/postprocessing/LuminosityHighPassShader.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Color } from 'three'
2
+
3
+ import simpleVertexCode from '../shaders/simpleVertex.glsl'
4
+ import luminosityHighPassCode from '../shaders/luminosityHighPass.glsl'
5
+
6
+ export default {
7
+
8
+ shaderID: 'luminosityHighPass',
9
+
10
+ uniforms: {
11
+ tDiffuse: { type: 't', value: null },
12
+ luminosityThreshold: { type: 'f', value: 1.0 },
13
+ smoothWidth: { type: 'f', value: 1.0 },
14
+ defaultColor: { type: 'c', value: new Color(0x000000) },
15
+ defaultOpacity: { type: 'f', value: 0.0 }
16
+ },
17
+
18
+ vertexShader: simpleVertexCode,
19
+ fragmentShader: luminosityHighPassCode
20
+
21
+ }
src/postprocessing/Pass.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ OrthographicCamera,
3
+ Scene,
4
+ Mesh,
5
+ PlaneBufferGeometry
6
+ } from 'three'
7
+
8
+ export default class Pass {
9
+
10
+ constructor () {
11
+ this.clear = false
12
+ this.renderToScreen = false
13
+
14
+ this.camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1)
15
+ this.scene = new Scene()
16
+
17
+ this.quad = new Mesh(new PlaneBufferGeometry(2, 2), null)
18
+ this.quad.frustumCulled = false // Avoid getting clipped
19
+ this.scene.add(this.quad)
20
+ }
21
+
22
+ setSize (width, height) {
23
+
24
+ }
25
+
26
+ render (renderer, writeBuffer, readBuffer) {
27
+
28
+ }
29
+
30
+ }
src/postprocessing/RenderPass.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Pass from './Pass'
2
+
3
+ export default class RenderPass extends Pass {
4
+
5
+ constructor (scene, camera) {
6
+ super()
7
+
8
+ this.scene = scene
9
+ this.camera = camera
10
+ }
11
+
12
+ render (renderer, writeBuffer, readBuffer) {
13
+ const oldAutoClear = renderer.autoClear
14
+ renderer.autoClear = false
15
+
16
+ this.scene.overrideMaterial = this.overrideMaterial
17
+ renderer.render(this.scene, this.camera, this.renderToScreen ? null : readBuffer, true)
18
+
19
+ this.scene.overrideMaterial = null
20
+ renderer.autoClear = oldAutoClear
21
+ }
22
+
23
+ }
src/postprocessing/UnrealBloomPass.js ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * The source code of this file is heavily based on
3
+ * https://github.com/mrdoob/three.js/blob/dev/examples/js/postprocessing/UnrealBloomPass.js
4
+ */
5
+
6
+ import {
7
+ Vector2,
8
+ Vector3,
9
+ Color,
10
+
11
+ ShaderMaterial,
12
+ MeshBasicMaterial,
13
+
14
+ LinearFilter,
15
+ RGBAFormat,
16
+ AdditiveBlending,
17
+
18
+ UniformsUtils,
19
+
20
+ WebGLRenderTarget
21
+ } from 'three'
22
+
23
+ import Pass from './Pass'
24
+ import CopyShader from './CopyShader'
25
+ import LuminosityHighPassShader from './LuminosityHighPassShader'
26
+ import simpleVertexCode from '../shaders/simpleVertex.glsl'
27
+ import seperableBlurCode from '../shaders/seperableBlur.glsl'
28
+ import bloomCompositeCode from '../shaders/bloomComposite.glsl'
29
+
30
+ export default class UnrealBloomPass extends Pass {
31
+
32
+ constructor (resolution, strength, radius, threshold) {
33
+ super()
34
+
35
+ this.strength = strength
36
+ this.radius = radius
37
+ this.threshold = threshold
38
+ this.resolution = new Vector2(resolution.x, resolution.y)
39
+
40
+ // render targets
41
+ const pars = {
42
+ minFilter: LinearFilter,
43
+ magFilter: LinearFilter,
44
+ format: RGBAFormat
45
+ }
46
+
47
+ this.renderTargetsHorizontal = []
48
+ this.renderTargetsVertical = []
49
+ this.nMips = 5
50
+
51
+ let resx = Math.round(this.resolution.x / 2)
52
+ let resy = Math.round(this.resolution.y / 2)
53
+
54
+ this.renderTargetBright = new WebGLRenderTarget(resx, resy, pars)
55
+ this.renderTargetBright.texture.name = 'UnrealBloomPass.bright'
56
+ this.renderTargetBright.texture.generateMipmaps = false
57
+
58
+ for (let i = 0; i < this.nMips; i++) {
59
+ let renderTarget = new WebGLRenderTarget(resx, resy, pars)
60
+
61
+ renderTarget.texture.name = `UnrealBloomPass.h${i}`
62
+ renderTarget.texture.generateMipmaps = false
63
+
64
+ this.renderTargetsHorizontal.push(renderTarget)
65
+
66
+ renderTarget = new WebGLRenderTarget(resx, resy, pars)
67
+
68
+ renderTarget.texture.name = `UnrealBloomPass.v${i}`
69
+ renderTarget.texture.generateMipmaps = false
70
+
71
+ this.renderTargetsVertical.push(renderTarget)
72
+
73
+ resx = Math.round(resx / 2)
74
+ resy = Math.round(resy / 2)
75
+ }
76
+
77
+ this.highPassUniforms = UniformsUtils.clone(LuminosityHighPassShader.uniforms)
78
+
79
+ this.highPassUniforms.luminosityThreshold.value = threshold
80
+ this.highPassUniforms.smoothWidth.value = 0.01
81
+
82
+ this.materialHighPassFilter = new ShaderMaterial(
83
+ {
84
+ uniforms: this.highPassUniforms,
85
+ vertexShader: LuminosityHighPassShader.vertexShader,
86
+ fragmentShader: LuminosityHighPassShader.fragmentShader,
87
+ defines: {}
88
+ }
89
+ )
90
+
91
+ // Gaussian Blur Materials
92
+ this.separableBlurMaterials = []
93
+
94
+ const kernelSizeArray = [3, 5, 7, 9, 11]
95
+
96
+ resx = Math.round(this.resolution.x / 2)
97
+ resy = Math.round(this.resolution.y / 2)
98
+
99
+ for (let i = 0; i < this.nMips; i++) {
100
+ this.separableBlurMaterials.push(this.getSeperableBlurMaterial(kernelSizeArray[i]))
101
+ this.separableBlurMaterials[i].uniforms.texSize.value = new Vector2(resx, resy)
102
+
103
+ resx = Math.round(resx / 2)
104
+ resy = Math.round(resy / 2)
105
+ }
106
+
107
+ // Composite material
108
+ this.compositeMaterial = this.getCompositeMaterial(this.nMips)
109
+ this.compositeMaterial.uniforms.blurTexture1.value = this.renderTargetsVertical[0].texture
110
+ this.compositeMaterial.uniforms.blurTexture2.value = this.renderTargetsVertical[1].texture
111
+ this.compositeMaterial.uniforms.blurTexture3.value = this.renderTargetsVertical[2].texture
112
+ this.compositeMaterial.uniforms.blurTexture4.value = this.renderTargetsVertical[3].texture
113
+ this.compositeMaterial.uniforms.blurTexture5.value = this.renderTargetsVertical[4].texture
114
+ this.compositeMaterial.uniforms.bloomStrength.value = strength
115
+ this.compositeMaterial.uniforms.bloomRadius.value = 0.1
116
+ this.compositeMaterial.needsUpdate = true
117
+
118
+ const bloomFactors = [1.0, 0.8, 0.6, 0.4, 0.2]
119
+ this.compositeMaterial.uniforms.bloomFactors.value = bloomFactors
120
+ this.bloomTintColors = [
121
+ new Vector3(1, 1, 1),
122
+ new Vector3(1, 1, 1),
123
+ new Vector3(1, 1, 1),
124
+ new Vector3(1, 1, 1),
125
+ new Vector3(1, 1, 1)
126
+ ]
127
+ this.compositeMaterial.uniforms.bloomTintColors.value = this.bloomTintColors
128
+
129
+ this.copyUniforms = UniformsUtils.clone(CopyShader.uniforms)
130
+ this.copyUniforms.opacity.value = 1.0
131
+
132
+ this.materialCopy = new ShaderMaterial(
133
+ {
134
+ uniforms: this.copyUniforms,
135
+ vertexShader: CopyShader.vertexShader,
136
+ fragmentShader: CopyShader.fragmentShader,
137
+ blending: AdditiveBlending,
138
+ depthTest: false,
139
+ depthWrite: false,
140
+ transparent: true
141
+ }
142
+ )
143
+
144
+ this.oldClearColor = new Color()
145
+ this.oldClearAlpha = 1
146
+
147
+ this.basic = new MeshBasicMaterial()
148
+ }
149
+
150
+ dispose () {
151
+ for (let i = 0; i < this.renderTargetsHorizontal.length; i++) {
152
+ this.renderTargetsHorizontal[i].dispose()
153
+ }
154
+
155
+ for (let i = 0; i < this.renderTargetsVertical.length; i++) {
156
+ this.renderTargetsVertical[i].dispose()
157
+ }
158
+
159
+ this.renderTargetBright.dispose()
160
+ }
161
+
162
+ setSize (width, height) {
163
+ let resx = Math.round(width / 2)
164
+ let resy = Math.round(height / 2)
165
+
166
+ this.renderTargetBright.setSize(resx, resy)
167
+
168
+ for (let i = 0; i < this.nMips; i++) {
169
+ this.renderTargetsHorizontal[i].setSize(resx, resy)
170
+ this.renderTargetsVertical[i].setSize(resx, resy)
171
+
172
+ this.separableBlurMaterials[i].uniforms.texSize.value = new Vector2(resx, resy)
173
+
174
+ resx = Math.round(resx / 2)
175
+ resy = Math.round(resy / 2)
176
+ }
177
+ }
178
+
179
+ render (renderer, writeBuffer, readBuffer) {
180
+ this.oldClearColor.copy(renderer.getClearColor())
181
+ this.oldClearAlpha = renderer.getClearAlpha()
182
+ const oldAutoClear = renderer.autoClear
183
+ renderer.autoClear = false
184
+
185
+ renderer.setClearColor(new Color(0, 0, 0), 0)
186
+
187
+ // Render input to screen
188
+
189
+ if (this.renderToScreen) {
190
+ this.quad.material = this.basic
191
+ this.basic.map = readBuffer.texture
192
+
193
+ renderer.render(this.scene, this.camera, undefined, true)
194
+ }
195
+
196
+ // 1. Extract Bright Areas
197
+
198
+ this.highPassUniforms.tDiffuse.value = readBuffer.texture
199
+ this.highPassUniforms.luminosityThreshold.value = this.threshold
200
+ this.quad.material = this.materialHighPassFilter
201
+
202
+ renderer.render(this.scene, this.camera, this.renderTargetBright, true)
203
+
204
+ // 2. Blur All the mips progressively
205
+
206
+ let inputRenderTarget = this.renderTargetBright
207
+
208
+ for (let i = 0; i < this.nMips; i++) {
209
+ this.quad.material = this.separableBlurMaterials[i]
210
+
211
+ this.separableBlurMaterials[i].uniforms.colorTexture.value = inputRenderTarget.texture
212
+ this.separableBlurMaterials[i].uniforms.direction.value = UnrealBloomPass.BlurDirectionX
213
+ renderer.render(this.scene, this.camera, this.renderTargetsHorizontal[i], true)
214
+
215
+ this.separableBlurMaterials[i].uniforms.colorTexture.value = this.renderTargetsHorizontal[i].texture
216
+ this.separableBlurMaterials[i].uniforms.direction.value = UnrealBloomPass.BlurDirectionY
217
+ renderer.render(this.scene, this.camera, this.renderTargetsVertical[i], true)
218
+
219
+ inputRenderTarget = this.renderTargetsVertical[i]
220
+ }
221
+
222
+ // Composite All the mips
223
+
224
+ this.quad.material = this.compositeMaterial
225
+ this.compositeMaterial.uniforms.bloomStrength.value = this.strength
226
+ this.compositeMaterial.uniforms.bloomRadius.value = this.radius
227
+ this.compositeMaterial.uniforms.bloomTintColors.value = this.bloomTintColors
228
+
229
+ renderer.render(this.scene, this.camera, this.renderTargetsHorizontal[0], true)
230
+
231
+ // Blend it additively over the input texture
232
+
233
+ this.quad.material = this.materialCopy
234
+ this.copyUniforms.tDiffuse.value = this.renderTargetsHorizontal[0].texture
235
+
236
+ if (this.renderToScreen) {
237
+ renderer.render(this.scene, this.camera, undefined, false)
238
+ }
239
+ else {
240
+ renderer.render(this.scene, this.camera, readBuffer, false)
241
+ }
242
+
243
+ // Restore renderer settings
244
+
245
+ renderer.setClearColor(this.oldClearColor, this.oldClearAlpha)
246
+ renderer.autoClear = oldAutoClear
247
+ }
248
+
249
+ getSeperableBlurMaterial (kernelRadius) {
250
+ return new ShaderMaterial(
251
+ {
252
+ defines: {
253
+ KERNEL_RADIUS: kernelRadius,
254
+ SIGMA: kernelRadius
255
+ },
256
+
257
+ uniforms: {
258
+ colorTexture: { value: null },
259
+ texSize: { value: new Vector2(0.5, 0.5) },
260
+ direction: { value: new Vector2(0.5, 0.5) }
261
+ },
262
+
263
+ vertexShader: simpleVertexCode,
264
+ fragmentShader: seperableBlurCode
265
+ }
266
+ )
267
+ }
268
+
269
+ getCompositeMaterial (nMips) {
270
+ return new ShaderMaterial(
271
+ {
272
+ defines: {
273
+ NUM_MIPS: nMips
274
+ },
275
+
276
+ uniforms: {
277
+ blurTexture1: { value: null },
278
+ blurTexture2: { value: null },
279
+ blurTexture3: { value: null },
280
+ blurTexture4: { value: null },
281
+ blurTexture5: { value: null },
282
+ dirtTexture: { value: null },
283
+ bloomStrength: { value: 1.0 },
284
+ bloomFactors: { value: null },
285
+ bloomTintColors: { value: null },
286
+ bloomRadius: { value: 0.0 }
287
+ },
288
+
289
+ vertexShader: simpleVertexCode,
290
+ fragmentShader: bloomCompositeCode
291
+ }
292
+ )
293
+ }
294
+
295
+ }
296
+
297
+ UnrealBloomPass.BlurDirectionX = new Vector2(1, 0)
298
+ UnrealBloomPass.BlurDirectionY = new Vector2(0, 1)
src/shaders/bloomComposite.glsl ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ varying vec2 vUv;
2
+ uniform sampler2D blurTexture1;
3
+ uniform sampler2D blurTexture2;
4
+ uniform sampler2D blurTexture3;
5
+ uniform sampler2D blurTexture4;
6
+ uniform sampler2D blurTexture5;
7
+ uniform sampler2D dirtTexture;
8
+ uniform float bloomStrength;
9
+ uniform float bloomRadius;
10
+ uniform float bloomFactors[NUM_MIPS];
11
+ uniform vec3 bloomTintColors[NUM_MIPS];
12
+
13
+ float lerpBloomFactor(const in float factor) {
14
+ float mirrorFactor = 1.2 - factor;
15
+ return mix(factor, mirrorFactor, bloomRadius);
16
+ }
17
+
18
+ void main() {
19
+ gl_FragColor = bloomStrength * ( lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) +
20
+ lerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) +
21
+ lerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) +
22
+ lerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) +
23
+ lerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv) );
24
+ }
src/shaders/copy.glsl ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ uniform float opacity;
2
+ uniform sampler2D tDiffuse;
3
+
4
+ varying vec2 vUv;
5
+
6
+ void main() {
7
+ vec4 texel = texture2D( tDiffuse, vUv );
8
+ gl_FragColor = opacity * texel;
9
+ }
src/shaders/luminosityHighPass.glsl ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ uniform sampler2D tDiffuse;
2
+ uniform vec3 defaultColor;
3
+ uniform float defaultOpacity;
4
+ uniform float luminosityThreshold;
5
+ uniform float smoothWidth;
6
+
7
+ varying vec2 vUv;
8
+
9
+ void main() {
10
+ vec4 texel = texture2D( tDiffuse, vUv );
11
+ vec3 luma = vec3( 0.299, 0.587, 0.114 );
12
+
13
+ float v = dot( texel.xyz, luma );
14
+
15
+ vec4 outputColor = vec4( defaultColor.rgb, defaultOpacity );
16
+
17
+ float alpha = smoothstep( luminosityThreshold, luminosityThreshold + smoothWidth, v );
18
+
19
+ gl_FragColor = mix( outputColor, texel, alpha );
20
+ }
src/shaders/seperableBlur.glsl ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include <common>
2
+
3
+ varying vec2 vUv;
4
+ uniform sampler2D colorTexture;
5
+ uniform vec2 texSize;
6
+ uniform vec2 direction;
7
+
8
+ float gaussianPdf(in float x, in float sigma) {
9
+ return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma;
10
+ }
11
+ void main() {
12
+ vec2 invSize = 1.0 / texSize;
13
+ float fSigma = float(SIGMA);
14
+ float weightSum = gaussianPdf(0.0, fSigma);
15
+ vec3 diffuseSum = texture2D( colorTexture, vUv).rgb * weightSum;
16
+ for( int i = 1; i < KERNEL_RADIUS; i ++ ) {
17
+ float x = float(i);
18
+ float w = gaussianPdf(x, fSigma);
19
+ vec2 uvOffset = direction * invSize * x;
20
+ vec3 sample1 = texture2D( colorTexture, vUv + uvOffset).rgb;
21
+ vec3 sample2 = texture2D( colorTexture, vUv - uvOffset).rgb;
22
+ diffuseSum += (sample1 + sample2) * w;
23
+ weightSum += 2.0 * w;
24
+ }
25
+ gl_FragColor = vec4(diffuseSum/weightSum, 1.0);
26
+ }