Przeglądaj źródła

Initial commit.

Simon 3 lat temu
rodzic
commit
80fc5ad21f
47 zmienionych plików z 1458 dodań i 0 usunięć
  1. 9 0
      base.css
  2. 12 0
      index.html
  3. 855 0
      main.js
  4. 42 0
      math.js
  5. 45 0
      noise.js
  6. BIN
      resources/background-grey-dots.png
  7. BIN
      resources/crosshair.png
  8. BIN
      resources/fire.png
  9. BIN
      resources/freepbr/broken_down_concrete2_Height.png
  10. BIN
      resources/freepbr/broken_down_concrete2_albedo.png
  11. BIN
      resources/freepbr/broken_down_concrete2_ao.png
  12. BIN
      resources/freepbr/broken_down_concrete2_metallic.png
  13. BIN
      resources/freepbr/broken_down_concrete2_normal.png
  14. BIN
      resources/freepbr/broken_down_concrete2_roughness.png
  15. BIN
      resources/freepbr/concrete3-albedo.png
  16. BIN
      resources/freepbr/concrete3-metallic.png
  17. BIN
      resources/freepbr/concrete3-normal.png
  18. BIN
      resources/freepbr/concrete3-roughness.png
  19. BIN
      resources/freepbr/flaking-plaster_albedo.png
  20. BIN
      resources/freepbr/flaking-plaster_ao.png
  21. BIN
      resources/freepbr/flaking-plaster_metallic.png
  22. BIN
      resources/freepbr/flaking-plaster_normal-ogl.png
  23. BIN
      resources/freepbr/flaking-plaster_roughness.png
  24. 2 0
      resources/freepbr/readme.txt
  25. BIN
      resources/freepbr/rustediron2_albedo.png
  26. BIN
      resources/freepbr/rustediron2_metallic.png
  27. BIN
      resources/freepbr/rustediron2_normal.png
  28. BIN
      resources/freepbr/rustediron2_roughness.png
  29. BIN
      resources/freepbr/worn_metal4_Height.png
  30. BIN
      resources/freepbr/worn_metal4_albedo.png
  31. BIN
      resources/freepbr/worn_metal4_ao.png
  32. BIN
      resources/freepbr/worn_metal4_metallic.png
  33. BIN
      resources/freepbr/worn_metal4_normal.png
  34. BIN
      resources/freepbr/worn_metal4_preview.jpg
  35. BIN
      resources/freepbr/worn_metal4_roughness.png
  36. BIN
      resources/music/AcousticRock.mp3
  37. BIN
      resources/music/Ectoplasm.mp3
  38. 1 0
      resources/music/readme.txt
  39. BIN
      resources/skybox/negx.jpg
  40. BIN
      resources/skybox/negy.jpg
  41. BIN
      resources/skybox/negz.jpg
  42. BIN
      resources/skybox/posx.jpg
  43. BIN
      resources/skybox/posy.jpg
  44. BIN
      resources/skybox/posz.jpg
  45. 13 0
      resources/skybox/readme.txt
  46. BIN
      resources/speaker.png
  47. 479 0
      simplex-noise.js

+ 9 - 0
base.css

@@ -0,0 +1,9 @@
+body {
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  background: #000000;
+  margin: 0;
+  padding: 0;
+  overscroll-behavior: none;
+}

+ 12 - 0
index.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Three.JS Tutorial: Audio Visualizer</title>
+  <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+  <link rel="stylesheet" type="text/css" href="base.css">
+</head>
+<body>
+  <script src="./main.js" type="module">
+  </script>
+</body>
+</html>

+ 855 - 0
main.js

@@ -0,0 +1,855 @@
+import * as THREE from 'https://cdn.skypack.dev/[email protected]';
+
+import {EffectComposer} from 'https://cdn.skypack.dev/[email protected]/examples/jsm/postprocessing/EffectComposer.js';
+import {ShaderPass} from 'https://cdn.skypack.dev/[email protected]/examples//jsm/postprocessing/ShaderPass.js';
+import {GammaCorrectionShader} from 'https://cdn.skypack.dev/[email protected]/examples/jsm/shaders/GammaCorrectionShader.js';
+import {RenderPass} from 'https://cdn.skypack.dev/[email protected]/examples/jsm/postprocessing/RenderPass.js';
+import {FXAAShader} from 'https://cdn.skypack.dev/[email protected]/examples/jsm/shaders/FXAAShader.js';
+
+import {math} from './math.js';
+import {noise} from './noise.js';
+
+
+
+const FS_DECLARATIONS = `
+
+uniform sampler2D audioDataTexture;
+uniform vec2 iResolution;
+uniform float iTime;
+
+#define M_PI 3.14159
+#define NUM_BARS 64.0
+#define CIRCLE_RADIUS 0.15
+#define BAR_HEIGHT 0.125
+
+
+// All code snippets taken from Inigo Quilez's site
+// Make sure to check out his site!
+// https://iquilezles.org/
+//
+vec3 pal( in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d) {
+    return a + b*cos( 6.28318*(c*t+d) );
+}
+
+float dot2(in vec2 v ) { return dot(v,v); }
+
+float sdfTrapezoid(in vec2 p, in float r1, float r2, float he) {
+  vec2 k1 = vec2(r2,he);
+  vec2 k2 = vec2(r2-r1,2.0*he);
+  p.x = abs(p.x);
+  vec2 ca = vec2(p.x-min(p.x,(p.y<0.0)?r1:r2), abs(p.y)-he);
+  vec2 cb = p - k1 + k2*clamp( dot(k1-p,k2)/dot2(k2), 0.0, 1.0 );
+  float s = (cb.x<0.0 && ca.y<0.0) ? -1.0 : 1.0;
+  return s*sqrt( min(dot2(ca),dot2(cb)) );
+}
+
+float sdUnevenCapsule( vec2 p, float r1, float r2, float h ) {
+    p.x = abs(p.x);
+    float b = (r1-r2)/h;
+    float a = sqrt(1.0-b*b);
+    float k = dot(p,vec2(-b,a));
+    if( k < 0.0 ) return length(p) - r1;
+    if( k > a*h ) return length(p-vec2(0.0,h)) - r2;
+    return dot(p, vec2(a,b) ) - r1;
+}
+
+float sdTriangleIsosceles( in vec2 p, in vec2 q ) {
+    p.x = abs(p.x);
+    vec2 a = p - q*clamp( dot(p,q)/dot(q,q), 0.0, 1.0 );
+    vec2 b = p - q*vec2( clamp( p.x/q.x, 0.0, 1.0 ), 1.0 );
+    float s = -sign( q.y );
+    vec2 d = min( vec2( dot(a,a), s*(p.x*q.y-p.y*q.x) ),
+                  vec2( dot(b,b), s*(p.y-q.y)  ));
+    return -sqrt(d.x)*sign(d.y);
+}
+
+float opSmoothUnion( float d1, float d2, float k ) {
+  float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 );
+  return mix( d2, d1, h ) - k*h*(1.0-h);
+}
+
+float opUnion( float d1, float d2 ) { return min(d1,d2); }
+float opIntersection( float d1, float d2 ) { return max(d1,d2); }
+float opSubtraction( float d1, float d2 ) { return max(-d1,d2); }
+
+float sdfBar(vec2 position, vec2 dimensions, vec2 uv, float frequencySample) {
+  float w = mix(dimensions.x * 0.5, dimensions.x, smoothstep(0.0, 1.0, frequencySample));
+  vec2 basePosition = uv - position + vec2(0.0, -dimensions.y * 0.5 - frequencySample * 0.05);
+
+  float d = sdfTrapezoid(
+      basePosition,
+      dimensions.x * 0.5,
+      w, dimensions.y * 0.5);
+
+  return (d > 0.0 ? 0.0 : 1.0);
+}
+
+vec2 rotate2D(vec2 pt, float a) {
+	float c = cos(a);
+  float s = sin(a);
+
+  mat2 r = mat2(c, s, -s, c);
+
+  return r * pt;
+}
+
+vec4 DrawBars(vec2 center, vec2 uv) {
+  float barWidth = 2.0 * M_PI * CIRCLE_RADIUS / (NUM_BARS * 1.25);
+
+  vec4 resultColour = vec4(1.0, 1.0, 1.0, 0.0);
+  vec2 position = vec2(center.x, center.y + CIRCLE_RADIUS);
+
+  for(int i = 0; i < int(NUM_BARS); i++) {
+    float frequencyUV = 0.0;
+    
+    if (float(i) >= NUM_BARS * 0.5) {
+      frequencyUV = 1.0 - ((float(i) - (NUM_BARS * 0.5)) / (NUM_BARS * 0.5));
+    } else {
+      frequencyUV = float(i) / (NUM_BARS * 0.5);
+    }
+
+    float frequencyData = texture(audioDataTexture, vec2(frequencyUV, 0.0)).x;
+
+    float barFinalHeight = BAR_HEIGHT * (0.1 + 0.9 * frequencyData);
+    vec2 barDimensions = vec2(barWidth, barFinalHeight);
+    vec2 barUvs = rotate2D(uv - center, (2.0 * M_PI * float(i)) / NUM_BARS) + center;
+
+    resultColour.w += sdfBar(position, barDimensions, barUvs, frequencyData);
+  }
+
+  float d = saturate(1.1 * ((distance(uv, center) - CIRCLE_RADIUS) / BAR_HEIGHT));
+  d = smoothstep(0.0, 1.0, d);
+  d = 0.45 + 0.55 * d;
+  resultColour.xyz *= pal(d, vec3(0.5,0.5,0.5),vec3(0.5,0.5,0.5),vec3(1.0,1.0,1.0),vec3(0.0,0.20,0.30) );
+  resultColour.xyz *= resultColour.w;
+
+  return saturate(resultColour);
+}
+
+
+vec4 AudioVisualizer() {
+  float aspect = iResolution.x / iResolution.y;
+  vec2 uv = vUv * vec2(aspect, 1.0);
+
+  vec2 circleCenter = vec2(aspect * 0.5, 0.5);
+
+  return DrawBars(circleCenter, uv);
+}
+`;
+
+
+function clamp(x, a, b) {
+  return Math.min(Math.max(x, a), b);
+}
+
+const KEYS = {
+  'a': 65,
+  's': 83,
+  'w': 87,
+  'd': 68,
+};
+
+class InputController {
+  constructor(target) {
+    this.target_ = target || document;
+    this.initialize_();    
+  }
+
+  initialize_() {
+    this.current_ = {
+      leftButton: false,
+      rightButton: false,
+      mouseXDelta: 0,
+      mouseYDelta: 0,
+      mouseX: 0,
+      mouseY: 0,
+    };
+    this.previous_ = null;
+    this.keys_ = {};
+    this.previousKeys_ = {};
+    this.target_.addEventListener('mousedown', (e) => this.onMouseDown_(e), false);
+    this.target_.addEventListener('mousemove', (e) => this.onMouseMove_(e), false);
+    this.target_.addEventListener('mouseup', (e) => this.onMouseUp_(e), false);
+    this.target_.addEventListener('keydown', (e) => this.onKeyDown_(e), false);
+    this.target_.addEventListener('keyup', (e) => this.onKeyUp_(e), false);
+  }
+
+  onMouseMove_(e) {
+    this.current_.mouseX = e.pageX - window.innerWidth / 2;
+    this.current_.mouseY = e.pageY - window.innerHeight / 2;
+
+    if (this.previous_ === null) {
+      this.previous_ = {...this.current_};
+    }
+
+    this.current_.mouseXDelta = this.current_.mouseX - this.previous_.mouseX;
+    this.current_.mouseYDelta = this.current_.mouseY - this.previous_.mouseY;
+  }
+
+  onMouseDown_(e) {
+    this.onMouseMove_(e);
+
+    switch (e.button) {
+      case 0: {
+        this.current_.leftButton = true;
+        break;
+      }
+      case 2: {
+        this.current_.rightButton = true;
+        break;
+      }
+    }
+  }
+
+  onMouseUp_(e) {
+    this.onMouseMove_(e);
+
+    switch (e.button) {
+      case 0: {
+        this.current_.leftButton = false;
+        break;
+      }
+      case 2: {
+        this.current_.rightButton = false;
+        break;
+      }
+    }
+  }
+
+  onKeyDown_(e) {
+    this.keys_[e.keyCode] = true;
+  }
+
+  onKeyUp_(e) {
+    this.keys_[e.keyCode] = false;
+  }
+
+  key(keyCode) {
+    return !!this.keys_[keyCode];
+  }
+
+  isReady() {
+    return this.previous_ !== null;
+  }
+
+  update(_) {
+    if (this.previous_ !== null) {
+      this.current_.mouseXDelta = this.current_.mouseX - this.previous_.mouseX;
+      this.current_.mouseYDelta = this.current_.mouseY - this.previous_.mouseY;
+
+      this.previous_ = {...this.current_};
+    }
+  }
+};
+
+
+
+class FirstPersonCamera {
+  constructor(camera, objects) {
+    this.camera_ = camera;
+    this.input_ = new InputController();
+    this.phi_ = 0;
+    this.phiSpeed_ = 8;
+    this.theta_ = 0;
+    this.thetaSpeed_ = 5;
+    this.movementSpeed_ = 10;
+    this.rotation_ = new THREE.Quaternion();
+    this.translation_ = new THREE.Vector3(30, 2, 0);
+    this.bobTimer_ = 0;
+    this.bobMagnitude_ = 0.175;
+    this.bobFrequency_ = 10;
+    this.objects_ = objects;
+  }
+
+  update(timeElapsedS) {
+    if (this.input_.isReady()) {
+      this.updateRotation_(timeElapsedS);
+      this.updateTranslation_(timeElapsedS);
+      this.updateBob_(timeElapsedS);
+      this.updateCamera_(timeElapsedS);
+    }
+
+    this.input_.update(timeElapsedS);
+  }
+
+  updateBob_(timeElapsedS) {
+    if (this.bobActive_) {
+      const waveLength = Math.PI;
+      const nextStep = 1 + Math.floor(((this.bobTimer_ + 0.000001) * this.bobFrequency_) / waveLength);
+      const nextStepTime = nextStep * waveLength / this.bobFrequency_;
+      this.bobTimer_ = Math.min(this.bobTimer_ + timeElapsedS, nextStepTime);
+
+      if (this.bobTimer_ == nextStepTime) {
+        this.bobActive_ = false;
+        this.bobTimer_ = 0;
+      }
+    }
+  }
+
+  updateCamera_(timeElapsedS) {
+    this.camera_.quaternion.copy(this.rotation_);
+    this.camera_.position.copy(this.translation_);
+    this.camera_.position.y += Math.sin(this.bobTimer_ * this.bobFrequency_) * this.bobMagnitude_;
+
+    const forward = new THREE.Vector3(0, 0, -1);
+    forward.applyQuaternion(this.rotation_);
+
+    const dir = forward.clone();
+
+    forward.multiplyScalar(100);
+    forward.add(this.translation_);
+
+    let closest = forward;
+    const result = new THREE.Vector3();
+    const ray = new THREE.Ray(this.translation_, dir);
+    for (let i = 0; i < this.objects_.length; ++i) {
+      if (ray.intersectBox(this.objects_[i], result)) {
+        if (result.distanceTo(ray.origin) < closest.distanceTo(ray.origin)) {
+          closest = result.clone();
+        }
+      }
+    }
+
+    this.camera_.lookAt(closest);
+  }
+
+  updateTranslation_(timeElapsedS) {
+    const forwardVelocity = ((this.input_.key(KEYS.w) ? 1 : 0) + (this.input_.key(KEYS.s) ? -1 : 0));
+    const strafeVelocity = ((this.input_.key(KEYS.a) ? 1 : 0) + (this.input_.key(KEYS.d) ? -1 : 0));
+
+    const qx = new THREE.Quaternion();
+    qx.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.phi_);
+    
+    const forward = new THREE.Vector3(0, 0, -1);
+    forward.applyQuaternion(qx);
+    forward.multiplyScalar(forwardVelocity * this.movementSpeed_ * timeElapsedS);
+
+    const left = new THREE.Vector3(-1, 0, 0);
+    left.applyQuaternion(qx);
+    left.multiplyScalar(strafeVelocity * this.movementSpeed_ * timeElapsedS);
+
+    this.translation_.add(forward);
+    this.translation_.add(left);
+
+    if(forwardVelocity != 0 || strafeVelocity != 0) {
+      this.bobActive_ = true;
+    }
+  }
+
+  updateRotation_(timeElapsedS) {
+    const xh = this.input_.current_.mouseXDelta / window.innerWidth;
+    const yh = this.input_.current_.mouseYDelta / window.innerHeight;
+
+    this.phi_ += -xh * this.phiSpeed_;
+    this.theta_ = clamp(this.theta_ + -yh * this.thetaSpeed_, -Math.PI / 3, Math.PI / 3);
+
+    // console.log(this.input_.current_.mouseYDelta);
+
+    const qx = new THREE.Quaternion();
+    qx.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.phi_);
+    const qz = new THREE.Quaternion();
+    qz.setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.theta_);
+
+    const q = new THREE.Quaternion();
+    q.multiply(qx);
+    q.multiply(qz);
+
+    const t = 1.0 - Math.pow(0.001, 5 * timeElapsedS);
+    this.rotation_.slerp(q, t);
+  }
+};
+
+
+
+class LinearSpline {
+  constructor(lerp) {
+    this.points_ = [];
+    this._lerp = lerp;
+  }
+
+  AddPoint(t, d) {
+    this.points_.push([t, d]);
+  }
+
+  Get(t) {
+    let p1 = 0;
+
+    for (let i = 0; i < this.points_.length; i++) {
+      if (this.points_[i][0] >= t) {
+        break;
+      }
+      p1 = i;
+    }
+
+    const p2 = Math.min(this.points_.length - 1, p1 + 1);
+
+    if (p1 == p2) {
+      return this.points_[p1][1];
+    }
+
+    return this._lerp(
+        (t - this.points_[p1][0]) / (
+            this.points_[p2][0] - this.points_[p1][0]),
+        this.points_[p1][1], this.points_[p2][1]);
+  }
+}
+
+
+class FirstPersonCameraDemo {
+  constructor() {
+    this.initialize_();
+  }
+
+  initialize_() {
+    this.initializeRenderer_();
+    this.initializeScene_();
+    this.initializePostFX_();
+    this.initializeAudio_();
+
+    this.previousRAF_ = null;
+    this.raf_();
+    this.onWindowResize_();
+  }
+
+  initializeAudio_() {
+    this.listener_ = new THREE.AudioListener();
+    this.camera_.add(this.listener_);
+
+    const sound1 = new THREE.PositionalAudio(this.listener_);
+    const sound2 = new THREE.PositionalAudio(this.listener_);
+
+    this.speakerMesh1_.add(sound1);
+    this.speakerMesh2_.add(sound2);
+
+    const loader = new THREE.AudioLoader();
+    loader.load('resources/music/Ectoplasm.mp3', (buffer) => {
+      setTimeout(() => {
+        sound1.setBuffer(buffer);
+        sound1.setLoop(true);
+        sound1.setVolume(1.0);
+        sound1.setRefDistance(1);
+        sound1.play();
+        this.analyzer1_ = new THREE.AudioAnalyser(sound1, 32);
+        this.analyzer1Data_ = [];
+      }, 5000);
+    });
+
+    loader.load('resources/music/AcousticRock.mp3', (buffer) => {
+      setTimeout(() => {
+        sound2.setBuffer(buffer);
+        sound2.setLoop(true);
+        sound2.setVolume(1.0);
+        sound2.setRefDistance(1);
+        sound2.play();
+        this.analyzer2_ = new THREE.AudioAnalyser(sound2, 128);
+        this.analyzer2Texture_ = new THREE.DataTexture(
+            this.analyzer2_.data, 64, 1, THREE.RedFormat);
+        this.analyzer2Texture_.magFilter = THREE.LinearFilter;
+      }, 5000);
+    });
+
+    this.indexTimer_ = 0;
+    this.noise1_ = new noise.Noise({
+      octaves: 3,
+      persistence: 0.5,
+      lacunarity: 1.6,
+      exponentiation: 1.0,
+      height: 1.0,
+      scale: 0.1,
+      seed: 1
+    });
+  }
+
+  initializeScene_() {
+    const distance = 50.0;
+    const angle = Math.PI / 4.0;
+    const penumbra = 0.5;
+    const decay = 1.0;
+
+    let light = null;
+    
+    light = new THREE.SpotLight(
+      0xFFFFFF, 100.0, distance, angle, penumbra, decay);
+    light.castShadow = true;
+    light.shadow.bias = -0.00001;
+    light.shadow.mapSize.width = 4096;
+    light.shadow.mapSize.height = 4096;
+    light.shadow.camera.near = 1;
+    light.shadow.camera.far = 100;
+    light.position.set(-35, 25, 0);
+    light.target.position.set(-40, 4, 0);
+    this.scene_.add(light);
+    this.scene_.add(light.target);
+
+    light = new THREE.SpotLight(
+        0xFFFFFF, 100.0, distance, angle, penumbra, decay);
+    light.castShadow = true;
+    light.shadow.bias = -0.00001;
+    light.shadow.mapSize.width = 4096;
+    light.shadow.mapSize.height = 4096;
+    light.shadow.camera.near = 1;
+    light.shadow.camera.far = 100;
+    light.position.set(35, 25, 0);
+    light.target.position.set(40, 4, 0);
+    this.scene_.add(light);
+    this.scene_.add(light.target);
+
+    const upColour = 0xFFFF80;
+    const downColour = 0x808080;
+    light = new THREE.HemisphereLight(upColour, downColour, 0.5);
+    light.color.setHSL( 0.6, 1, 0.6 );
+    light.groundColor.setHSL( 0.095, 1, 0.75 );
+    light.position.set(0, 4, 0);
+    this.scene_.add(light);
+
+    const loader = new THREE.CubeTextureLoader();
+    const texture = loader.load([
+        './resources/skybox/posx.jpg',
+        './resources/skybox/negx.jpg',
+        './resources/skybox/posy.jpg',
+        './resources/skybox/negy.jpg',
+        './resources/skybox/posz.jpg',
+        './resources/skybox/negz.jpg',
+    ]);
+
+    texture.encoding = THREE.sRGBEncoding;
+    this.scene_.background = texture;
+
+    const mapLoader = new THREE.TextureLoader();
+    const maxAnisotropy = this.threejs_.capabilities.getMaxAnisotropy();
+
+    const plane = new THREE.Mesh(
+        new THREE.PlaneGeometry(100, 100, 10, 10),
+        this.loadMaterial_('rustediron2_', 4));
+    plane.castShadow = false;
+    plane.receiveShadow = true;
+    plane.rotation.x = -Math.PI / 2;
+    this.scene_.add(plane);
+
+    const concreteMaterial = this.loadMaterial_('concrete3-', 4);
+
+    const wall1 = new THREE.Mesh(
+      new THREE.BoxGeometry(100, 100, 4),
+      concreteMaterial);
+    wall1.position.set(0, -40, -50);
+    wall1.castShadow = true;
+    wall1.receiveShadow = true;
+    this.scene_.add(wall1);
+
+    const wall2 = new THREE.Mesh(
+      new THREE.BoxGeometry(100, 100, 4),
+      concreteMaterial);
+    wall2.position.set(0, -40, 50);
+    wall2.castShadow = true;
+    wall2.receiveShadow = true;
+    this.scene_.add(wall2);
+
+    const wall3 = new THREE.Mesh(
+      new THREE.BoxGeometry(4, 100, 100),
+      concreteMaterial);
+    wall3.position.set(50, -40, 0);
+    wall3.castShadow = true;
+    wall3.receiveShadow = true;
+    this.scene_.add(wall3);
+
+    const wall4 = new THREE.Mesh(
+      new THREE.BoxGeometry(4, 100, 100),
+      concreteMaterial);
+    wall4.position.set(-50, -40, 0);
+    wall4.castShadow = true;
+    wall4.receiveShadow = true;
+    this.scene_.add(wall4);
+
+    const speaker1Material = this.loadMaterial_('worn_metal4_', 1);
+    const speaker1 = new THREE.Mesh(
+      new THREE.BoxGeometry(1, 8, 4),
+      speaker1Material);
+    speaker1.position.set(-40, 4, 0);
+    speaker1.castShadow = true;
+    speaker1.receiveShadow = true;
+    this.scene_.add(speaker1);
+
+    const speaker1Geo = new THREE.BoxGeometry(0.25, 0.25, 0.25);
+    const speaker1BoxMaterial = this.loadMaterial_('broken_down_concrete2_', 1);
+    this.speakerMeshes1_ = [];
+    const speaker1Group = new THREE.Group();
+    speaker1Group.position.x = 0.5 + 0.125;
+
+    for (let x = -5; x <= 5; ++x) {
+      const row = [];
+      for (let y = 0; y < 16; ++y) {
+        const speaker1_1 = new THREE.Mesh(
+          speaker1Geo,
+          speaker1BoxMaterial.clone());
+        speaker1_1.position.set(0, y*0.35 - 3, x * 0.35);
+        speaker1_1.castShadow = true;
+        speaker1_1.receiveShadow = true;
+        speaker1Group.add(speaker1_1);
+        row.push(speaker1_1);
+      }
+      this.speakerMeshes1_.push(row);
+    }
+    speaker1.add(speaker1Group);
+
+    this.speakerMesh1_ = speaker1;
+
+    const speaker2 = new THREE.Mesh(
+      new THREE.BoxGeometry(1, 8, 4),
+      new THREE.MeshStandardMaterial({color: 0x404040, roughness: 0.1, metalness: 0 }));
+      speaker2.position.set(40, 4, 0);
+    speaker2.castShadow = true;
+    speaker2.receiveShadow = true;
+    this.scene_.add(speaker2);
+
+    this.speakerMesh2_ = speaker2;
+
+    const diffuseMap = mapLoader.load('resources/background-grey-dots.png');
+    diffuseMap.anisotropy = maxAnisotropy;
+  
+    const visualizerMaterial = new THREE.MeshStandardMaterial({
+      map: diffuseMap,
+      normalMap: mapLoader.load('resources/freepbr/flaking-plaster_normal-ogl.png'),
+      roughnessMap: mapLoader.load('resources/freepbr/flaking-plaster_roughness.png'),
+      metalnessMap: mapLoader.load('resources/freepbr/flaking-plaster_metallic.png'),
+    });
+
+    visualizerMaterial.onBeforeCompile = (shader) => {
+      shader.uniforms.iTime = { value: 0.0 };
+      shader.uniforms.iResolution = {value: new THREE.Vector2(128, 256)};
+      shader.uniforms.audioDataTexture = {value: null};
+
+      shader.fragmentShader = shader.fragmentShader.replace('void main()', FS_DECLARATIONS + 'void main()');
+      shader.fragmentShader = shader.fragmentShader.replace('totalEmissiveRadiance = emissive;', `
+      
+      totalEmissiveRadiance = emissive + AudioVisualizer().xyz;
+
+      `);
+      visualizerMaterial.userData.shader = shader;
+    };
+
+    visualizerMaterial.customProgramCacheKey = () => {
+      return 'visualizerMaterial';
+    };
+
+    this.speaker2Material_ = visualizerMaterial;
+
+    const speaker2Screen = new THREE.Mesh(
+      new THREE.PlaneGeometry(4, 8),
+      this.speaker2Material_);
+    speaker2Screen.castShadow = false;
+    speaker2Screen.receiveShadow = true;
+    speaker2Screen.rotation.y = -Math.PI / 2;
+    speaker2Screen.position.x -= 0.51;
+    this.speakerMesh2_.add(speaker2Screen);
+
+    // Create Box3 for each mesh in the scene so that we can
+    // do some easy intersection tests.
+    const meshes = [
+      plane, wall1, wall2, wall3, wall4];
+
+    this.objects_ = [];
+
+    for (let i = 0; i < meshes.length; ++i) {
+      const b = new THREE.Box3();
+      b.setFromObject(meshes[i]);
+      this.objects_.push(b);
+    }
+
+    this.fpsCamera_ = new FirstPersonCamera(this.camera_, this.objects_);
+
+    // Crosshair
+    const crosshair = mapLoader.load('resources/crosshair.png');
+    crosshair.anisotropy = maxAnisotropy;
+
+    this.sprite_ = new THREE.Sprite(
+      new THREE.SpriteMaterial({map: crosshair, color: 0xffffff, fog: false, depthTest: false, depthWrite: false}));
+    this.sprite_.scale.set(0.15, 0.15 * this.camera_.aspect, 1)
+    this.sprite_.position.set(0, 0, -10);
+
+    // this.uiScene_.add(this.sprite_);
+  }
+
+  loadMaterial_(name, tiling) {
+    const mapLoader = new THREE.TextureLoader();
+    const maxAnisotropy = this.threejs_.capabilities.getMaxAnisotropy();
+
+    const metalMap = mapLoader.load('resources/freepbr/' + name + 'metallic.png');
+    metalMap.anisotropy = maxAnisotropy;
+    metalMap.wrapS = THREE.RepeatWrapping;
+    metalMap.wrapT = THREE.RepeatWrapping;
+    metalMap.repeat.set(tiling, tiling);
+
+    const albedo = mapLoader.load('resources/freepbr/' + name + 'albedo.png');
+    albedo.anisotropy = maxAnisotropy;
+    albedo.wrapS = THREE.RepeatWrapping;
+    albedo.wrapT = THREE.RepeatWrapping;
+    albedo.repeat.set(tiling, tiling);
+
+    const normalMap = mapLoader.load('resources/freepbr/' + name + 'normal.png');
+    normalMap.anisotropy = maxAnisotropy;
+    normalMap.wrapS = THREE.RepeatWrapping;
+    normalMap.wrapT = THREE.RepeatWrapping;
+    normalMap.repeat.set(tiling, tiling);
+
+    const roughnessMap = mapLoader.load('resources/freepbr/' + name + 'roughness.png');
+    roughnessMap.anisotropy = maxAnisotropy;
+    roughnessMap.wrapS = THREE.RepeatWrapping;
+    roughnessMap.wrapT = THREE.RepeatWrapping;
+    roughnessMap.repeat.set(tiling, tiling);
+
+    const material = new THREE.MeshStandardMaterial({
+      metalnessMap: metalMap,
+      map: albedo,
+      normalMap: normalMap,
+      roughnessMap: roughnessMap,
+    });
+
+    return material;
+  }
+
+  initializeRenderer_() {
+    this.threejs_ = new THREE.WebGLRenderer({
+      antialias: false,
+    });
+    this.threejs_.shadowMap.enabled = true;
+    this.threejs_.shadowMap.type = THREE.PCFSoftShadowMap;
+    this.threejs_.setPixelRatio(window.devicePixelRatio);
+    this.threejs_.setSize(window.innerWidth, window.innerHeight);
+    this.threejs_.physicallyCorrectLights = true;
+    this.threejs_.autoClear = false;
+
+    document.body.appendChild(this.threejs_.domElement);
+
+    window.addEventListener('resize', () => {
+      this.onWindowResize_();
+    }, false);
+
+    const fov = 60;
+    const aspect = 1920 / 1080;
+    const near = 1.0;
+    const far = 1000.0;
+    this.camera_ = new THREE.PerspectiveCamera(fov, aspect, near, far);
+    this.camera_.position.set(-30, 2, 0);
+
+    this.scene_ = new THREE.Scene();
+
+    this.uiCamera_ = new THREE.OrthographicCamera(
+        -1, 1, 1 * aspect, -1 * aspect, 1, 1000);
+    this.uiScene_ = new THREE.Scene();
+  }
+
+  initializePostFX_() {
+    const parameters = {
+      minFilter: THREE.LinearFilter,
+      magFilter: THREE.LinearFilter,
+      format: THREE.RGBAFormat,
+      type: THREE.FloatType,
+      stencilBuffer: true,
+    };
+    
+    const renderTarget = new THREE.WebGLRenderTarget(
+        window.innerWidth, window.innerHeight, parameters);
+
+    this.composer_ = new EffectComposer(this.threejs_, renderTarget);
+    this.composer_.setPixelRatio(window.devicePixelRatio);
+    this.composer_.setSize(window.innerWidth, window.innerHeight);
+
+    this.fxaaPass_ = new ShaderPass(FXAAShader);
+
+    const uiPass = new RenderPass(this.uiScene_, this.uiCamera_);
+    uiPass.clear = false;
+
+    this.composer_.addPass(new RenderPass(this.scene_, this.camera_));
+    this.composer_.addPass(uiPass);
+    this.composer_.addPass(new ShaderPass(GammaCorrectionShader));
+    this.composer_.addPass(this.fxaaPass_);
+  }
+
+  onWindowResize_() {
+    this.camera_.aspect = window.innerWidth / window.innerHeight;
+    this.camera_.updateProjectionMatrix();
+
+    this.uiCamera_.left = -this.camera_.aspect;
+    this.uiCamera_.right = this.camera_.aspect;
+    this.uiCamera_.updateProjectionMatrix();
+
+    this.threejs_.setSize(window.innerWidth, window.innerHeight);
+    this.composer_.setSize(window.innerWidth, window.innerHeight);
+
+    const pixelRatio = this.threejs_.getPixelRatio();
+    this.fxaaPass_.material.uniforms['resolution'].value.x = 1 / (
+        window.innerWidth * pixelRatio);
+    this.fxaaPass_.material.uniforms['resolution'].value.y = 1 / (
+        window.innerHeight * pixelRatio);
+  }
+
+  raf_() {
+    requestAnimationFrame((t) => {
+      if (this.previousRAF_ === null) {
+        this.previousRAF_ = t;
+      }
+
+      this.step_(t - this.previousRAF_);
+      this.composer_.render();
+
+      this.previousRAF_ = t;
+      this.raf_();
+    });
+  }
+
+  step_(timeElapsed) {
+    const timeElapsedS = timeElapsed * 0.001;
+
+    this.fpsCamera_.update(timeElapsedS);
+
+    if (this.analyzer1_) {
+      this.indexTimer_ += timeElapsedS * 0.1;
+
+      this.analyzer1Data_.push([...this.analyzer1_.getFrequencyData()]);
+      const rows = this.speakerMeshes1_.length;
+      if (this.analyzer1Data_.length > rows) {
+        this.analyzer1Data_.shift();
+      }
+
+      const colourSpline = new LinearSpline((t, a, b) => {
+        const c = a.clone();
+        return c.lerp(b, t);
+      });
+      colourSpline.AddPoint(0.0, new THREE.Color(0x4040FF));
+      colourSpline.AddPoint(0.25, new THREE.Color(0xFF4040));
+      colourSpline.AddPoint(1.0, new THREE.Color(0xFFFF80));
+
+      const remap = [15, 13, 11, 9, 7, 5, 3, 1, 0, 2, 4, 6, 8, 10, 12, 14];
+      for (let r = 0; r < this.analyzer1Data_.length; ++r) {
+        const data = this.analyzer1Data_[r];
+        const speakerRow = this.speakerMeshes1_[r];
+        for (let i = 0; i < data.length; ++i) {
+          const freqScale = math.smootherstep((data[remap[i]]/255) ** 0.5, 0, 1);
+          const sc = 1 + 6 * freqScale + this.noise1_.Get(this.indexTimer_, r * 0.42142, i * 0.3455);
+          speakerRow[i].scale.set(sc, 1, 1);
+          speakerRow[i].material.color.copy(colourSpline.Get(freqScale));
+          speakerRow[i].material.emissive.copy(colourSpline.Get(freqScale));
+          speakerRow[i].material.emissive.multiplyScalar(freqScale ** 2);
+        }  
+      }
+    }
+
+    if (this.analyzer2_ && this.speaker2Material_ && this.speaker2Material_.userData.shader) {
+      this.analyzer2_.getFrequencyData();
+      this.speaker2Material_.userData.shader.uniforms.audioDataTexture.value = this.analyzer2Texture_;
+      this.speaker2Material_.userData.shader.uniforms.iTime.value += timeElapsedS;
+      this.speaker2Material_.userData.shader.uniforms.audioDataTexture.value.needsUpdate = true;
+    }
+  }
+}
+
+
+let _APP = null;
+
+window.addEventListener('DOMContentLoaded', () => {
+  const _Setup = () => {
+    _APP = new FirstPersonCameraDemo();
+    document.body.removeEventListener('click', _Setup);
+  };
+  document.body.addEventListener('click', _Setup);
+});

+ 42 - 0
math.js

@@ -0,0 +1,42 @@
+export const math = (function() {
+  return {
+    rand_range: function(a, b) {
+      return Math.random() * (b - a) + a;
+    },
+
+    rand_normalish: function() {
+      const r = Math.random() + Math.random() + Math.random() + Math.random();
+      return (r / 4.0) * 2.0 - 1;
+    },
+
+    rand_int: function(a, b) {
+      return Math.round(Math.random() * (b - a) + a);
+    },
+
+    lerp: function(x, a, b) {
+      return x * (b - a) + a;
+    },
+
+    smoothstep: function(x, a, b) {
+      x = x * x * (3.0 - 2.0 * x);
+      return x * (b - a) + a;
+    },
+
+    smootherstep: function(x, a, b) {
+      x = x * x * x * (x * (x * 6 - 15) + 10);
+      return x * (b - a) + a;
+    },
+
+    clamp: function(x, a, b) {
+      return Math.min(Math.max(x, a), b);
+    },
+
+    sat: function(x) {
+      return Math.min(Math.max(x, 0.0), 1.0);
+    },
+
+    in_range: (x, a, b) => {
+      return x >= a && x <= b;
+    },
+  };
+})();

+ 45 - 0
noise.js

@@ -0,0 +1,45 @@
+import {simplex} from './simplex-noise.js';
+
+
+export const noise = (function() {
+
+  class _NoiseGenerator {
+    constructor(params) {
+      this._params = params;
+      this._Init();
+    }
+
+    _Init() {
+      this._noise = new simplex.SimplexNoise(this._params.seed);
+    }
+
+    Get(x, y, z) {
+      const G = 2.0 ** (-this._params.persistence);
+      const xs = x / this._params.scale;
+      const ys = y / this._params.scale;
+      const zs = z / this._params.scale;
+      const noiseFunc = this._noise;
+
+      let amplitude = 1.0;
+      let frequency = 1.0;
+      let normalization = 0;
+      let total = 0;
+      for (let o = 0; o < this._params.octaves; o++) {
+        const noiseValue = noiseFunc.noise3D(
+          xs * frequency, ys * frequency, zs * frequency) * 0.5 + 0.5;
+
+        total += noiseValue * amplitude;
+        normalization += amplitude;
+        amplitude *= G;
+        frequency *= this._params.lacunarity;
+      }
+      total /= normalization;
+      return Math.pow(
+          total, this._params.exponentiation) * this._params.height;
+    }
+  }
+
+  return {
+    Noise: _NoiseGenerator
+  }
+})();

BIN
resources/background-grey-dots.png


BIN
resources/crosshair.png


BIN
resources/fire.png


BIN
resources/freepbr/broken_down_concrete2_Height.png


BIN
resources/freepbr/broken_down_concrete2_albedo.png


BIN
resources/freepbr/broken_down_concrete2_ao.png


BIN
resources/freepbr/broken_down_concrete2_metallic.png


BIN
resources/freepbr/broken_down_concrete2_normal.png


BIN
resources/freepbr/broken_down_concrete2_roughness.png


BIN
resources/freepbr/concrete3-albedo.png


BIN
resources/freepbr/concrete3-metallic.png


BIN
resources/freepbr/concrete3-normal.png


BIN
resources/freepbr/concrete3-roughness.png


BIN
resources/freepbr/flaking-plaster_albedo.png


BIN
resources/freepbr/flaking-plaster_ao.png


BIN
resources/freepbr/flaking-plaster_metallic.png


BIN
resources/freepbr/flaking-plaster_normal-ogl.png


BIN
resources/freepbr/flaking-plaster_roughness.png


+ 2 - 0
resources/freepbr/readme.txt

@@ -0,0 +1,2 @@
+These textures were all taken from https://freepbr.com/
+

BIN
resources/freepbr/rustediron2_albedo.png


BIN
resources/freepbr/rustediron2_metallic.png


BIN
resources/freepbr/rustediron2_normal.png


BIN
resources/freepbr/rustediron2_roughness.png


BIN
resources/freepbr/worn_metal4_Height.png


BIN
resources/freepbr/worn_metal4_albedo.png


BIN
resources/freepbr/worn_metal4_ao.png


BIN
resources/freepbr/worn_metal4_metallic.png


BIN
resources/freepbr/worn_metal4_normal.png


BIN
resources/freepbr/worn_metal4_preview.jpg


BIN
resources/freepbr/worn_metal4_roughness.png


BIN
resources/music/AcousticRock.mp3


BIN
resources/music/Ectoplasm.mp3


+ 1 - 0
resources/music/readme.txt

@@ -0,0 +1 @@
+Music: https://audionautix.com/

BIN
resources/skybox/negx.jpg


BIN
resources/skybox/negy.jpg


BIN
resources/skybox/negz.jpg


BIN
resources/skybox/posx.jpg


BIN
resources/skybox/posy.jpg


BIN
resources/skybox/posz.jpg


+ 13 - 0
resources/skybox/readme.txt

@@ -0,0 +1,13 @@
+Author
+======
+
+This is the work of Emil Persson, aka Humus.
+http://www.humus.name
+
+
+
+License
+=======
+
+This work is licensed under a Creative Commons Attribution 3.0 Unported License.
+http://creativecommons.org/licenses/by/3.0/

BIN
resources/speaker.png


+ 479 - 0
simplex-noise.js

@@ -0,0 +1,479 @@
+/*
+ * A fast javascript implementation of simplex noise by Jonas Wagner
+
+Based on a speed-improved simplex noise algorithm for 2D, 3D and 4D in Java.
+Which is based on example code by Stefan Gustavson ([email protected]).
+With Optimisations by Peter Eastman ([email protected]).
+Better rank ordering method by Stefan Gustavson in 2012.
+
+
+ Copyright (c) 2018 Jonas Wagner
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+// (function() {
+
+export const simplex = (function() {
+
+  'use strict';
+
+  var F2 = 0.5 * (Math.sqrt(3.0) - 1.0);
+  var G2 = (3.0 - Math.sqrt(3.0)) / 6.0;
+  var F3 = 1.0 / 3.0;
+  var G3 = 1.0 / 6.0;
+  var F4 = (Math.sqrt(5.0) - 1.0) / 4.0;
+  var G4 = (5.0 - Math.sqrt(5.0)) / 20.0;
+
+  function SimplexNoise(randomOrSeed) {
+    var random;
+    if (typeof randomOrSeed == 'function') {
+      random = randomOrSeed;
+    }
+    else if (randomOrSeed) {
+      random = alea(randomOrSeed);
+    } else {
+      random = Math.random;
+    }
+    this.p = buildPermutationTable(random);
+    this.perm = new Uint8Array(512);
+    this.permMod12 = new Uint8Array(512);
+    for (var i = 0; i < 512; i++) {
+      this.perm[i] = this.p[i & 255];
+      this.permMod12[i] = this.perm[i] % 12;
+    }
+
+  }
+  SimplexNoise.prototype = {
+    grad3: new Float32Array([1, 1, 0,
+      -1, 1, 0,
+      1, -1, 0,
+
+      -1, -1, 0,
+      1, 0, 1,
+      -1, 0, 1,
+
+      1, 0, -1,
+      -1, 0, -1,
+      0, 1, 1,
+
+      0, -1, 1,
+      0, 1, -1,
+      0, -1, -1]),
+    grad4: new Float32Array([0, 1, 1, 1, 0, 1, 1, -1, 0, 1, -1, 1, 0, 1, -1, -1,
+      0, -1, 1, 1, 0, -1, 1, -1, 0, -1, -1, 1, 0, -1, -1, -1,
+      1, 0, 1, 1, 1, 0, 1, -1, 1, 0, -1, 1, 1, 0, -1, -1,
+      -1, 0, 1, 1, -1, 0, 1, -1, -1, 0, -1, 1, -1, 0, -1, -1,
+      1, 1, 0, 1, 1, 1, 0, -1, 1, -1, 0, 1, 1, -1, 0, -1,
+      -1, 1, 0, 1, -1, 1, 0, -1, -1, -1, 0, 1, -1, -1, 0, -1,
+      1, 1, 1, 0, 1, 1, -1, 0, 1, -1, 1, 0, 1, -1, -1, 0,
+      -1, 1, 1, 0, -1, 1, -1, 0, -1, -1, 1, 0, -1, -1, -1, 0]),
+    noise2D: function(xin, yin) {
+      var permMod12 = this.permMod12;
+      var perm = this.perm;
+      var grad3 = this.grad3;
+      var n0 = 0; // Noise contributions from the three corners
+      var n1 = 0;
+      var n2 = 0;
+      // Skew the input space to determine which simplex cell we're in
+      var s = (xin + yin) * F2; // Hairy factor for 2D
+      var i = Math.floor(xin + s);
+      var j = Math.floor(yin + s);
+      var t = (i + j) * G2;
+      var X0 = i - t; // Unskew the cell origin back to (x,y) space
+      var Y0 = j - t;
+      var x0 = xin - X0; // The x,y distances from the cell origin
+      var y0 = yin - Y0;
+      // For the 2D case, the simplex shape is an equilateral triangle.
+      // Determine which simplex we are in.
+      var i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords
+      if (x0 > y0) {
+        i1 = 1;
+        j1 = 0;
+      } // lower triangle, XY order: (0,0)->(1,0)->(1,1)
+      else {
+        i1 = 0;
+        j1 = 1;
+      } // upper triangle, YX order: (0,0)->(0,1)->(1,1)
+      // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
+      // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
+      // c = (3-sqrt(3))/6
+      var x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords
+      var y1 = y0 - j1 + G2;
+      var x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords
+      var y2 = y0 - 1.0 + 2.0 * G2;
+      // Work out the hashed gradient indices of the three simplex corners
+      var ii = i & 255;
+      var jj = j & 255;
+      // Calculate the contribution from the three corners
+      var t0 = 0.5 - x0 * x0 - y0 * y0;
+      if (t0 >= 0) {
+        var gi0 = permMod12[ii + perm[jj]] * 3;
+        t0 *= t0;
+        n0 = t0 * t0 * (grad3[gi0] * x0 + grad3[gi0 + 1] * y0); // (x,y) of grad3 used for 2D gradient
+      }
+      var t1 = 0.5 - x1 * x1 - y1 * y1;
+      if (t1 >= 0) {
+        var gi1 = permMod12[ii + i1 + perm[jj + j1]] * 3;
+        t1 *= t1;
+        n1 = t1 * t1 * (grad3[gi1] * x1 + grad3[gi1 + 1] * y1);
+      }
+      var t2 = 0.5 - x2 * x2 - y2 * y2;
+      if (t2 >= 0) {
+        var gi2 = permMod12[ii + 1 + perm[jj + 1]] * 3;
+        t2 *= t2;
+        n2 = t2 * t2 * (grad3[gi2] * x2 + grad3[gi2 + 1] * y2);
+      }
+      // Add contributions from each corner to get the final noise value.
+      // The result is scaled to return values in the interval [-1,1].
+      return 70.0 * (n0 + n1 + n2);
+    },
+    // 3D simplex noise
+    noise3D: function(xin, yin, zin) {
+      var permMod12 = this.permMod12;
+      var perm = this.perm;
+      var grad3 = this.grad3;
+      var n0, n1, n2, n3; // Noise contributions from the four corners
+      // Skew the input space to determine which simplex cell we're in
+      var s = (xin + yin + zin) * F3; // Very nice and simple skew factor for 3D
+      var i = Math.floor(xin + s);
+      var j = Math.floor(yin + s);
+      var k = Math.floor(zin + s);
+      var t = (i + j + k) * G3;
+      var X0 = i - t; // Unskew the cell origin back to (x,y,z) space
+      var Y0 = j - t;
+      var Z0 = k - t;
+      var x0 = xin - X0; // The x,y,z distances from the cell origin
+      var y0 = yin - Y0;
+      var z0 = zin - Z0;
+      // For the 3D case, the simplex shape is a slightly irregular tetrahedron.
+      // Determine which simplex we are in.
+      var i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords
+      var i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords
+      if (x0 >= y0) {
+        if (y0 >= z0) {
+          i1 = 1;
+          j1 = 0;
+          k1 = 0;
+          i2 = 1;
+          j2 = 1;
+          k2 = 0;
+        } // X Y Z order
+        else if (x0 >= z0) {
+          i1 = 1;
+          j1 = 0;
+          k1 = 0;
+          i2 = 1;
+          j2 = 0;
+          k2 = 1;
+        } // X Z Y order
+        else {
+          i1 = 0;
+          j1 = 0;
+          k1 = 1;
+          i2 = 1;
+          j2 = 0;
+          k2 = 1;
+        } // Z X Y order
+      }
+      else { // x0<y0
+        if (y0 < z0) {
+          i1 = 0;
+          j1 = 0;
+          k1 = 1;
+          i2 = 0;
+          j2 = 1;
+          k2 = 1;
+        } // Z Y X order
+        else if (x0 < z0) {
+          i1 = 0;
+          j1 = 1;
+          k1 = 0;
+          i2 = 0;
+          j2 = 1;
+          k2 = 1;
+        } // Y Z X order
+        else {
+          i1 = 0;
+          j1 = 1;
+          k1 = 0;
+          i2 = 1;
+          j2 = 1;
+          k2 = 0;
+        } // Y X Z order
+      }
+      // A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
+      // a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and
+      // a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where
+      // c = 1/6.
+      var x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords
+      var y1 = y0 - j1 + G3;
+      var z1 = z0 - k1 + G3;
+      var x2 = x0 - i2 + 2.0 * G3; // Offsets for third corner in (x,y,z) coords
+      var y2 = y0 - j2 + 2.0 * G3;
+      var z2 = z0 - k2 + 2.0 * G3;
+      var x3 = x0 - 1.0 + 3.0 * G3; // Offsets for last corner in (x,y,z) coords
+      var y3 = y0 - 1.0 + 3.0 * G3;
+      var z3 = z0 - 1.0 + 3.0 * G3;
+      // Work out the hashed gradient indices of the four simplex corners
+      var ii = i & 255;
+      var jj = j & 255;
+      var kk = k & 255;
+      // Calculate the contribution from the four corners
+      var t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
+      if (t0 < 0) n0 = 0.0;
+      else {
+        var gi0 = permMod12[ii + perm[jj + perm[kk]]] * 3;
+        t0 *= t0;
+        n0 = t0 * t0 * (grad3[gi0] * x0 + grad3[gi0 + 1] * y0 + grad3[gi0 + 2] * z0);
+      }
+      var t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
+      if (t1 < 0) n1 = 0.0;
+      else {
+        var gi1 = permMod12[ii + i1 + perm[jj + j1 + perm[kk + k1]]] * 3;
+        t1 *= t1;
+        n1 = t1 * t1 * (grad3[gi1] * x1 + grad3[gi1 + 1] * y1 + grad3[gi1 + 2] * z1);
+      }
+      var t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
+      if (t2 < 0) n2 = 0.0;
+      else {
+        var gi2 = permMod12[ii + i2 + perm[jj + j2 + perm[kk + k2]]] * 3;
+        t2 *= t2;
+        n2 = t2 * t2 * (grad3[gi2] * x2 + grad3[gi2 + 1] * y2 + grad3[gi2 + 2] * z2);
+      }
+      var t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
+      if (t3 < 0) n3 = 0.0;
+      else {
+        var gi3 = permMod12[ii + 1 + perm[jj + 1 + perm[kk + 1]]] * 3;
+        t3 *= t3;
+        n3 = t3 * t3 * (grad3[gi3] * x3 + grad3[gi3 + 1] * y3 + grad3[gi3 + 2] * z3);
+      }
+      // Add contributions from each corner to get the final noise value.
+      // The result is scaled to stay just inside [-1,1]
+      return 32.0 * (n0 + n1 + n2 + n3);
+    },
+    // 4D simplex noise, better simplex rank ordering method 2012-03-09
+    noise4D: function(x, y, z, w) {
+      var perm = this.perm;
+      var grad4 = this.grad4;
+
+      var n0, n1, n2, n3, n4; // Noise contributions from the five corners
+      // Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in
+      var s = (x + y + z + w) * F4; // Factor for 4D skewing
+      var i = Math.floor(x + s);
+      var j = Math.floor(y + s);
+      var k = Math.floor(z + s);
+      var l = Math.floor(w + s);
+      var t = (i + j + k + l) * G4; // Factor for 4D unskewing
+      var X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space
+      var Y0 = j - t;
+      var Z0 = k - t;
+      var W0 = l - t;
+      var x0 = x - X0; // The x,y,z,w distances from the cell origin
+      var y0 = y - Y0;
+      var z0 = z - Z0;
+      var w0 = w - W0;
+      // For the 4D case, the simplex is a 4D shape I won't even try to describe.
+      // To find out which of the 24 possible simplices we're in, we need to
+      // determine the magnitude ordering of x0, y0, z0 and w0.
+      // Six pair-wise comparisons are performed between each possible pair
+      // of the four coordinates, and the results are used to rank the numbers.
+      var rankx = 0;
+      var ranky = 0;
+      var rankz = 0;
+      var rankw = 0;
+      if (x0 > y0) rankx++;
+      else ranky++;
+      if (x0 > z0) rankx++;
+      else rankz++;
+      if (x0 > w0) rankx++;
+      else rankw++;
+      if (y0 > z0) ranky++;
+      else rankz++;
+      if (y0 > w0) ranky++;
+      else rankw++;
+      if (z0 > w0) rankz++;
+      else rankw++;
+      var i1, j1, k1, l1; // The integer offsets for the second simplex corner
+      var i2, j2, k2, l2; // The integer offsets for the third simplex corner
+      var i3, j3, k3, l3; // The integer offsets for the fourth simplex corner
+      // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
+      // Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w
+      // impossible. Only the 24 indices which have non-zero entries make any sense.
+      // We use a thresholding to set the coordinates in turn from the largest magnitude.
+      // Rank 3 denotes the largest coordinate.
+      i1 = rankx >= 3 ? 1 : 0;
+      j1 = ranky >= 3 ? 1 : 0;
+      k1 = rankz >= 3 ? 1 : 0;
+      l1 = rankw >= 3 ? 1 : 0;
+      // Rank 2 denotes the second largest coordinate.
+      i2 = rankx >= 2 ? 1 : 0;
+      j2 = ranky >= 2 ? 1 : 0;
+      k2 = rankz >= 2 ? 1 : 0;
+      l2 = rankw >= 2 ? 1 : 0;
+      // Rank 1 denotes the second smallest coordinate.
+      i3 = rankx >= 1 ? 1 : 0;
+      j3 = ranky >= 1 ? 1 : 0;
+      k3 = rankz >= 1 ? 1 : 0;
+      l3 = rankw >= 1 ? 1 : 0;
+      // The fifth corner has all coordinate offsets = 1, so no need to compute that.
+      var x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords
+      var y1 = y0 - j1 + G4;
+      var z1 = z0 - k1 + G4;
+      var w1 = w0 - l1 + G4;
+      var x2 = x0 - i2 + 2.0 * G4; // Offsets for third corner in (x,y,z,w) coords
+      var y2 = y0 - j2 + 2.0 * G4;
+      var z2 = z0 - k2 + 2.0 * G4;
+      var w2 = w0 - l2 + 2.0 * G4;
+      var x3 = x0 - i3 + 3.0 * G4; // Offsets for fourth corner in (x,y,z,w) coords
+      var y3 = y0 - j3 + 3.0 * G4;
+      var z3 = z0 - k3 + 3.0 * G4;
+      var w3 = w0 - l3 + 3.0 * G4;
+      var x4 = x0 - 1.0 + 4.0 * G4; // Offsets for last corner in (x,y,z,w) coords
+      var y4 = y0 - 1.0 + 4.0 * G4;
+      var z4 = z0 - 1.0 + 4.0 * G4;
+      var w4 = w0 - 1.0 + 4.0 * G4;
+      // Work out the hashed gradient indices of the five simplex corners
+      var ii = i & 255;
+      var jj = j & 255;
+      var kk = k & 255;
+      var ll = l & 255;
+      // Calculate the contribution from the five corners
+      var t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0;
+      if (t0 < 0) n0 = 0.0;
+      else {
+        var gi0 = (perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32) * 4;
+        t0 *= t0;
+        n0 = t0 * t0 * (grad4[gi0] * x0 + grad4[gi0 + 1] * y0 + grad4[gi0 + 2] * z0 + grad4[gi0 + 3] * w0);
+      }
+      var t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1;
+      if (t1 < 0) n1 = 0.0;
+      else {
+        var gi1 = (perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32) * 4;
+        t1 *= t1;
+        n1 = t1 * t1 * (grad4[gi1] * x1 + grad4[gi1 + 1] * y1 + grad4[gi1 + 2] * z1 + grad4[gi1 + 3] * w1);
+      }
+      var t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2;
+      if (t2 < 0) n2 = 0.0;
+      else {
+        var gi2 = (perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32) * 4;
+        t2 *= t2;
+        n2 = t2 * t2 * (grad4[gi2] * x2 + grad4[gi2 + 1] * y2 + grad4[gi2 + 2] * z2 + grad4[gi2 + 3] * w2);
+      }
+      var t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3;
+      if (t3 < 0) n3 = 0.0;
+      else {
+        var gi3 = (perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32) * 4;
+        t3 *= t3;
+        n3 = t3 * t3 * (grad4[gi3] * x3 + grad4[gi3 + 1] * y3 + grad4[gi3 + 2] * z3 + grad4[gi3 + 3] * w3);
+      }
+      var t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4;
+      if (t4 < 0) n4 = 0.0;
+      else {
+        var gi4 = (perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32) * 4;
+        t4 *= t4;
+        n4 = t4 * t4 * (grad4[gi4] * x4 + grad4[gi4 + 1] * y4 + grad4[gi4 + 2] * z4 + grad4[gi4 + 3] * w4);
+      }
+      // Sum up and scale the result to cover the range [-1,1]
+      return 27.0 * (n0 + n1 + n2 + n3 + n4);
+    }
+  };
+
+  function buildPermutationTable(random) {
+    var i;
+    var p = new Uint8Array(256);
+    for (i = 0; i < 256; i++) {
+      p[i] = i;
+    }
+    for (i = 0; i < 255; i++) {
+      var r = i + ~~(random() * (256 - i));
+      var aux = p[i];
+      p[i] = p[r];
+      p[r] = aux;
+    }
+    return p;
+  }
+  SimplexNoise._buildPermutationTable = buildPermutationTable;
+
+  function alea() {
+    // Johannes Baagøe <[email protected]>, 2010
+    var s0 = 0;
+    var s1 = 0;
+    var s2 = 0;
+    var c = 1;
+
+    var mash = masher();
+    s0 = mash(' ');
+    s1 = mash(' ');
+    s2 = mash(' ');
+
+    for (var i = 0; i < arguments.length; i++) {
+      s0 -= mash(arguments[i]);
+      if (s0 < 0) {
+        s0 += 1;
+      }
+      s1 -= mash(arguments[i]);
+      if (s1 < 0) {
+        s1 += 1;
+      }
+      s2 -= mash(arguments[i]);
+      if (s2 < 0) {
+        s2 += 1;
+      }
+    }
+    mash = null;
+    return function() {
+      var t = 2091639 * s0 + c * 2.3283064365386963e-10; // 2^-32
+      s0 = s1;
+      s1 = s2;
+      return s2 = t - (c = t | 0);
+    };
+  }
+  function masher() {
+    var n = 0xefc8249d;
+    return function(data) {
+      data = data.toString();
+      for (var i = 0; i < data.length; i++) {
+        n += data.charCodeAt(i);
+        var h = 0.02519603282416938 * n;
+        n = h >>> 0;
+        h -= n;
+        h *= n;
+        n = h >>> 0;
+        h -= n;
+        n += h * 0x100000000; // 2^32
+      }
+      return (n >>> 0) * 2.3283064365386963e-10; // 2^-32
+    };
+  }
+
+  // // amd
+  // if (typeof define !== 'undefined' && define.amd) define(function() {return SimplexNoise;});
+  // // common js
+  // if (typeof exports !== 'undefined') exports.SimplexNoise = SimplexNoise;
+  // // browser
+  // else if (typeof window !== 'undefined') window.SimplexNoise = SimplexNoise;
+  // // nodejs
+  // if (typeof module !== 'undefined') {
+  //   module.exports = SimplexNoise;
+  // }
+  return {
+    SimplexNoise: SimplexNoise
+  };
+
+})();