├── LICENSE ├── base.css ├── index.html ├── main.js ├── math.js ├── noise.js ├── resources ├── background-grey-dots.png ├── crosshair.png ├── fire.png ├── freepbr │ ├── broken_down_concrete2_Height.png │ ├── broken_down_concrete2_albedo.png │ ├── broken_down_concrete2_ao.png │ ├── broken_down_concrete2_metallic.png │ ├── broken_down_concrete2_normal.png │ ├── broken_down_concrete2_roughness.png │ ├── concrete3-albedo.png │ ├── concrete3-metallic.png │ ├── concrete3-normal.png │ ├── concrete3-roughness.png │ ├── flaking-plaster_albedo.png │ ├── flaking-plaster_ao.png │ ├── flaking-plaster_metallic.png │ ├── flaking-plaster_normal-ogl.png │ ├── flaking-plaster_roughness.png │ ├── readme.txt │ ├── rustediron2_albedo.png │ ├── rustediron2_metallic.png │ ├── rustediron2_normal.png │ ├── rustediron2_roughness.png │ ├── worn_metal4_Height.png │ ├── worn_metal4_albedo.png │ ├── worn_metal4_ao.png │ ├── worn_metal4_metallic.png │ ├── worn_metal4_normal.png │ ├── worn_metal4_preview.jpg │ └── worn_metal4_roughness.png ├── music │ ├── AcousticRock.mp3 │ ├── Ectoplasm.mp3 │ └── readme.txt ├── skybox │ ├── negx.jpg │ ├── negy.jpg │ ├── negz.jpg │ ├── posx.jpg │ ├── posy.jpg │ ├── posz.jpg │ └── readme.txt └── speaker.png └── simplex-noise.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 simondevyoutube 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /base.css: -------------------------------------------------------------------------------- 1 | body { 2 | width: 100%; 3 | height: 100%; 4 | position: absolute; 5 | background: #000000; 6 | margin: 0; 7 | padding: 0; 8 | overscroll-behavior: none; 9 | } 10 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Three.JS Tutorial: Audio Visualizer 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'https://cdn.skypack.dev/three@0.136'; 2 | 3 | import {EffectComposer} from 'https://cdn.skypack.dev/three@0.136/examples/jsm/postprocessing/EffectComposer.js'; 4 | import {ShaderPass} from 'https://cdn.skypack.dev/three@0.136/examples//jsm/postprocessing/ShaderPass.js'; 5 | import {GammaCorrectionShader} from 'https://cdn.skypack.dev/three@0.136/examples/jsm/shaders/GammaCorrectionShader.js'; 6 | import {RenderPass} from 'https://cdn.skypack.dev/three@0.136/examples/jsm/postprocessing/RenderPass.js'; 7 | import {FXAAShader} from 'https://cdn.skypack.dev/three@0.136/examples/jsm/shaders/FXAAShader.js'; 8 | 9 | import {math} from './math.js'; 10 | import {noise} from './noise.js'; 11 | 12 | 13 | 14 | const FS_DECLARATIONS = ` 15 | 16 | uniform sampler2D audioDataTexture; 17 | uniform vec2 iResolution; 18 | uniform float iTime; 19 | 20 | #define M_PI 3.14159 21 | #define NUM_BARS 64.0 22 | #define CIRCLE_RADIUS 0.15 23 | #define BAR_HEIGHT 0.125 24 | 25 | 26 | // All code snippets taken from Inigo Quilez's site 27 | // Make sure to check out his site! 28 | // https://iquilezles.org/ 29 | // 30 | vec3 pal( in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d) { 31 | return a + b*cos( 6.28318*(c*t+d) ); 32 | } 33 | 34 | float dot2(in vec2 v ) { return dot(v,v); } 35 | 36 | float sdfTrapezoid(in vec2 p, in float r1, float r2, float he) { 37 | vec2 k1 = vec2(r2,he); 38 | vec2 k2 = vec2(r2-r1,2.0*he); 39 | p.x = abs(p.x); 40 | vec2 ca = vec2(p.x-min(p.x,(p.y<0.0)?r1:r2), abs(p.y)-he); 41 | vec2 cb = p - k1 + k2*clamp( dot(k1-p,k2)/dot2(k2), 0.0, 1.0 ); 42 | float s = (cb.x<0.0 && ca.y<0.0) ? -1.0 : 1.0; 43 | return s*sqrt( min(dot2(ca),dot2(cb)) ); 44 | } 45 | 46 | float sdUnevenCapsule( vec2 p, float r1, float r2, float h ) { 47 | p.x = abs(p.x); 48 | float b = (r1-r2)/h; 49 | float a = sqrt(1.0-b*b); 50 | float k = dot(p,vec2(-b,a)); 51 | if( k < 0.0 ) return length(p) - r1; 52 | if( k > a*h ) return length(p-vec2(0.0,h)) - r2; 53 | return dot(p, vec2(a,b) ) - r1; 54 | } 55 | 56 | float sdTriangleIsosceles( in vec2 p, in vec2 q ) { 57 | p.x = abs(p.x); 58 | vec2 a = p - q*clamp( dot(p,q)/dot(q,q), 0.0, 1.0 ); 59 | vec2 b = p - q*vec2( clamp( p.x/q.x, 0.0, 1.0 ), 1.0 ); 60 | float s = -sign( q.y ); 61 | vec2 d = min( vec2( dot(a,a), s*(p.x*q.y-p.y*q.x) ), 62 | vec2( dot(b,b), s*(p.y-q.y) )); 63 | return -sqrt(d.x)*sign(d.y); 64 | } 65 | 66 | float opSmoothUnion( float d1, float d2, float k ) { 67 | float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 ); 68 | return mix( d2, d1, h ) - k*h*(1.0-h); 69 | } 70 | 71 | float opUnion( float d1, float d2 ) { return min(d1,d2); } 72 | float opIntersection( float d1, float d2 ) { return max(d1,d2); } 73 | float opSubtraction( float d1, float d2 ) { return max(-d1,d2); } 74 | 75 | float sdfBar(vec2 position, vec2 dimensions, vec2 uv, float frequencySample) { 76 | float w = mix(dimensions.x * 0.5, dimensions.x, smoothstep(0.0, 1.0, frequencySample)); 77 | vec2 basePosition = uv - position + vec2(0.0, -dimensions.y * 0.5 - frequencySample * 0.05); 78 | 79 | float d = sdfTrapezoid( 80 | basePosition, 81 | dimensions.x * 0.5, 82 | w, dimensions.y * 0.5); 83 | 84 | return (d > 0.0 ? 0.0 : 1.0); 85 | } 86 | 87 | vec2 rotate2D(vec2 pt, float a) { 88 | float c = cos(a); 89 | float s = sin(a); 90 | 91 | mat2 r = mat2(c, s, -s, c); 92 | 93 | return r * pt; 94 | } 95 | 96 | vec4 DrawBars(vec2 center, vec2 uv) { 97 | float barWidth = 2.0 * M_PI * CIRCLE_RADIUS / (NUM_BARS * 1.25); 98 | 99 | vec4 resultColour = vec4(1.0, 1.0, 1.0, 0.0); 100 | vec2 position = vec2(center.x, center.y + CIRCLE_RADIUS); 101 | 102 | for(int i = 0; i < int(NUM_BARS); i++) { 103 | float frequencyUV = 0.0; 104 | 105 | if (float(i) >= NUM_BARS * 0.5) { 106 | frequencyUV = 1.0 - ((float(i) - (NUM_BARS * 0.5)) / (NUM_BARS * 0.5)); 107 | } else { 108 | frequencyUV = float(i) / (NUM_BARS * 0.5); 109 | } 110 | 111 | float frequencyData = texture(audioDataTexture, vec2(frequencyUV, 0.0)).x; 112 | 113 | float barFinalHeight = BAR_HEIGHT * (0.1 + 0.9 * frequencyData); 114 | vec2 barDimensions = vec2(barWidth, barFinalHeight); 115 | vec2 barUvs = rotate2D(uv - center, (2.0 * M_PI * float(i)) / NUM_BARS) + center; 116 | 117 | resultColour.w += sdfBar(position, barDimensions, barUvs, frequencyData); 118 | } 119 | 120 | float d = saturate(1.1 * ((distance(uv, center) - CIRCLE_RADIUS) / BAR_HEIGHT)); 121 | d = smoothstep(0.0, 1.0, d); 122 | d = 0.45 + 0.55 * d; 123 | 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) ); 124 | resultColour.xyz *= resultColour.w; 125 | 126 | return saturate(resultColour); 127 | } 128 | 129 | 130 | vec4 AudioVisualizer() { 131 | float aspect = iResolution.x / iResolution.y; 132 | vec2 uv = vUv * vec2(aspect, 1.0); 133 | 134 | vec2 circleCenter = vec2(aspect * 0.5, 0.5); 135 | 136 | return DrawBars(circleCenter, uv); 137 | } 138 | `; 139 | 140 | 141 | function clamp(x, a, b) { 142 | return Math.min(Math.max(x, a), b); 143 | } 144 | 145 | const KEYS = { 146 | 'a': 65, 147 | 's': 83, 148 | 'w': 87, 149 | 'd': 68, 150 | }; 151 | 152 | class InputController { 153 | constructor(target) { 154 | this.target_ = target || document; 155 | this.initialize_(); 156 | } 157 | 158 | initialize_() { 159 | this.current_ = { 160 | leftButton: false, 161 | rightButton: false, 162 | mouseXDelta: 0, 163 | mouseYDelta: 0, 164 | mouseX: 0, 165 | mouseY: 0, 166 | }; 167 | this.previous_ = null; 168 | this.keys_ = {}; 169 | this.previousKeys_ = {}; 170 | this.target_.addEventListener('mousedown', (e) => this.onMouseDown_(e), false); 171 | this.target_.addEventListener('mousemove', (e) => this.onMouseMove_(e), false); 172 | this.target_.addEventListener('mouseup', (e) => this.onMouseUp_(e), false); 173 | this.target_.addEventListener('keydown', (e) => this.onKeyDown_(e), false); 174 | this.target_.addEventListener('keyup', (e) => this.onKeyUp_(e), false); 175 | } 176 | 177 | onMouseMove_(e) { 178 | this.current_.mouseX = e.pageX - window.innerWidth / 2; 179 | this.current_.mouseY = e.pageY - window.innerHeight / 2; 180 | 181 | if (this.previous_ === null) { 182 | this.previous_ = {...this.current_}; 183 | } 184 | 185 | this.current_.mouseXDelta = this.current_.mouseX - this.previous_.mouseX; 186 | this.current_.mouseYDelta = this.current_.mouseY - this.previous_.mouseY; 187 | } 188 | 189 | onMouseDown_(e) { 190 | this.onMouseMove_(e); 191 | 192 | switch (e.button) { 193 | case 0: { 194 | this.current_.leftButton = true; 195 | break; 196 | } 197 | case 2: { 198 | this.current_.rightButton = true; 199 | break; 200 | } 201 | } 202 | } 203 | 204 | onMouseUp_(e) { 205 | this.onMouseMove_(e); 206 | 207 | switch (e.button) { 208 | case 0: { 209 | this.current_.leftButton = false; 210 | break; 211 | } 212 | case 2: { 213 | this.current_.rightButton = false; 214 | break; 215 | } 216 | } 217 | } 218 | 219 | onKeyDown_(e) { 220 | this.keys_[e.keyCode] = true; 221 | } 222 | 223 | onKeyUp_(e) { 224 | this.keys_[e.keyCode] = false; 225 | } 226 | 227 | key(keyCode) { 228 | return !!this.keys_[keyCode]; 229 | } 230 | 231 | isReady() { 232 | return this.previous_ !== null; 233 | } 234 | 235 | update(_) { 236 | if (this.previous_ !== null) { 237 | this.current_.mouseXDelta = this.current_.mouseX - this.previous_.mouseX; 238 | this.current_.mouseYDelta = this.current_.mouseY - this.previous_.mouseY; 239 | 240 | this.previous_ = {...this.current_}; 241 | } 242 | } 243 | }; 244 | 245 | 246 | 247 | class FirstPersonCamera { 248 | constructor(camera, objects) { 249 | this.camera_ = camera; 250 | this.input_ = new InputController(); 251 | this.phi_ = 0; 252 | this.phiSpeed_ = 8; 253 | this.theta_ = 0; 254 | this.thetaSpeed_ = 5; 255 | this.movementSpeed_ = 10; 256 | this.rotation_ = new THREE.Quaternion(); 257 | this.translation_ = new THREE.Vector3(30, 2, 0); 258 | this.bobTimer_ = 0; 259 | this.bobMagnitude_ = 0.175; 260 | this.bobFrequency_ = 10; 261 | this.objects_ = objects; 262 | } 263 | 264 | update(timeElapsedS) { 265 | if (this.input_.isReady()) { 266 | this.updateRotation_(timeElapsedS); 267 | this.updateTranslation_(timeElapsedS); 268 | this.updateBob_(timeElapsedS); 269 | this.updateCamera_(timeElapsedS); 270 | } 271 | 272 | this.input_.update(timeElapsedS); 273 | } 274 | 275 | updateBob_(timeElapsedS) { 276 | if (this.bobActive_) { 277 | const waveLength = Math.PI; 278 | const nextStep = 1 + Math.floor(((this.bobTimer_ + 0.000001) * this.bobFrequency_) / waveLength); 279 | const nextStepTime = nextStep * waveLength / this.bobFrequency_; 280 | this.bobTimer_ = Math.min(this.bobTimer_ + timeElapsedS, nextStepTime); 281 | 282 | if (this.bobTimer_ == nextStepTime) { 283 | this.bobActive_ = false; 284 | this.bobTimer_ = 0; 285 | } 286 | } 287 | } 288 | 289 | updateCamera_(timeElapsedS) { 290 | this.camera_.quaternion.copy(this.rotation_); 291 | this.camera_.position.copy(this.translation_); 292 | this.camera_.position.y += Math.sin(this.bobTimer_ * this.bobFrequency_) * this.bobMagnitude_; 293 | 294 | const forward = new THREE.Vector3(0, 0, -1); 295 | forward.applyQuaternion(this.rotation_); 296 | 297 | const dir = forward.clone(); 298 | 299 | forward.multiplyScalar(100); 300 | forward.add(this.translation_); 301 | 302 | let closest = forward; 303 | const result = new THREE.Vector3(); 304 | const ray = new THREE.Ray(this.translation_, dir); 305 | for (let i = 0; i < this.objects_.length; ++i) { 306 | if (ray.intersectBox(this.objects_[i], result)) { 307 | if (result.distanceTo(ray.origin) < closest.distanceTo(ray.origin)) { 308 | closest = result.clone(); 309 | } 310 | } 311 | } 312 | 313 | this.camera_.lookAt(closest); 314 | } 315 | 316 | updateTranslation_(timeElapsedS) { 317 | const forwardVelocity = ((this.input_.key(KEYS.w) ? 1 : 0) + (this.input_.key(KEYS.s) ? -1 : 0)); 318 | const strafeVelocity = ((this.input_.key(KEYS.a) ? 1 : 0) + (this.input_.key(KEYS.d) ? -1 : 0)); 319 | 320 | const qx = new THREE.Quaternion(); 321 | qx.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.phi_); 322 | 323 | const forward = new THREE.Vector3(0, 0, -1); 324 | forward.applyQuaternion(qx); 325 | forward.multiplyScalar(forwardVelocity * this.movementSpeed_ * timeElapsedS); 326 | 327 | const left = new THREE.Vector3(-1, 0, 0); 328 | left.applyQuaternion(qx); 329 | left.multiplyScalar(strafeVelocity * this.movementSpeed_ * timeElapsedS); 330 | 331 | this.translation_.add(forward); 332 | this.translation_.add(left); 333 | 334 | if(forwardVelocity != 0 || strafeVelocity != 0) { 335 | this.bobActive_ = true; 336 | } 337 | } 338 | 339 | updateRotation_(timeElapsedS) { 340 | const xh = this.input_.current_.mouseXDelta / window.innerWidth; 341 | const yh = this.input_.current_.mouseYDelta / window.innerHeight; 342 | 343 | this.phi_ += -xh * this.phiSpeed_; 344 | this.theta_ = clamp(this.theta_ + -yh * this.thetaSpeed_, -Math.PI / 3, Math.PI / 3); 345 | 346 | // console.log(this.input_.current_.mouseYDelta); 347 | 348 | const qx = new THREE.Quaternion(); 349 | qx.setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.phi_); 350 | const qz = new THREE.Quaternion(); 351 | qz.setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.theta_); 352 | 353 | const q = new THREE.Quaternion(); 354 | q.multiply(qx); 355 | q.multiply(qz); 356 | 357 | const t = 1.0 - Math.pow(0.001, 5 * timeElapsedS); 358 | this.rotation_.slerp(q, t); 359 | } 360 | }; 361 | 362 | 363 | 364 | class LinearSpline { 365 | constructor(lerp) { 366 | this.points_ = []; 367 | this._lerp = lerp; 368 | } 369 | 370 | AddPoint(t, d) { 371 | this.points_.push([t, d]); 372 | } 373 | 374 | Get(t) { 375 | let p1 = 0; 376 | 377 | for (let i = 0; i < this.points_.length; i++) { 378 | if (this.points_[i][0] >= t) { 379 | break; 380 | } 381 | p1 = i; 382 | } 383 | 384 | const p2 = Math.min(this.points_.length - 1, p1 + 1); 385 | 386 | if (p1 == p2) { 387 | return this.points_[p1][1]; 388 | } 389 | 390 | return this._lerp( 391 | (t - this.points_[p1][0]) / ( 392 | this.points_[p2][0] - this.points_[p1][0]), 393 | this.points_[p1][1], this.points_[p2][1]); 394 | } 395 | } 396 | 397 | 398 | class FirstPersonCameraDemo { 399 | constructor() { 400 | this.initialize_(); 401 | } 402 | 403 | initialize_() { 404 | this.initializeRenderer_(); 405 | this.initializeScene_(); 406 | this.initializePostFX_(); 407 | this.initializeAudio_(); 408 | 409 | this.previousRAF_ = null; 410 | this.raf_(); 411 | this.onWindowResize_(); 412 | } 413 | 414 | initializeAudio_() { 415 | this.listener_ = new THREE.AudioListener(); 416 | this.camera_.add(this.listener_); 417 | 418 | const sound1 = new THREE.PositionalAudio(this.listener_); 419 | const sound2 = new THREE.PositionalAudio(this.listener_); 420 | 421 | this.speakerMesh1_.add(sound1); 422 | this.speakerMesh2_.add(sound2); 423 | 424 | const loader = new THREE.AudioLoader(); 425 | loader.load('resources/music/Ectoplasm.mp3', (buffer) => { 426 | setTimeout(() => { 427 | sound1.setBuffer(buffer); 428 | sound1.setLoop(true); 429 | sound1.setVolume(1.0); 430 | sound1.setRefDistance(1); 431 | sound1.play(); 432 | this.analyzer1_ = new THREE.AudioAnalyser(sound1, 32); 433 | this.analyzer1Data_ = []; 434 | }, 5000); 435 | }); 436 | 437 | loader.load('resources/music/AcousticRock.mp3', (buffer) => { 438 | setTimeout(() => { 439 | sound2.setBuffer(buffer); 440 | sound2.setLoop(true); 441 | sound2.setVolume(1.0); 442 | sound2.setRefDistance(1); 443 | sound2.play(); 444 | this.analyzer2_ = new THREE.AudioAnalyser(sound2, 128); 445 | this.analyzer2Texture_ = new THREE.DataTexture( 446 | this.analyzer2_.data, 64, 1, THREE.RedFormat); 447 | this.analyzer2Texture_.magFilter = THREE.LinearFilter; 448 | }, 5000); 449 | }); 450 | 451 | this.indexTimer_ = 0; 452 | this.noise1_ = new noise.Noise({ 453 | octaves: 3, 454 | persistence: 0.5, 455 | lacunarity: 1.6, 456 | exponentiation: 1.0, 457 | height: 1.0, 458 | scale: 0.1, 459 | seed: 1 460 | }); 461 | } 462 | 463 | initializeScene_() { 464 | const distance = 50.0; 465 | const angle = Math.PI / 4.0; 466 | const penumbra = 0.5; 467 | const decay = 1.0; 468 | 469 | let light = null; 470 | 471 | light = new THREE.SpotLight( 472 | 0xFFFFFF, 100.0, distance, angle, penumbra, decay); 473 | light.castShadow = true; 474 | light.shadow.bias = -0.00001; 475 | light.shadow.mapSize.width = 4096; 476 | light.shadow.mapSize.height = 4096; 477 | light.shadow.camera.near = 1; 478 | light.shadow.camera.far = 100; 479 | light.position.set(-35, 25, 0); 480 | light.target.position.set(-40, 4, 0); 481 | this.scene_.add(light); 482 | this.scene_.add(light.target); 483 | 484 | light = new THREE.SpotLight( 485 | 0xFFFFFF, 100.0, distance, angle, penumbra, decay); 486 | light.castShadow = true; 487 | light.shadow.bias = -0.00001; 488 | light.shadow.mapSize.width = 4096; 489 | light.shadow.mapSize.height = 4096; 490 | light.shadow.camera.near = 1; 491 | light.shadow.camera.far = 100; 492 | light.position.set(35, 25, 0); 493 | light.target.position.set(40, 4, 0); 494 | this.scene_.add(light); 495 | this.scene_.add(light.target); 496 | 497 | const upColour = 0xFFFF80; 498 | const downColour = 0x808080; 499 | light = new THREE.HemisphereLight(upColour, downColour, 0.5); 500 | light.color.setHSL( 0.6, 1, 0.6 ); 501 | light.groundColor.setHSL( 0.095, 1, 0.75 ); 502 | light.position.set(0, 4, 0); 503 | this.scene_.add(light); 504 | 505 | const loader = new THREE.CubeTextureLoader(); 506 | const texture = loader.load([ 507 | './resources/skybox/posx.jpg', 508 | './resources/skybox/negx.jpg', 509 | './resources/skybox/posy.jpg', 510 | './resources/skybox/negy.jpg', 511 | './resources/skybox/posz.jpg', 512 | './resources/skybox/negz.jpg', 513 | ]); 514 | 515 | texture.encoding = THREE.sRGBEncoding; 516 | this.scene_.background = texture; 517 | 518 | const mapLoader = new THREE.TextureLoader(); 519 | const maxAnisotropy = this.threejs_.capabilities.getMaxAnisotropy(); 520 | 521 | const plane = new THREE.Mesh( 522 | new THREE.PlaneGeometry(100, 100, 10, 10), 523 | this.loadMaterial_('rustediron2_', 4)); 524 | plane.castShadow = false; 525 | plane.receiveShadow = true; 526 | plane.rotation.x = -Math.PI / 2; 527 | this.scene_.add(plane); 528 | 529 | const concreteMaterial = this.loadMaterial_('concrete3-', 4); 530 | 531 | const wall1 = new THREE.Mesh( 532 | new THREE.BoxGeometry(100, 100, 4), 533 | concreteMaterial); 534 | wall1.position.set(0, -40, -50); 535 | wall1.castShadow = true; 536 | wall1.receiveShadow = true; 537 | this.scene_.add(wall1); 538 | 539 | const wall2 = new THREE.Mesh( 540 | new THREE.BoxGeometry(100, 100, 4), 541 | concreteMaterial); 542 | wall2.position.set(0, -40, 50); 543 | wall2.castShadow = true; 544 | wall2.receiveShadow = true; 545 | this.scene_.add(wall2); 546 | 547 | const wall3 = new THREE.Mesh( 548 | new THREE.BoxGeometry(4, 100, 100), 549 | concreteMaterial); 550 | wall3.position.set(50, -40, 0); 551 | wall3.castShadow = true; 552 | wall3.receiveShadow = true; 553 | this.scene_.add(wall3); 554 | 555 | const wall4 = new THREE.Mesh( 556 | new THREE.BoxGeometry(4, 100, 100), 557 | concreteMaterial); 558 | wall4.position.set(-50, -40, 0); 559 | wall4.castShadow = true; 560 | wall4.receiveShadow = true; 561 | this.scene_.add(wall4); 562 | 563 | const speaker1Material = this.loadMaterial_('worn_metal4_', 1); 564 | const speaker1 = new THREE.Mesh( 565 | new THREE.BoxGeometry(1, 8, 4), 566 | speaker1Material); 567 | speaker1.position.set(-40, 4, 0); 568 | speaker1.castShadow = true; 569 | speaker1.receiveShadow = true; 570 | this.scene_.add(speaker1); 571 | 572 | const speaker1Geo = new THREE.BoxGeometry(0.25, 0.25, 0.25); 573 | const speaker1BoxMaterial = this.loadMaterial_('broken_down_concrete2_', 1); 574 | this.speakerMeshes1_ = []; 575 | const speaker1Group = new THREE.Group(); 576 | speaker1Group.position.x = 0.5 + 0.125; 577 | 578 | for (let x = -5; x <= 5; ++x) { 579 | const row = []; 580 | for (let y = 0; y < 16; ++y) { 581 | const speaker1_1 = new THREE.Mesh( 582 | speaker1Geo, 583 | speaker1BoxMaterial.clone()); 584 | speaker1_1.position.set(0, y*0.35 - 3, x * 0.35); 585 | speaker1_1.castShadow = true; 586 | speaker1_1.receiveShadow = true; 587 | speaker1Group.add(speaker1_1); 588 | row.push(speaker1_1); 589 | } 590 | this.speakerMeshes1_.push(row); 591 | } 592 | speaker1.add(speaker1Group); 593 | 594 | this.speakerMesh1_ = speaker1; 595 | 596 | const speaker2 = new THREE.Mesh( 597 | new THREE.BoxGeometry(1, 8, 4), 598 | new THREE.MeshStandardMaterial({color: 0x404040, roughness: 0.1, metalness: 0 })); 599 | speaker2.position.set(40, 4, 0); 600 | speaker2.castShadow = true; 601 | speaker2.receiveShadow = true; 602 | this.scene_.add(speaker2); 603 | 604 | this.speakerMesh2_ = speaker2; 605 | 606 | const diffuseMap = mapLoader.load('resources/background-grey-dots.png'); 607 | diffuseMap.anisotropy = maxAnisotropy; 608 | 609 | const visualizerMaterial = new THREE.MeshStandardMaterial({ 610 | map: diffuseMap, 611 | normalMap: mapLoader.load('resources/freepbr/flaking-plaster_normal-ogl.png'), 612 | roughnessMap: mapLoader.load('resources/freepbr/flaking-plaster_roughness.png'), 613 | metalnessMap: mapLoader.load('resources/freepbr/flaking-plaster_metallic.png'), 614 | }); 615 | 616 | visualizerMaterial.onBeforeCompile = (shader) => { 617 | shader.uniforms.iTime = { value: 0.0 }; 618 | shader.uniforms.iResolution = {value: new THREE.Vector2(128, 256)}; 619 | shader.uniforms.audioDataTexture = {value: null}; 620 | 621 | shader.fragmentShader = shader.fragmentShader.replace('void main()', FS_DECLARATIONS + 'void main()'); 622 | shader.fragmentShader = shader.fragmentShader.replace('totalEmissiveRadiance = emissive;', ` 623 | 624 | totalEmissiveRadiance = emissive + AudioVisualizer().xyz; 625 | 626 | `); 627 | visualizerMaterial.userData.shader = shader; 628 | }; 629 | 630 | visualizerMaterial.customProgramCacheKey = () => { 631 | return 'visualizerMaterial'; 632 | }; 633 | 634 | this.speaker2Material_ = visualizerMaterial; 635 | 636 | const speaker2Screen = new THREE.Mesh( 637 | new THREE.PlaneGeometry(4, 8), 638 | this.speaker2Material_); 639 | speaker2Screen.castShadow = false; 640 | speaker2Screen.receiveShadow = true; 641 | speaker2Screen.rotation.y = -Math.PI / 2; 642 | speaker2Screen.position.x -= 0.51; 643 | this.speakerMesh2_.add(speaker2Screen); 644 | 645 | // Create Box3 for each mesh in the scene so that we can 646 | // do some easy intersection tests. 647 | const meshes = [ 648 | plane, wall1, wall2, wall3, wall4]; 649 | 650 | this.objects_ = []; 651 | 652 | for (let i = 0; i < meshes.length; ++i) { 653 | const b = new THREE.Box3(); 654 | b.setFromObject(meshes[i]); 655 | this.objects_.push(b); 656 | } 657 | 658 | this.fpsCamera_ = new FirstPersonCamera(this.camera_, this.objects_); 659 | 660 | // Crosshair 661 | const crosshair = mapLoader.load('resources/crosshair.png'); 662 | crosshair.anisotropy = maxAnisotropy; 663 | 664 | this.sprite_ = new THREE.Sprite( 665 | new THREE.SpriteMaterial({map: crosshair, color: 0xffffff, fog: false, depthTest: false, depthWrite: false})); 666 | this.sprite_.scale.set(0.15, 0.15 * this.camera_.aspect, 1) 667 | this.sprite_.position.set(0, 0, -10); 668 | 669 | // this.uiScene_.add(this.sprite_); 670 | } 671 | 672 | loadMaterial_(name, tiling) { 673 | const mapLoader = new THREE.TextureLoader(); 674 | const maxAnisotropy = this.threejs_.capabilities.getMaxAnisotropy(); 675 | 676 | const metalMap = mapLoader.load('resources/freepbr/' + name + 'metallic.png'); 677 | metalMap.anisotropy = maxAnisotropy; 678 | metalMap.wrapS = THREE.RepeatWrapping; 679 | metalMap.wrapT = THREE.RepeatWrapping; 680 | metalMap.repeat.set(tiling, tiling); 681 | 682 | const albedo = mapLoader.load('resources/freepbr/' + name + 'albedo.png'); 683 | albedo.anisotropy = maxAnisotropy; 684 | albedo.wrapS = THREE.RepeatWrapping; 685 | albedo.wrapT = THREE.RepeatWrapping; 686 | albedo.repeat.set(tiling, tiling); 687 | 688 | const normalMap = mapLoader.load('resources/freepbr/' + name + 'normal.png'); 689 | normalMap.anisotropy = maxAnisotropy; 690 | normalMap.wrapS = THREE.RepeatWrapping; 691 | normalMap.wrapT = THREE.RepeatWrapping; 692 | normalMap.repeat.set(tiling, tiling); 693 | 694 | const roughnessMap = mapLoader.load('resources/freepbr/' + name + 'roughness.png'); 695 | roughnessMap.anisotropy = maxAnisotropy; 696 | roughnessMap.wrapS = THREE.RepeatWrapping; 697 | roughnessMap.wrapT = THREE.RepeatWrapping; 698 | roughnessMap.repeat.set(tiling, tiling); 699 | 700 | const material = new THREE.MeshStandardMaterial({ 701 | metalnessMap: metalMap, 702 | map: albedo, 703 | normalMap: normalMap, 704 | roughnessMap: roughnessMap, 705 | }); 706 | 707 | return material; 708 | } 709 | 710 | initializeRenderer_() { 711 | this.threejs_ = new THREE.WebGLRenderer({ 712 | antialias: false, 713 | }); 714 | this.threejs_.shadowMap.enabled = true; 715 | this.threejs_.shadowMap.type = THREE.PCFSoftShadowMap; 716 | this.threejs_.setPixelRatio(window.devicePixelRatio); 717 | this.threejs_.setSize(window.innerWidth, window.innerHeight); 718 | this.threejs_.physicallyCorrectLights = true; 719 | this.threejs_.autoClear = false; 720 | 721 | document.body.appendChild(this.threejs_.domElement); 722 | 723 | window.addEventListener('resize', () => { 724 | this.onWindowResize_(); 725 | }, false); 726 | 727 | const fov = 60; 728 | const aspect = 1920 / 1080; 729 | const near = 1.0; 730 | const far = 1000.0; 731 | this.camera_ = new THREE.PerspectiveCamera(fov, aspect, near, far); 732 | this.camera_.position.set(-30, 2, 0); 733 | 734 | this.scene_ = new THREE.Scene(); 735 | 736 | this.uiCamera_ = new THREE.OrthographicCamera( 737 | -1, 1, 1 * aspect, -1 * aspect, 1, 1000); 738 | this.uiScene_ = new THREE.Scene(); 739 | } 740 | 741 | initializePostFX_() { 742 | const parameters = { 743 | minFilter: THREE.LinearFilter, 744 | magFilter: THREE.LinearFilter, 745 | format: THREE.RGBAFormat, 746 | type: THREE.FloatType, 747 | stencilBuffer: true, 748 | }; 749 | 750 | const renderTarget = new THREE.WebGLRenderTarget( 751 | window.innerWidth, window.innerHeight, parameters); 752 | 753 | this.composer_ = new EffectComposer(this.threejs_, renderTarget); 754 | this.composer_.setPixelRatio(window.devicePixelRatio); 755 | this.composer_.setSize(window.innerWidth, window.innerHeight); 756 | 757 | this.fxaaPass_ = new ShaderPass(FXAAShader); 758 | 759 | const uiPass = new RenderPass(this.uiScene_, this.uiCamera_); 760 | uiPass.clear = false; 761 | 762 | this.composer_.addPass(new RenderPass(this.scene_, this.camera_)); 763 | this.composer_.addPass(uiPass); 764 | this.composer_.addPass(new ShaderPass(GammaCorrectionShader)); 765 | this.composer_.addPass(this.fxaaPass_); 766 | } 767 | 768 | onWindowResize_() { 769 | this.camera_.aspect = window.innerWidth / window.innerHeight; 770 | this.camera_.updateProjectionMatrix(); 771 | 772 | this.uiCamera_.left = -this.camera_.aspect; 773 | this.uiCamera_.right = this.camera_.aspect; 774 | this.uiCamera_.updateProjectionMatrix(); 775 | 776 | this.threejs_.setSize(window.innerWidth, window.innerHeight); 777 | this.composer_.setSize(window.innerWidth, window.innerHeight); 778 | 779 | const pixelRatio = this.threejs_.getPixelRatio(); 780 | this.fxaaPass_.material.uniforms['resolution'].value.x = 1 / ( 781 | window.innerWidth * pixelRatio); 782 | this.fxaaPass_.material.uniforms['resolution'].value.y = 1 / ( 783 | window.innerHeight * pixelRatio); 784 | } 785 | 786 | raf_() { 787 | requestAnimationFrame((t) => { 788 | if (this.previousRAF_ === null) { 789 | this.previousRAF_ = t; 790 | } 791 | 792 | this.step_(t - this.previousRAF_); 793 | this.composer_.render(); 794 | 795 | this.previousRAF_ = t; 796 | this.raf_(); 797 | }); 798 | } 799 | 800 | step_(timeElapsed) { 801 | const timeElapsedS = timeElapsed * 0.001; 802 | 803 | this.fpsCamera_.update(timeElapsedS); 804 | 805 | if (this.analyzer1_) { 806 | this.indexTimer_ += timeElapsedS * 0.1; 807 | 808 | this.analyzer1Data_.push([...this.analyzer1_.getFrequencyData()]); 809 | const rows = this.speakerMeshes1_.length; 810 | if (this.analyzer1Data_.length > rows) { 811 | this.analyzer1Data_.shift(); 812 | } 813 | 814 | const colourSpline = new LinearSpline((t, a, b) => { 815 | const c = a.clone(); 816 | return c.lerp(b, t); 817 | }); 818 | colourSpline.AddPoint(0.0, new THREE.Color(0x4040FF)); 819 | colourSpline.AddPoint(0.25, new THREE.Color(0xFF4040)); 820 | colourSpline.AddPoint(1.0, new THREE.Color(0xFFFF80)); 821 | 822 | const remap = [15, 13, 11, 9, 7, 5, 3, 1, 0, 2, 4, 6, 8, 10, 12, 14]; 823 | for (let r = 0; r < this.analyzer1Data_.length; ++r) { 824 | const data = this.analyzer1Data_[r]; 825 | const speakerRow = this.speakerMeshes1_[r]; 826 | for (let i = 0; i < data.length; ++i) { 827 | const freqScale = math.smootherstep((data[remap[i]]/255) ** 0.5, 0, 1); 828 | const sc = 1 + 6 * freqScale + this.noise1_.Get(this.indexTimer_, r * 0.42142, i * 0.3455); 829 | speakerRow[i].scale.set(sc, 1, 1); 830 | speakerRow[i].material.color.copy(colourSpline.Get(freqScale)); 831 | speakerRow[i].material.emissive.copy(colourSpline.Get(freqScale)); 832 | speakerRow[i].material.emissive.multiplyScalar(freqScale ** 2); 833 | } 834 | } 835 | } 836 | 837 | if (this.analyzer2_ && this.speaker2Material_ && this.speaker2Material_.userData.shader) { 838 | this.analyzer2_.getFrequencyData(); 839 | this.speaker2Material_.userData.shader.uniforms.audioDataTexture.value = this.analyzer2Texture_; 840 | this.speaker2Material_.userData.shader.uniforms.iTime.value += timeElapsedS; 841 | this.speaker2Material_.userData.shader.uniforms.audioDataTexture.value.needsUpdate = true; 842 | } 843 | } 844 | } 845 | 846 | 847 | let _APP = null; 848 | 849 | window.addEventListener('DOMContentLoaded', () => { 850 | const _Setup = () => { 851 | _APP = new FirstPersonCameraDemo(); 852 | document.body.removeEventListener('click', _Setup); 853 | }; 854 | document.body.addEventListener('click', _Setup); 855 | }); 856 | -------------------------------------------------------------------------------- /math.js: -------------------------------------------------------------------------------- 1 | export const math = (function() { 2 | return { 3 | rand_range: function(a, b) { 4 | return Math.random() * (b - a) + a; 5 | }, 6 | 7 | rand_normalish: function() { 8 | const r = Math.random() + Math.random() + Math.random() + Math.random(); 9 | return (r / 4.0) * 2.0 - 1; 10 | }, 11 | 12 | rand_int: function(a, b) { 13 | return Math.round(Math.random() * (b - a) + a); 14 | }, 15 | 16 | lerp: function(x, a, b) { 17 | return x * (b - a) + a; 18 | }, 19 | 20 | smoothstep: function(x, a, b) { 21 | x = x * x * (3.0 - 2.0 * x); 22 | return x * (b - a) + a; 23 | }, 24 | 25 | smootherstep: function(x, a, b) { 26 | x = x * x * x * (x * (x * 6 - 15) + 10); 27 | return x * (b - a) + a; 28 | }, 29 | 30 | clamp: function(x, a, b) { 31 | return Math.min(Math.max(x, a), b); 32 | }, 33 | 34 | sat: function(x) { 35 | return Math.min(Math.max(x, 0.0), 1.0); 36 | }, 37 | 38 | in_range: (x, a, b) => { 39 | return x >= a && x <= b; 40 | }, 41 | }; 42 | })(); 43 | -------------------------------------------------------------------------------- /noise.js: -------------------------------------------------------------------------------- 1 | import {simplex} from './simplex-noise.js'; 2 | 3 | 4 | export const noise = (function() { 5 | 6 | class _NoiseGenerator { 7 | constructor(params) { 8 | this._params = params; 9 | this._Init(); 10 | } 11 | 12 | _Init() { 13 | this._noise = new simplex.SimplexNoise(this._params.seed); 14 | } 15 | 16 | Get(x, y, z) { 17 | const G = 2.0 ** (-this._params.persistence); 18 | const xs = x / this._params.scale; 19 | const ys = y / this._params.scale; 20 | const zs = z / this._params.scale; 21 | const noiseFunc = this._noise; 22 | 23 | let amplitude = 1.0; 24 | let frequency = 1.0; 25 | let normalization = 0; 26 | let total = 0; 27 | for (let o = 0; o < this._params.octaves; o++) { 28 | const noiseValue = noiseFunc.noise3D( 29 | xs * frequency, ys * frequency, zs * frequency) * 0.5 + 0.5; 30 | 31 | total += noiseValue * amplitude; 32 | normalization += amplitude; 33 | amplitude *= G; 34 | frequency *= this._params.lacunarity; 35 | } 36 | total /= normalization; 37 | return Math.pow( 38 | total, this._params.exponentiation) * this._params.height; 39 | } 40 | } 41 | 42 | return { 43 | Noise: _NoiseGenerator 44 | } 45 | })(); 46 | -------------------------------------------------------------------------------- /resources/background-grey-dots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/background-grey-dots.png -------------------------------------------------------------------------------- /resources/crosshair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/crosshair.png -------------------------------------------------------------------------------- /resources/fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/fire.png -------------------------------------------------------------------------------- /resources/freepbr/broken_down_concrete2_Height.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/broken_down_concrete2_Height.png -------------------------------------------------------------------------------- /resources/freepbr/broken_down_concrete2_albedo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/broken_down_concrete2_albedo.png -------------------------------------------------------------------------------- /resources/freepbr/broken_down_concrete2_ao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/broken_down_concrete2_ao.png -------------------------------------------------------------------------------- /resources/freepbr/broken_down_concrete2_metallic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/broken_down_concrete2_metallic.png -------------------------------------------------------------------------------- /resources/freepbr/broken_down_concrete2_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/broken_down_concrete2_normal.png -------------------------------------------------------------------------------- /resources/freepbr/broken_down_concrete2_roughness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/broken_down_concrete2_roughness.png -------------------------------------------------------------------------------- /resources/freepbr/concrete3-albedo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/concrete3-albedo.png -------------------------------------------------------------------------------- /resources/freepbr/concrete3-metallic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/concrete3-metallic.png -------------------------------------------------------------------------------- /resources/freepbr/concrete3-normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/concrete3-normal.png -------------------------------------------------------------------------------- /resources/freepbr/concrete3-roughness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/concrete3-roughness.png -------------------------------------------------------------------------------- /resources/freepbr/flaking-plaster_albedo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/flaking-plaster_albedo.png -------------------------------------------------------------------------------- /resources/freepbr/flaking-plaster_ao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/flaking-plaster_ao.png -------------------------------------------------------------------------------- /resources/freepbr/flaking-plaster_metallic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/flaking-plaster_metallic.png -------------------------------------------------------------------------------- /resources/freepbr/flaking-plaster_normal-ogl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/flaking-plaster_normal-ogl.png -------------------------------------------------------------------------------- /resources/freepbr/flaking-plaster_roughness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/flaking-plaster_roughness.png -------------------------------------------------------------------------------- /resources/freepbr/readme.txt: -------------------------------------------------------------------------------- 1 | These textures were all taken from https://freepbr.com/ 2 | 3 | -------------------------------------------------------------------------------- /resources/freepbr/rustediron2_albedo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/rustediron2_albedo.png -------------------------------------------------------------------------------- /resources/freepbr/rustediron2_metallic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/rustediron2_metallic.png -------------------------------------------------------------------------------- /resources/freepbr/rustediron2_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/rustediron2_normal.png -------------------------------------------------------------------------------- /resources/freepbr/rustediron2_roughness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/rustediron2_roughness.png -------------------------------------------------------------------------------- /resources/freepbr/worn_metal4_Height.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/worn_metal4_Height.png -------------------------------------------------------------------------------- /resources/freepbr/worn_metal4_albedo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/worn_metal4_albedo.png -------------------------------------------------------------------------------- /resources/freepbr/worn_metal4_ao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/worn_metal4_ao.png -------------------------------------------------------------------------------- /resources/freepbr/worn_metal4_metallic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/worn_metal4_metallic.png -------------------------------------------------------------------------------- /resources/freepbr/worn_metal4_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/worn_metal4_normal.png -------------------------------------------------------------------------------- /resources/freepbr/worn_metal4_preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/worn_metal4_preview.jpg -------------------------------------------------------------------------------- /resources/freepbr/worn_metal4_roughness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/freepbr/worn_metal4_roughness.png -------------------------------------------------------------------------------- /resources/music/AcousticRock.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/music/AcousticRock.mp3 -------------------------------------------------------------------------------- /resources/music/Ectoplasm.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/music/Ectoplasm.mp3 -------------------------------------------------------------------------------- /resources/music/readme.txt: -------------------------------------------------------------------------------- 1 | Music: https://audionautix.com/ 2 | -------------------------------------------------------------------------------- /resources/skybox/negx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/skybox/negx.jpg -------------------------------------------------------------------------------- /resources/skybox/negy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/skybox/negy.jpg -------------------------------------------------------------------------------- /resources/skybox/negz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/skybox/negz.jpg -------------------------------------------------------------------------------- /resources/skybox/posx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/skybox/posx.jpg -------------------------------------------------------------------------------- /resources/skybox/posy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/skybox/posy.jpg -------------------------------------------------------------------------------- /resources/skybox/posz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/skybox/posz.jpg -------------------------------------------------------------------------------- /resources/skybox/readme.txt: -------------------------------------------------------------------------------- 1 | Author 2 | ====== 3 | 4 | This is the work of Emil Persson, aka Humus. 5 | http://www.humus.name 6 | 7 | 8 | 9 | License 10 | ======= 11 | 12 | This work is licensed under a Creative Commons Attribution 3.0 Unported License. 13 | http://creativecommons.org/licenses/by/3.0/ 14 | -------------------------------------------------------------------------------- /resources/speaker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simondevyoutube/ThreeJS_Tutorial_3DSound/80fc5ad21f72c6b4d54cc388ff00228aa4d2253a/resources/speaker.png -------------------------------------------------------------------------------- /simplex-noise.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A fast javascript implementation of simplex noise by Jonas Wagner 3 | 4 | Based on a speed-improved simplex noise algorithm for 2D, 3D and 4D in Java. 5 | Which is based on example code by Stefan Gustavson (stegu@itn.liu.se). 6 | With Optimisations by Peter Eastman (peastman@drizzle.stanford.edu). 7 | Better rank ordering method by Stefan Gustavson in 2012. 8 | 9 | 10 | Copyright (c) 2018 Jonas Wagner 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal 14 | in the Software without restriction, including without limitation the rights 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | copies of the Software, and to permit persons to whom the Software is 17 | furnished to do so, subject to the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be included in all 20 | copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | SOFTWARE. 29 | */ 30 | // (function() { 31 | 32 | export const simplex = (function() { 33 | 34 | 'use strict'; 35 | 36 | var F2 = 0.5 * (Math.sqrt(3.0) - 1.0); 37 | var G2 = (3.0 - Math.sqrt(3.0)) / 6.0; 38 | var F3 = 1.0 / 3.0; 39 | var G3 = 1.0 / 6.0; 40 | var F4 = (Math.sqrt(5.0) - 1.0) / 4.0; 41 | var G4 = (5.0 - Math.sqrt(5.0)) / 20.0; 42 | 43 | function SimplexNoise(randomOrSeed) { 44 | var random; 45 | if (typeof randomOrSeed == 'function') { 46 | random = randomOrSeed; 47 | } 48 | else if (randomOrSeed) { 49 | random = alea(randomOrSeed); 50 | } else { 51 | random = Math.random; 52 | } 53 | this.p = buildPermutationTable(random); 54 | this.perm = new Uint8Array(512); 55 | this.permMod12 = new Uint8Array(512); 56 | for (var i = 0; i < 512; i++) { 57 | this.perm[i] = this.p[i & 255]; 58 | this.permMod12[i] = this.perm[i] % 12; 59 | } 60 | 61 | } 62 | SimplexNoise.prototype = { 63 | grad3: new Float32Array([1, 1, 0, 64 | -1, 1, 0, 65 | 1, -1, 0, 66 | 67 | -1, -1, 0, 68 | 1, 0, 1, 69 | -1, 0, 1, 70 | 71 | 1, 0, -1, 72 | -1, 0, -1, 73 | 0, 1, 1, 74 | 75 | 0, -1, 1, 76 | 0, 1, -1, 77 | 0, -1, -1]), 78 | grad4: new Float32Array([0, 1, 1, 1, 0, 1, 1, -1, 0, 1, -1, 1, 0, 1, -1, -1, 79 | 0, -1, 1, 1, 0, -1, 1, -1, 0, -1, -1, 1, 0, -1, -1, -1, 80 | 1, 0, 1, 1, 1, 0, 1, -1, 1, 0, -1, 1, 1, 0, -1, -1, 81 | -1, 0, 1, 1, -1, 0, 1, -1, -1, 0, -1, 1, -1, 0, -1, -1, 82 | 1, 1, 0, 1, 1, 1, 0, -1, 1, -1, 0, 1, 1, -1, 0, -1, 83 | -1, 1, 0, 1, -1, 1, 0, -1, -1, -1, 0, 1, -1, -1, 0, -1, 84 | 1, 1, 1, 0, 1, 1, -1, 0, 1, -1, 1, 0, 1, -1, -1, 0, 85 | -1, 1, 1, 0, -1, 1, -1, 0, -1, -1, 1, 0, -1, -1, -1, 0]), 86 | noise2D: function(xin, yin) { 87 | var permMod12 = this.permMod12; 88 | var perm = this.perm; 89 | var grad3 = this.grad3; 90 | var n0 = 0; // Noise contributions from the three corners 91 | var n1 = 0; 92 | var n2 = 0; 93 | // Skew the input space to determine which simplex cell we're in 94 | var s = (xin + yin) * F2; // Hairy factor for 2D 95 | var i = Math.floor(xin + s); 96 | var j = Math.floor(yin + s); 97 | var t = (i + j) * G2; 98 | var X0 = i - t; // Unskew the cell origin back to (x,y) space 99 | var Y0 = j - t; 100 | var x0 = xin - X0; // The x,y distances from the cell origin 101 | var y0 = yin - Y0; 102 | // For the 2D case, the simplex shape is an equilateral triangle. 103 | // Determine which simplex we are in. 104 | var i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords 105 | if (x0 > y0) { 106 | i1 = 1; 107 | j1 = 0; 108 | } // lower triangle, XY order: (0,0)->(1,0)->(1,1) 109 | else { 110 | i1 = 0; 111 | j1 = 1; 112 | } // upper triangle, YX order: (0,0)->(0,1)->(1,1) 113 | // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and 114 | // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where 115 | // c = (3-sqrt(3))/6 116 | var x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords 117 | var y1 = y0 - j1 + G2; 118 | var x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords 119 | var y2 = y0 - 1.0 + 2.0 * G2; 120 | // Work out the hashed gradient indices of the three simplex corners 121 | var ii = i & 255; 122 | var jj = j & 255; 123 | // Calculate the contribution from the three corners 124 | var t0 = 0.5 - x0 * x0 - y0 * y0; 125 | if (t0 >= 0) { 126 | var gi0 = permMod12[ii + perm[jj]] * 3; 127 | t0 *= t0; 128 | n0 = t0 * t0 * (grad3[gi0] * x0 + grad3[gi0 + 1] * y0); // (x,y) of grad3 used for 2D gradient 129 | } 130 | var t1 = 0.5 - x1 * x1 - y1 * y1; 131 | if (t1 >= 0) { 132 | var gi1 = permMod12[ii + i1 + perm[jj + j1]] * 3; 133 | t1 *= t1; 134 | n1 = t1 * t1 * (grad3[gi1] * x1 + grad3[gi1 + 1] * y1); 135 | } 136 | var t2 = 0.5 - x2 * x2 - y2 * y2; 137 | if (t2 >= 0) { 138 | var gi2 = permMod12[ii + 1 + perm[jj + 1]] * 3; 139 | t2 *= t2; 140 | n2 = t2 * t2 * (grad3[gi2] * x2 + grad3[gi2 + 1] * y2); 141 | } 142 | // Add contributions from each corner to get the final noise value. 143 | // The result is scaled to return values in the interval [-1,1]. 144 | return 70.0 * (n0 + n1 + n2); 145 | }, 146 | // 3D simplex noise 147 | noise3D: function(xin, yin, zin) { 148 | var permMod12 = this.permMod12; 149 | var perm = this.perm; 150 | var grad3 = this.grad3; 151 | var n0, n1, n2, n3; // Noise contributions from the four corners 152 | // Skew the input space to determine which simplex cell we're in 153 | var s = (xin + yin + zin) * F3; // Very nice and simple skew factor for 3D 154 | var i = Math.floor(xin + s); 155 | var j = Math.floor(yin + s); 156 | var k = Math.floor(zin + s); 157 | var t = (i + j + k) * G3; 158 | var X0 = i - t; // Unskew the cell origin back to (x,y,z) space 159 | var Y0 = j - t; 160 | var Z0 = k - t; 161 | var x0 = xin - X0; // The x,y,z distances from the cell origin 162 | var y0 = yin - Y0; 163 | var z0 = zin - Z0; 164 | // For the 3D case, the simplex shape is a slightly irregular tetrahedron. 165 | // Determine which simplex we are in. 166 | var i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords 167 | var i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords 168 | if (x0 >= y0) { 169 | if (y0 >= z0) { 170 | i1 = 1; 171 | j1 = 0; 172 | k1 = 0; 173 | i2 = 1; 174 | j2 = 1; 175 | k2 = 0; 176 | } // X Y Z order 177 | else if (x0 >= z0) { 178 | i1 = 1; 179 | j1 = 0; 180 | k1 = 0; 181 | i2 = 1; 182 | j2 = 0; 183 | k2 = 1; 184 | } // X Z Y order 185 | else { 186 | i1 = 0; 187 | j1 = 0; 188 | k1 = 1; 189 | i2 = 1; 190 | j2 = 0; 191 | k2 = 1; 192 | } // Z X Y order 193 | } 194 | else { // x0 y0) rankx++; 301 | else ranky++; 302 | if (x0 > z0) rankx++; 303 | else rankz++; 304 | if (x0 > w0) rankx++; 305 | else rankw++; 306 | if (y0 > z0) ranky++; 307 | else rankz++; 308 | if (y0 > w0) ranky++; 309 | else rankw++; 310 | if (z0 > w0) rankz++; 311 | else rankw++; 312 | var i1, j1, k1, l1; // The integer offsets for the second simplex corner 313 | var i2, j2, k2, l2; // The integer offsets for the third simplex corner 314 | var i3, j3, k3, l3; // The integer offsets for the fourth simplex corner 315 | // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. 316 | // Many values of c will never occur, since e.g. x>y>z>w makes x= 3 ? 1 : 0; 321 | j1 = ranky >= 3 ? 1 : 0; 322 | k1 = rankz >= 3 ? 1 : 0; 323 | l1 = rankw >= 3 ? 1 : 0; 324 | // Rank 2 denotes the second largest coordinate. 325 | i2 = rankx >= 2 ? 1 : 0; 326 | j2 = ranky >= 2 ? 1 : 0; 327 | k2 = rankz >= 2 ? 1 : 0; 328 | l2 = rankw >= 2 ? 1 : 0; 329 | // Rank 1 denotes the second smallest coordinate. 330 | i3 = rankx >= 1 ? 1 : 0; 331 | j3 = ranky >= 1 ? 1 : 0; 332 | k3 = rankz >= 1 ? 1 : 0; 333 | l3 = rankw >= 1 ? 1 : 0; 334 | // The fifth corner has all coordinate offsets = 1, so no need to compute that. 335 | var x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords 336 | var y1 = y0 - j1 + G4; 337 | var z1 = z0 - k1 + G4; 338 | var w1 = w0 - l1 + G4; 339 | var x2 = x0 - i2 + 2.0 * G4; // Offsets for third corner in (x,y,z,w) coords 340 | var y2 = y0 - j2 + 2.0 * G4; 341 | var z2 = z0 - k2 + 2.0 * G4; 342 | var w2 = w0 - l2 + 2.0 * G4; 343 | var x3 = x0 - i3 + 3.0 * G4; // Offsets for fourth corner in (x,y,z,w) coords 344 | var y3 = y0 - j3 + 3.0 * G4; 345 | var z3 = z0 - k3 + 3.0 * G4; 346 | var w3 = w0 - l3 + 3.0 * G4; 347 | var x4 = x0 - 1.0 + 4.0 * G4; // Offsets for last corner in (x,y,z,w) coords 348 | var y4 = y0 - 1.0 + 4.0 * G4; 349 | var z4 = z0 - 1.0 + 4.0 * G4; 350 | var w4 = w0 - 1.0 + 4.0 * G4; 351 | // Work out the hashed gradient indices of the five simplex corners 352 | var ii = i & 255; 353 | var jj = j & 255; 354 | var kk = k & 255; 355 | var ll = l & 255; 356 | // Calculate the contribution from the five corners 357 | var t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0; 358 | if (t0 < 0) n0 = 0.0; 359 | else { 360 | var gi0 = (perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32) * 4; 361 | t0 *= t0; 362 | n0 = t0 * t0 * (grad4[gi0] * x0 + grad4[gi0 + 1] * y0 + grad4[gi0 + 2] * z0 + grad4[gi0 + 3] * w0); 363 | } 364 | var t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1; 365 | if (t1 < 0) n1 = 0.0; 366 | else { 367 | var gi1 = (perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32) * 4; 368 | t1 *= t1; 369 | n1 = t1 * t1 * (grad4[gi1] * x1 + grad4[gi1 + 1] * y1 + grad4[gi1 + 2] * z1 + grad4[gi1 + 3] * w1); 370 | } 371 | var t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2; 372 | if (t2 < 0) n2 = 0.0; 373 | else { 374 | var gi2 = (perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32) * 4; 375 | t2 *= t2; 376 | n2 = t2 * t2 * (grad4[gi2] * x2 + grad4[gi2 + 1] * y2 + grad4[gi2 + 2] * z2 + grad4[gi2 + 3] * w2); 377 | } 378 | var t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3; 379 | if (t3 < 0) n3 = 0.0; 380 | else { 381 | var gi3 = (perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32) * 4; 382 | t3 *= t3; 383 | n3 = t3 * t3 * (grad4[gi3] * x3 + grad4[gi3 + 1] * y3 + grad4[gi3 + 2] * z3 + grad4[gi3 + 3] * w3); 384 | } 385 | var t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4; 386 | if (t4 < 0) n4 = 0.0; 387 | else { 388 | var gi4 = (perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32) * 4; 389 | t4 *= t4; 390 | n4 = t4 * t4 * (grad4[gi4] * x4 + grad4[gi4 + 1] * y4 + grad4[gi4 + 2] * z4 + grad4[gi4 + 3] * w4); 391 | } 392 | // Sum up and scale the result to cover the range [-1,1] 393 | return 27.0 * (n0 + n1 + n2 + n3 + n4); 394 | } 395 | }; 396 | 397 | function buildPermutationTable(random) { 398 | var i; 399 | var p = new Uint8Array(256); 400 | for (i = 0; i < 256; i++) { 401 | p[i] = i; 402 | } 403 | for (i = 0; i < 255; i++) { 404 | var r = i + ~~(random() * (256 - i)); 405 | var aux = p[i]; 406 | p[i] = p[r]; 407 | p[r] = aux; 408 | } 409 | return p; 410 | } 411 | SimplexNoise._buildPermutationTable = buildPermutationTable; 412 | 413 | function alea() { 414 | // Johannes Baagøe , 2010 415 | var s0 = 0; 416 | var s1 = 0; 417 | var s2 = 0; 418 | var c = 1; 419 | 420 | var mash = masher(); 421 | s0 = mash(' '); 422 | s1 = mash(' '); 423 | s2 = mash(' '); 424 | 425 | for (var i = 0; i < arguments.length; i++) { 426 | s0 -= mash(arguments[i]); 427 | if (s0 < 0) { 428 | s0 += 1; 429 | } 430 | s1 -= mash(arguments[i]); 431 | if (s1 < 0) { 432 | s1 += 1; 433 | } 434 | s2 -= mash(arguments[i]); 435 | if (s2 < 0) { 436 | s2 += 1; 437 | } 438 | } 439 | mash = null; 440 | return function() { 441 | var t = 2091639 * s0 + c * 2.3283064365386963e-10; // 2^-32 442 | s0 = s1; 443 | s1 = s2; 444 | return s2 = t - (c = t | 0); 445 | }; 446 | } 447 | function masher() { 448 | var n = 0xefc8249d; 449 | return function(data) { 450 | data = data.toString(); 451 | for (var i = 0; i < data.length; i++) { 452 | n += data.charCodeAt(i); 453 | var h = 0.02519603282416938 * n; 454 | n = h >>> 0; 455 | h -= n; 456 | h *= n; 457 | n = h >>> 0; 458 | h -= n; 459 | n += h * 0x100000000; // 2^32 460 | } 461 | return (n >>> 0) * 2.3283064365386963e-10; // 2^-32 462 | }; 463 | } 464 | 465 | // // amd 466 | // if (typeof define !== 'undefined' && define.amd) define(function() {return SimplexNoise;}); 467 | // // common js 468 | // if (typeof exports !== 'undefined') exports.SimplexNoise = SimplexNoise; 469 | // // browser 470 | // else if (typeof window !== 'undefined') window.SimplexNoise = SimplexNoise; 471 | // // nodejs 472 | // if (typeof module !== 'undefined') { 473 | // module.exports = SimplexNoise; 474 | // } 475 | return { 476 | SimplexNoise: SimplexNoise 477 | }; 478 | 479 | })(); --------------------------------------------------------------------------------