├── README.md
├── index.html
└── js
├── OrbitControls.js
└── perlin.js
/README.md:
--------------------------------------------------------------------------------
1 | # Procedural Generation
2 |
3 | Procedural generation is a tool that is extremely useful in creating many different kinds of content. The core idea is to create a generative program and feed it random values as parameters. It can be tricky to constrain the influence of the random values while maintaining good variety, but doing it right can produce an endless amount of effects that would be impossible to create by hand!
4 |
5 | So how does it work?
6 |
7 | At the base of any random number generator is a hash function. The image below links to an interactive implementation of a simple alias-based hash function in desmos. Note that this is a different design than hashes used in cryptography, but serves the same purpose of scrambling input into an seemingly random output. The demo then goes on to show how the hash function can be used to create "fractal noise"
8 |
9 | [
](https://www.desmos.com/calculator/dj2j2slyhl)
10 |
11 | Below is an example of a 2D random number generator implemented for the GPU with a shader, and explores a few transformations to create different distributions. This is very efficient and is an essential building block of many effects. This also can be replaced with a pre-generated random texture, which can be used in the exact same way with perhaps less computational cost. The 2D noise is easily interpreted as an image, and looks like the screen of a TV with no antenna.
12 |
13 | [
](https://www.shadertoy.com/view/4ssXRX)
14 |
15 | Here is 3D noise, smoothed and layered at multiple frequencies (as in the desmos example) to synthesize interesting textures on the fly.
16 |
17 | [
](https://www.shadertoy.com/view/4sc3z2)
18 |
19 | From this point on it is okay to treat random number generators as a black-box. Understanding how they work is not necessary to use them, just fun.
20 |
21 | This music visualizer demonstrates a more indirect use of 3D fractal noise. Rather than simply dumping the values onto the screen, the noise is used to displace the geometry of a mesh. By mapping the amplitudes of frequencies extracted from the music onto the amplitude of each noise frequency, the geometry becomes responsive to the music in a unique way.
22 |
23 | [
](http://uwc.graphics/FBM-Triangle-Shredder3.html)
24 |
25 | The index.html file in this repository demonstrates the application of noise onto the height displacement of a plane. With just a few iterations a procedural terrain effect is possible. This is a simplified version of the way terrain generation in games like minecraft work.
26 |
27 | [
](https://computer-graphics-and-pretty-pictures.github.io/Procedural-Generation/)
28 |
29 | Inigo Quilez's famous "elevated" terrain
30 |
31 | [
](https://www.shadertoy.com/view/MdX3Rr)
32 |
33 | It turns out lots of beautiful things in nature can be synthesized by applying simple transformations to noise.
34 |
35 | [
](https://www.shadertoy.com/view/ll2SWd)
36 |
37 | [
](https://www.shadertoy.com/view/Ms2SD1)
38 |
39 | [
](https://www.shadertoy.com/view/MsVXWW)
40 |
41 | Procedural generation can be used for a lot more than just visual effects. Here is an example of randomly generated music, using some of the same principals. Every measure, a random root note from a scale is selected, and then a chord configuration is created based on that root. A melody is created at every eight note by randomly selecting from one of the following: A: random note on the scale, B: ascending scale, C: descending scale, D: the root, E: a note from the chord other than the root. The result is a surprisingly natural sounding song.
42 |
43 | [
](https://www.shadertoy.com/view/ldXBzH)
44 |
45 | This is only scratching the surface!
46 | Writing generative programs is like meta-creativity- telling the machine which parameters can vary and in what ways allows it to generate unlimited "original" content. It's definitely worth the careful tuning it can require!
47 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Procedural Surface
4 |
9 |
10 |
11 |
12 |
13 |
14 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/js/OrbitControls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author qiao / https://github.com/qiao
3 | * @author mrdoob / http://mrdoob.com
4 | * @author alteredq / http://alteredqualia.com/
5 | * @author WestLangley / http://github.com/WestLangley
6 | * @author erich666 / http://erichaines.com
7 | */
8 |
9 | // This set of controls performs orbiting, dollying (zooming), and panning.
10 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
11 | //
12 | // Orbit - left mouse / touch: one finger move
13 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
14 | // Pan - right mouse, or arrow keys / touch: three finger swipe
15 |
16 | THREE.OrbitControls = function ( object, domElement ) {
17 |
18 | this.object = object;
19 |
20 | this.domElement = ( domElement !== undefined ) ? domElement : document;
21 |
22 | // Set to false to disable this control
23 | this.enabled = true;
24 |
25 | // "target" sets the location of focus, where the object orbits around
26 | this.target = new THREE.Vector3();
27 |
28 | // How far you can dolly in and out ( PerspectiveCamera only )
29 | this.minDistance = 0;
30 | this.maxDistance = Infinity;
31 |
32 | // How far you can zoom in and out ( OrthographicCamera only )
33 | this.minZoom = 0;
34 | this.maxZoom = Infinity;
35 |
36 | // How far you can orbit vertically, upper and lower limits.
37 | // Range is 0 to Math.PI radians.
38 | this.minPolarAngle = 0; // radians
39 | this.maxPolarAngle = Math.PI; // radians
40 |
41 | // How far you can orbit horizontally, upper and lower limits.
42 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
43 | this.minAzimuthAngle = - Infinity; // radians
44 | this.maxAzimuthAngle = Infinity; // radians
45 |
46 | // Set to true to enable damping (inertia)
47 | // If damping is enabled, you must call controls.update() in your animation loop
48 | this.enableDamping = false;
49 | this.dampingFactor = 0.25;
50 |
51 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
52 | // Set to false to disable zooming
53 | this.enableZoom = true;
54 | this.zoomSpeed = 1.0;
55 |
56 | // Set to false to disable rotating
57 | this.enableRotate = true;
58 | this.rotateSpeed = 1.0;
59 |
60 | // Set to false to disable panning
61 | this.enablePan = true;
62 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push
63 |
64 | // Set to true to automatically rotate around the target
65 | // If auto-rotate is enabled, you must call controls.update() in your animation loop
66 | this.autoRotate = false;
67 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
68 |
69 | // Set to false to disable use of the keys
70 | this.enableKeys = true;
71 |
72 | // The four arrow keys
73 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
74 |
75 | // Mouse buttons
76 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT };
77 |
78 | // for reset
79 | this.target0 = this.target.clone();
80 | this.position0 = this.object.position.clone();
81 | this.zoom0 = this.object.zoom;
82 |
83 | //
84 | // public methods
85 | //
86 |
87 | this.getPolarAngle = function () {
88 |
89 | return spherical.phi;
90 |
91 | };
92 |
93 | this.getAzimuthalAngle = function () {
94 |
95 | return spherical.theta;
96 |
97 | };
98 |
99 | this.reset = function () {
100 |
101 | scope.target.copy( scope.target0 );
102 | scope.object.position.copy( scope.position0 );
103 | scope.object.zoom = scope.zoom0;
104 |
105 | scope.object.updateProjectionMatrix();
106 | scope.dispatchEvent( changeEvent );
107 |
108 | scope.update();
109 |
110 | state = STATE.NONE;
111 |
112 | };
113 |
114 | // this method is exposed, but perhaps it would be better if we can make it private...
115 | this.update = function () {
116 |
117 | var offset = new THREE.Vector3();
118 |
119 | // so camera.up is the orbit axis
120 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
121 | var quatInverse = quat.clone().inverse();
122 |
123 | var lastPosition = new THREE.Vector3();
124 | var lastQuaternion = new THREE.Quaternion();
125 |
126 | return function update() {
127 |
128 | var position = scope.object.position;
129 |
130 | offset.copy( position ).sub( scope.target );
131 |
132 | // rotate offset to "y-axis-is-up" space
133 | offset.applyQuaternion( quat );
134 |
135 | // angle from z-axis around y-axis
136 | spherical.setFromVector3( offset );
137 |
138 | if ( scope.autoRotate && state === STATE.NONE ) {
139 |
140 | rotateLeft( getAutoRotationAngle() );
141 |
142 | }
143 |
144 | spherical.theta += sphericalDelta.theta;
145 | spherical.phi += sphericalDelta.phi;
146 |
147 | // restrict theta to be between desired limits
148 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );
149 |
150 | // restrict phi to be between desired limits
151 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
152 |
153 | spherical.makeSafe();
154 |
155 |
156 | spherical.radius *= scale;
157 |
158 | // restrict radius to be between desired limits
159 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
160 |
161 | // move target to panned location
162 | scope.target.add( panOffset );
163 |
164 | offset.setFromSpherical( spherical );
165 |
166 | // rotate offset back to "camera-up-vector-is-up" space
167 | offset.applyQuaternion( quatInverse );
168 |
169 | position.copy( scope.target ).add( offset );
170 |
171 | scope.object.lookAt( scope.target );
172 |
173 | if ( scope.enableDamping === true ) {
174 |
175 | sphericalDelta.theta *= ( 1 - scope.dampingFactor );
176 | sphericalDelta.phi *= ( 1 - scope.dampingFactor );
177 |
178 | } else {
179 |
180 | sphericalDelta.set( 0, 0, 0 );
181 |
182 | }
183 |
184 | scale = 1;
185 | panOffset.set( 0, 0, 0 );
186 |
187 | // update condition is:
188 | // min(camera displacement, camera rotation in radians)^2 > EPS
189 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8
190 |
191 | if ( zoomChanged ||
192 | lastPosition.distanceToSquared( scope.object.position ) > EPS ||
193 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
194 |
195 | scope.dispatchEvent( changeEvent );
196 |
197 | lastPosition.copy( scope.object.position );
198 | lastQuaternion.copy( scope.object.quaternion );
199 | zoomChanged = false;
200 |
201 | return true;
202 |
203 | }
204 |
205 | return false;
206 |
207 | };
208 |
209 | }();
210 |
211 | this.dispose = function () {
212 |
213 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
214 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
215 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );
216 |
217 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
218 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
219 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );
220 |
221 | document.removeEventListener( 'mousemove', onMouseMove, false );
222 | document.removeEventListener( 'mouseup', onMouseUp, false );
223 |
224 | window.removeEventListener( 'keydown', onKeyDown, false );
225 |
226 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
227 |
228 | };
229 |
230 | //
231 | // internals
232 | //
233 |
234 | var scope = this;
235 |
236 | var changeEvent = { type: 'change' };
237 | var startEvent = { type: 'start' };
238 | var endEvent = { type: 'end' };
239 |
240 | var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 };
241 |
242 | var state = STATE.NONE;
243 |
244 | var EPS = 0.000001;
245 |
246 | // current position in spherical coordinates
247 | var spherical = new THREE.Spherical();
248 | var sphericalDelta = new THREE.Spherical();
249 |
250 | var scale = 1;
251 | var panOffset = new THREE.Vector3();
252 | var zoomChanged = false;
253 |
254 | var rotateStart = new THREE.Vector2();
255 | var rotateEnd = new THREE.Vector2();
256 | var rotateDelta = new THREE.Vector2();
257 |
258 | var panStart = new THREE.Vector2();
259 | var panEnd = new THREE.Vector2();
260 | var panDelta = new THREE.Vector2();
261 |
262 | var dollyStart = new THREE.Vector2();
263 | var dollyEnd = new THREE.Vector2();
264 | var dollyDelta = new THREE.Vector2();
265 |
266 | function getAutoRotationAngle() {
267 |
268 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
269 |
270 | }
271 |
272 | function getZoomScale() {
273 |
274 | return Math.pow( 0.95, scope.zoomSpeed );
275 |
276 | }
277 |
278 | function rotateLeft( angle ) {
279 |
280 | sphericalDelta.theta -= angle;
281 |
282 | }
283 |
284 | function rotateUp( angle ) {
285 |
286 | sphericalDelta.phi -= angle;
287 |
288 | }
289 |
290 | var panLeft = function () {
291 |
292 | var v = new THREE.Vector3();
293 |
294 | return function panLeft( distance, objectMatrix ) {
295 |
296 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
297 | v.multiplyScalar( - distance );
298 |
299 | panOffset.add( v );
300 |
301 | };
302 |
303 | }();
304 |
305 | var panUp = function () {
306 |
307 | var v = new THREE.Vector3();
308 |
309 | return function panUp( distance, objectMatrix ) {
310 |
311 | v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix
312 | v.multiplyScalar( distance );
313 |
314 | panOffset.add( v );
315 |
316 | };
317 |
318 | }();
319 |
320 | // deltaX and deltaY are in pixels; right and down are positive
321 | var pan = function () {
322 |
323 | var offset = new THREE.Vector3();
324 |
325 | return function pan( deltaX, deltaY ) {
326 |
327 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
328 |
329 | if ( scope.object instanceof THREE.PerspectiveCamera ) {
330 |
331 | // perspective
332 | var position = scope.object.position;
333 | offset.copy( position ).sub( scope.target );
334 | var targetDistance = offset.length();
335 |
336 | // half of the fov is center to top of screen
337 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
338 |
339 | // we actually don't use screenWidth, since perspective camera is fixed to screen height
340 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
341 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
342 |
343 | } else if ( scope.object instanceof THREE.OrthographicCamera ) {
344 |
345 | // orthographic
346 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
347 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
348 |
349 | } else {
350 |
351 | // camera neither orthographic nor perspective
352 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
353 | scope.enablePan = false;
354 |
355 | }
356 |
357 | };
358 |
359 | }();
360 |
361 | function dollyIn( dollyScale ) {
362 |
363 | if ( scope.object instanceof THREE.PerspectiveCamera ) {
364 |
365 | scale /= dollyScale;
366 |
367 | } else if ( scope.object instanceof THREE.OrthographicCamera ) {
368 |
369 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
370 | scope.object.updateProjectionMatrix();
371 | zoomChanged = true;
372 |
373 | } else {
374 |
375 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
376 | scope.enableZoom = false;
377 |
378 | }
379 |
380 | }
381 |
382 | function dollyOut( dollyScale ) {
383 |
384 | if ( scope.object instanceof THREE.PerspectiveCamera ) {
385 |
386 | scale *= dollyScale;
387 |
388 | } else if ( scope.object instanceof THREE.OrthographicCamera ) {
389 |
390 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
391 | scope.object.updateProjectionMatrix();
392 | zoomChanged = true;
393 |
394 | } else {
395 |
396 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
397 | scope.enableZoom = false;
398 |
399 | }
400 |
401 | }
402 |
403 | //
404 | // event callbacks - update the object state
405 | //
406 |
407 | function handleMouseDownRotate( event ) {
408 |
409 | //console.log( 'handleMouseDownRotate' );
410 |
411 | rotateStart.set( event.clientX, event.clientY );
412 |
413 | }
414 |
415 | function handleMouseDownDolly( event ) {
416 |
417 | //console.log( 'handleMouseDownDolly' );
418 |
419 | dollyStart.set( event.clientX, event.clientY );
420 |
421 | }
422 |
423 | function handleMouseDownPan( event ) {
424 |
425 | //console.log( 'handleMouseDownPan' );
426 |
427 | panStart.set( event.clientX, event.clientY );
428 |
429 | }
430 |
431 | function handleMouseMoveRotate( event ) {
432 |
433 | //console.log( 'handleMouseMoveRotate' );
434 |
435 | rotateEnd.set( event.clientX, event.clientY );
436 | rotateDelta.subVectors( rotateEnd, rotateStart );
437 |
438 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
439 |
440 | // rotating across whole screen goes 360 degrees around
441 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
442 |
443 | // rotating up and down along whole screen attempts to go 360, but limited to 180
444 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
445 |
446 | rotateStart.copy( rotateEnd );
447 |
448 | scope.update();
449 |
450 | }
451 |
452 | function handleMouseMoveDolly( event ) {
453 |
454 | //console.log( 'handleMouseMoveDolly' );
455 |
456 | dollyEnd.set( event.clientX, event.clientY );
457 |
458 | dollyDelta.subVectors( dollyEnd, dollyStart );
459 |
460 | if ( dollyDelta.y > 0 ) {
461 |
462 | dollyIn( getZoomScale() );
463 |
464 | } else if ( dollyDelta.y < 0 ) {
465 |
466 | dollyOut( getZoomScale() );
467 |
468 | }
469 |
470 | dollyStart.copy( dollyEnd );
471 |
472 | scope.update();
473 |
474 | }
475 |
476 | function handleMouseMovePan( event ) {
477 |
478 | //console.log( 'handleMouseMovePan' );
479 |
480 | panEnd.set( event.clientX, event.clientY );
481 |
482 | panDelta.subVectors( panEnd, panStart );
483 |
484 | pan( panDelta.x, panDelta.y );
485 |
486 | panStart.copy( panEnd );
487 |
488 | scope.update();
489 |
490 | }
491 |
492 | function handleMouseUp( event ) {
493 |
494 | // console.log( 'handleMouseUp' );
495 |
496 | }
497 |
498 | function handleMouseWheel( event ) {
499 |
500 | // console.log( 'handleMouseWheel' );
501 |
502 | if ( event.deltaY < 0 ) {
503 |
504 | dollyOut( getZoomScale() );
505 |
506 | } else if ( event.deltaY > 0 ) {
507 |
508 | dollyIn( getZoomScale() );
509 |
510 | }
511 |
512 | scope.update();
513 |
514 | }
515 |
516 | function handleKeyDown( event ) {
517 |
518 | //console.log( 'handleKeyDown' );
519 |
520 | switch ( event.keyCode ) {
521 |
522 | case scope.keys.UP:
523 | pan( 0, scope.keyPanSpeed );
524 | scope.update();
525 | break;
526 |
527 | case scope.keys.BOTTOM:
528 | pan( 0, - scope.keyPanSpeed );
529 | scope.update();
530 | break;
531 |
532 | case scope.keys.LEFT:
533 | pan( scope.keyPanSpeed, 0 );
534 | scope.update();
535 | break;
536 |
537 | case scope.keys.RIGHT:
538 | pan( - scope.keyPanSpeed, 0 );
539 | scope.update();
540 | break;
541 |
542 | }
543 |
544 | }
545 |
546 | function handleTouchStartRotate( event ) {
547 |
548 | //console.log( 'handleTouchStartRotate' );
549 |
550 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
551 |
552 | }
553 |
554 | function handleTouchStartDolly( event ) {
555 |
556 | //console.log( 'handleTouchStartDolly' );
557 |
558 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
559 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
560 |
561 | var distance = Math.sqrt( dx * dx + dy * dy );
562 |
563 | dollyStart.set( 0, distance );
564 |
565 | }
566 |
567 | function handleTouchStartPan( event ) {
568 |
569 | //console.log( 'handleTouchStartPan' );
570 |
571 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
572 |
573 | }
574 |
575 | function handleTouchMoveRotate( event ) {
576 |
577 | //console.log( 'handleTouchMoveRotate' );
578 |
579 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
580 | rotateDelta.subVectors( rotateEnd, rotateStart );
581 |
582 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
583 |
584 | // rotating across whole screen goes 360 degrees around
585 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
586 |
587 | // rotating up and down along whole screen attempts to go 360, but limited to 180
588 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
589 |
590 | rotateStart.copy( rotateEnd );
591 |
592 | scope.update();
593 |
594 | }
595 |
596 | function handleTouchMoveDolly( event ) {
597 |
598 | //console.log( 'handleTouchMoveDolly' );
599 |
600 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
601 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
602 |
603 | var distance = Math.sqrt( dx * dx + dy * dy );
604 |
605 | dollyEnd.set( 0, distance );
606 |
607 | dollyDelta.subVectors( dollyEnd, dollyStart );
608 |
609 | if ( dollyDelta.y > 0 ) {
610 |
611 | dollyOut( getZoomScale() );
612 |
613 | } else if ( dollyDelta.y < 0 ) {
614 |
615 | dollyIn( getZoomScale() );
616 |
617 | }
618 |
619 | dollyStart.copy( dollyEnd );
620 |
621 | scope.update();
622 |
623 | }
624 |
625 | function handleTouchMovePan( event ) {
626 |
627 | //console.log( 'handleTouchMovePan' );
628 |
629 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
630 |
631 | panDelta.subVectors( panEnd, panStart );
632 |
633 | pan( panDelta.x, panDelta.y );
634 |
635 | panStart.copy( panEnd );
636 |
637 | scope.update();
638 |
639 | }
640 |
641 | function handleTouchEnd( event ) {
642 |
643 | //console.log( 'handleTouchEnd' );
644 |
645 | }
646 |
647 | //
648 | // event handlers - FSM: listen for events and reset state
649 | //
650 |
651 | function onMouseDown( event ) {
652 |
653 | if ( scope.enabled === false ) return;
654 |
655 | event.preventDefault();
656 |
657 | if ( event.button === scope.mouseButtons.ORBIT ) {
658 |
659 | if ( scope.enableRotate === false ) return;
660 |
661 | handleMouseDownRotate( event );
662 |
663 | state = STATE.ROTATE;
664 |
665 | } else if ( event.button === scope.mouseButtons.ZOOM ) {
666 |
667 | if ( scope.enableZoom === false ) return;
668 |
669 | handleMouseDownDolly( event );
670 |
671 | state = STATE.DOLLY;
672 |
673 | } else if ( event.button === scope.mouseButtons.PAN ) {
674 |
675 | if ( scope.enablePan === false ) return;
676 |
677 | handleMouseDownPan( event );
678 |
679 | state = STATE.PAN;
680 |
681 | }
682 |
683 | if ( state !== STATE.NONE ) {
684 |
685 | document.addEventListener( 'mousemove', onMouseMove, false );
686 | document.addEventListener( 'mouseup', onMouseUp, false );
687 |
688 | scope.dispatchEvent( startEvent );
689 |
690 | }
691 |
692 | }
693 |
694 | function onMouseMove( event ) {
695 |
696 | if ( scope.enabled === false ) return;
697 |
698 | event.preventDefault();
699 |
700 | if ( state === STATE.ROTATE ) {
701 |
702 | if ( scope.enableRotate === false ) return;
703 |
704 | handleMouseMoveRotate( event );
705 |
706 | } else if ( state === STATE.DOLLY ) {
707 |
708 | if ( scope.enableZoom === false ) return;
709 |
710 | handleMouseMoveDolly( event );
711 |
712 | } else if ( state === STATE.PAN ) {
713 |
714 | if ( scope.enablePan === false ) return;
715 |
716 | handleMouseMovePan( event );
717 |
718 | }
719 |
720 | }
721 |
722 | function onMouseUp( event ) {
723 |
724 | if ( scope.enabled === false ) return;
725 |
726 | handleMouseUp( event );
727 |
728 | document.removeEventListener( 'mousemove', onMouseMove, false );
729 | document.removeEventListener( 'mouseup', onMouseUp, false );
730 |
731 | scope.dispatchEvent( endEvent );
732 |
733 | state = STATE.NONE;
734 |
735 | }
736 |
737 | function onMouseWheel( event ) {
738 |
739 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;
740 |
741 | event.preventDefault();
742 | event.stopPropagation();
743 |
744 | handleMouseWheel( event );
745 |
746 | scope.dispatchEvent( startEvent ); // not sure why these are here...
747 | scope.dispatchEvent( endEvent );
748 |
749 | }
750 |
751 | function onKeyDown( event ) {
752 |
753 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
754 |
755 | handleKeyDown( event );
756 |
757 | }
758 |
759 | function onTouchStart( event ) {
760 |
761 | if ( scope.enabled === false ) return;
762 |
763 | switch ( event.touches.length ) {
764 |
765 | case 1: // one-fingered touch: rotate
766 |
767 | if ( scope.enableRotate === false ) return;
768 |
769 | handleTouchStartRotate( event );
770 |
771 | state = STATE.TOUCH_ROTATE;
772 |
773 | break;
774 |
775 | case 2: // two-fingered touch: dolly
776 |
777 | if ( scope.enableZoom === false ) return;
778 |
779 | handleTouchStartDolly( event );
780 |
781 | state = STATE.TOUCH_DOLLY;
782 |
783 | break;
784 |
785 | case 3: // three-fingered touch: pan
786 |
787 | if ( scope.enablePan === false ) return;
788 |
789 | handleTouchStartPan( event );
790 |
791 | state = STATE.TOUCH_PAN;
792 |
793 | break;
794 |
795 | default:
796 |
797 | state = STATE.NONE;
798 |
799 | }
800 |
801 | if ( state !== STATE.NONE ) {
802 |
803 | scope.dispatchEvent( startEvent );
804 |
805 | }
806 |
807 | }
808 |
809 | function onTouchMove( event ) {
810 |
811 | if ( scope.enabled === false ) return;
812 |
813 | event.preventDefault();
814 | event.stopPropagation();
815 |
816 | switch ( event.touches.length ) {
817 |
818 | case 1: // one-fingered touch: rotate
819 |
820 | if ( scope.enableRotate === false ) return;
821 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?...
822 |
823 | handleTouchMoveRotate( event );
824 |
825 | break;
826 |
827 | case 2: // two-fingered touch: dolly
828 |
829 | if ( scope.enableZoom === false ) return;
830 | if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?...
831 |
832 | handleTouchMoveDolly( event );
833 |
834 | break;
835 |
836 | case 3: // three-fingered touch: pan
837 |
838 | if ( scope.enablePan === false ) return;
839 | if ( state !== STATE.TOUCH_PAN ) return; // is this needed?...
840 |
841 | handleTouchMovePan( event );
842 |
843 | break;
844 |
845 | default:
846 |
847 | state = STATE.NONE;
848 |
849 | }
850 |
851 | }
852 |
853 | function onTouchEnd( event ) {
854 |
855 | if ( scope.enabled === false ) return;
856 |
857 | handleTouchEnd( event );
858 |
859 | scope.dispatchEvent( endEvent );
860 |
861 | state = STATE.NONE;
862 |
863 | }
864 |
865 | function onContextMenu( event ) {
866 |
867 | event.preventDefault();
868 |
869 | }
870 |
871 | //
872 |
873 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );
874 |
875 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
876 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false );
877 |
878 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false );
879 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false );
880 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false );
881 |
882 | window.addEventListener( 'keydown', onKeyDown, false );
883 |
884 | // force an update at start
885 |
886 | this.update();
887 |
888 | };
889 |
890 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
891 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;
892 |
893 | Object.defineProperties( THREE.OrbitControls.prototype, {
894 |
895 | center: {
896 |
897 | get: function () {
898 |
899 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' );
900 | return this.target;
901 |
902 | }
903 |
904 | },
905 |
906 | // backward compatibility
907 |
908 | noZoom: {
909 |
910 | get: function () {
911 |
912 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
913 | return ! this.enableZoom;
914 |
915 | },
916 |
917 | set: function ( value ) {
918 |
919 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' );
920 | this.enableZoom = ! value;
921 |
922 | }
923 |
924 | },
925 |
926 | noRotate: {
927 |
928 | get: function () {
929 |
930 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
931 | return ! this.enableRotate;
932 |
933 | },
934 |
935 | set: function ( value ) {
936 |
937 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' );
938 | this.enableRotate = ! value;
939 |
940 | }
941 |
942 | },
943 |
944 | noPan: {
945 |
946 | get: function () {
947 |
948 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
949 | return ! this.enablePan;
950 |
951 | },
952 |
953 | set: function ( value ) {
954 |
955 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' );
956 | this.enablePan = ! value;
957 |
958 | }
959 |
960 | },
961 |
962 | noKeys: {
963 |
964 | get: function () {
965 |
966 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
967 | return ! this.enableKeys;
968 |
969 | },
970 |
971 | set: function ( value ) {
972 |
973 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' );
974 | this.enableKeys = ! value;
975 |
976 | }
977 |
978 | },
979 |
980 | staticMoving: {
981 |
982 | get: function () {
983 |
984 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
985 | return ! this.enableDamping;
986 |
987 | },
988 |
989 | set: function ( value ) {
990 |
991 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' );
992 | this.enableDamping = ! value;
993 |
994 | }
995 |
996 | },
997 |
998 | dynamicDampingFactor: {
999 |
1000 | get: function () {
1001 |
1002 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
1003 | return this.dampingFactor;
1004 |
1005 | },
1006 |
1007 | set: function ( value ) {
1008 |
1009 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' );
1010 | this.dampingFactor = value;
1011 |
1012 | }
1013 |
1014 | }
1015 |
1016 | } );
1017 |
--------------------------------------------------------------------------------
/js/perlin.js:
--------------------------------------------------------------------------------
1 | /*
2 | * A speed-improved perlin and simplex noise algorithms for 2D.
3 | *
4 | * Based on example code by Stefan Gustavson (stegu@itn.liu.se).
5 | * Optimisations by Peter Eastman (peastman@drizzle.stanford.edu).
6 | * Better rank ordering method by Stefan Gustavson in 2012.
7 | * Converted to Javascript by Joseph Gentle.
8 | *
9 | * Version 2012-03-09
10 | *
11 | * This code was placed in the public domain by its original author,
12 | * Stefan Gustavson. You may use it as you see fit, but
13 | * attribution is appreciated.
14 | *
15 | */
16 |
17 | (function(global){
18 | var module = global.noise = {};
19 |
20 | function Grad(x, y, z) {
21 | this.x = x; this.y = y; this.z = z;
22 | }
23 |
24 | Grad.prototype.dot2 = function(x, y) {
25 | return this.x*x + this.y*y;
26 | };
27 |
28 | Grad.prototype.dot3 = function(x, y, z) {
29 | return this.x*x + this.y*y + this.z*z;
30 | };
31 |
32 | var grad3 = [new Grad(1,1,0),new Grad(-1,1,0),new Grad(1,-1,0),new Grad(-1,-1,0),
33 | new Grad(1,0,1),new Grad(-1,0,1),new Grad(1,0,-1),new Grad(-1,0,-1),
34 | new Grad(0,1,1),new Grad(0,-1,1),new Grad(0,1,-1),new Grad(0,-1,-1)];
35 |
36 | var p = [151,160,137,91,90,15,
37 | 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
38 | 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
39 | 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
40 | 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
41 | 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
42 | 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
43 | 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
44 | 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
45 | 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
46 | 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
47 | 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
48 | 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180];
49 | // To remove the need for index wrapping, double the permutation table length
50 | var perm = new Array(512);
51 | var gradP = new Array(512);
52 |
53 | // This isn't a very good seeding function, but it works ok. It supports 2^16
54 | // different seed values. Write something better if you need more seeds.
55 | module.seed = function(seed) {
56 | if(seed > 0 && seed < 1) {
57 | // Scale the seed out
58 | seed *= 65536;
59 | }
60 |
61 | seed = Math.floor(seed);
62 | if(seed < 256) {
63 | seed |= seed << 8;
64 | }
65 |
66 | for(var i = 0; i < 256; i++) {
67 | var v;
68 | if (i & 1) {
69 | v = p[i] ^ (seed & 255);
70 | } else {
71 | v = p[i] ^ ((seed>>8) & 255);
72 | }
73 |
74 | perm[i] = perm[i + 256] = v;
75 | gradP[i] = gradP[i + 256] = grad3[v % 12];
76 | }
77 | };
78 |
79 | module.seed(0);
80 |
81 | /*
82 | for(var i=0; i<256; i++) {
83 | perm[i] = perm[i + 256] = p[i];
84 | gradP[i] = gradP[i + 256] = grad3[perm[i] % 12];
85 | }*/
86 |
87 | // Skewing and unskewing factors for 2, 3, and 4 dimensions
88 | var F2 = 0.5*(Math.sqrt(3)-1);
89 | var G2 = (3-Math.sqrt(3))/6;
90 |
91 | var F3 = 1/3;
92 | var G3 = 1/6;
93 |
94 | // 2D simplex noise
95 | module.simplex2 = function(xin, yin) {
96 | var n0, n1, n2; // Noise contributions from the three corners
97 | // Skew the input space to determine which simplex cell we're in
98 | var s = (xin+yin)*F2; // Hairy factor for 2D
99 | var i = Math.floor(xin+s);
100 | var j = Math.floor(yin+s);
101 | var t = (i+j)*G2;
102 | var x0 = xin-i+t; // The x,y distances from the cell origin, unskewed.
103 | var y0 = yin-j+t;
104 | // For the 2D case, the simplex shape is an equilateral triangle.
105 | // Determine which simplex we are in.
106 | var i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords
107 | if(x0>y0) { // lower triangle, XY order: (0,0)->(1,0)->(1,1)
108 | i1=1; j1=0;
109 | } else { // upper triangle, YX order: (0,0)->(0,1)->(1,1)
110 | i1=0; j1=1;
111 | }
112 | // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
113 | // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
114 | // c = (3-sqrt(3))/6
115 | var x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords
116 | var y1 = y0 - j1 + G2;
117 | var x2 = x0 - 1 + 2 * G2; // Offsets for last corner in (x,y) unskewed coords
118 | var y2 = y0 - 1 + 2 * G2;
119 | // Work out the hashed gradient indices of the three simplex corners
120 | i &= 255;
121 | j &= 255;
122 | var gi0 = gradP[i+perm[j]];
123 | var gi1 = gradP[i+i1+perm[j+j1]];
124 | var gi2 = gradP[i+1+perm[j+1]];
125 | // Calculate the contribution from the three corners
126 | var t0 = 0.5 - x0*x0-y0*y0;
127 | if(t0<0) {
128 | n0 = 0;
129 | } else {
130 | t0 *= t0;
131 | n0 = t0 * t0 * gi0.dot2(x0, y0); // (x,y) of grad3 used for 2D gradient
132 | }
133 | var t1 = 0.5 - x1*x1-y1*y1;
134 | if(t1<0) {
135 | n1 = 0;
136 | } else {
137 | t1 *= t1;
138 | n1 = t1 * t1 * gi1.dot2(x1, y1);
139 | }
140 | var t2 = 0.5 - x2*x2-y2*y2;
141 | if(t2<0) {
142 | n2 = 0;
143 | } else {
144 | t2 *= t2;
145 | n2 = t2 * t2 * gi2.dot2(x2, y2);
146 | }
147 | // Add contributions from each corner to get the final noise value.
148 | // The result is scaled to return values in the interval [-1,1].
149 | return 70 * (n0 + n1 + n2);
150 | };
151 |
152 | // 3D simplex noise
153 | module.simplex3 = function(xin, yin, zin) {
154 | var n0, n1, n2, n3; // Noise contributions from the four corners
155 |
156 | // Skew the input space to determine which simplex cell we're in
157 | var s = (xin+yin+zin)*F3; // Hairy factor for 2D
158 | var i = Math.floor(xin+s);
159 | var j = Math.floor(yin+s);
160 | var k = Math.floor(zin+s);
161 |
162 | var t = (i+j+k)*G3;
163 | var x0 = xin-i+t; // The x,y distances from the cell origin, unskewed.
164 | var y0 = yin-j+t;
165 | var z0 = zin-k+t;
166 |
167 | // For the 3D case, the simplex shape is a slightly irregular tetrahedron.
168 | // Determine which simplex we are in.
169 | var i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords
170 | var i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords
171 | if(x0 >= y0) {
172 | if(y0 >= z0) { i1=1; j1=0; k1=0; i2=1; j2=1; k2=0; }
173 | else if(x0 >= z0) { i1=1; j1=0; k1=0; i2=1; j2=0; k2=1; }
174 | else { i1=0; j1=0; k1=1; i2=1; j2=0; k2=1; }
175 | } else {
176 | if(y0 < z0) { i1=0; j1=0; k1=1; i2=0; j2=1; k2=1; }
177 | else if(x0 < z0) { i1=0; j1=1; k1=0; i2=0; j2=1; k2=1; }
178 | else { i1=0; j1=1; k1=0; i2=1; j2=1; k2=0; }
179 | }
180 | // A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
181 | // a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and
182 | // a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where
183 | // c = 1/6.
184 | var x1 = x0 - i1 + G3; // Offsets for second corner
185 | var y1 = y0 - j1 + G3;
186 | var z1 = z0 - k1 + G3;
187 |
188 | var x2 = x0 - i2 + 2 * G3; // Offsets for third corner
189 | var y2 = y0 - j2 + 2 * G3;
190 | var z2 = z0 - k2 + 2 * G3;
191 |
192 | var x3 = x0 - 1 + 3 * G3; // Offsets for fourth corner
193 | var y3 = y0 - 1 + 3 * G3;
194 | var z3 = z0 - 1 + 3 * G3;
195 |
196 | // Work out the hashed gradient indices of the four simplex corners
197 | i &= 255;
198 | j &= 255;
199 | k &= 255;
200 | var gi0 = gradP[i+ perm[j+ perm[k ]]];
201 | var gi1 = gradP[i+i1+perm[j+j1+perm[k+k1]]];
202 | var gi2 = gradP[i+i2+perm[j+j2+perm[k+k2]]];
203 | var gi3 = gradP[i+ 1+perm[j+ 1+perm[k+ 1]]];
204 |
205 | // Calculate the contribution from the four corners
206 | var t0 = 0.6 - x0*x0 - y0*y0 - z0*z0;
207 | if(t0<0) {
208 | n0 = 0;
209 | } else {
210 | t0 *= t0;
211 | n0 = t0 * t0 * gi0.dot3(x0, y0, z0); // (x,y) of grad3 used for 2D gradient
212 | }
213 | var t1 = 0.6 - x1*x1 - y1*y1 - z1*z1;
214 | if(t1<0) {
215 | n1 = 0;
216 | } else {
217 | t1 *= t1;
218 | n1 = t1 * t1 * gi1.dot3(x1, y1, z1);
219 | }
220 | var t2 = 0.6 - x2*x2 - y2*y2 - z2*z2;
221 | if(t2<0) {
222 | n2 = 0;
223 | } else {
224 | t2 *= t2;
225 | n2 = t2 * t2 * gi2.dot3(x2, y2, z2);
226 | }
227 | var t3 = 0.6 - x3*x3 - y3*y3 - z3*z3;
228 | if(t3<0) {
229 | n3 = 0;
230 | } else {
231 | t3 *= t3;
232 | n3 = t3 * t3 * gi3.dot3(x3, y3, z3);
233 | }
234 | // Add contributions from each corner to get the final noise value.
235 | // The result is scaled to return values in the interval [-1,1].
236 | return 32 * (n0 + n1 + n2 + n3);
237 |
238 | };
239 |
240 | // ##### Perlin noise stuff
241 |
242 | function fade(t) {
243 | return t*t*t*(t*(t*6-15)+10);
244 | }
245 |
246 | function lerp(a, b, t) {
247 | return (1-t)*a + t*b;
248 | }
249 |
250 | // 2D Perlin Noise
251 | module.perlin2 = function(x, y) {
252 | // Find unit grid cell containing point
253 | var X = Math.floor(x), Y = Math.floor(y);
254 | // Get relative xy coordinates of point within that cell
255 | x = x - X; y = y - Y;
256 | // Wrap the integer cells at 255 (smaller integer period can be introduced here)
257 | X = X & 255; Y = Y & 255;
258 |
259 | // Calculate noise contributions from each of the four corners
260 | var n00 = gradP[X+perm[Y]].dot2(x, y);
261 | var n01 = gradP[X+perm[Y+1]].dot2(x, y-1);
262 | var n10 = gradP[X+1+perm[Y]].dot2(x-1, y);
263 | var n11 = gradP[X+1+perm[Y+1]].dot2(x-1, y-1);
264 |
265 | // Compute the fade curve value for x
266 | var u = fade(x);
267 |
268 | // Interpolate the four results
269 | return lerp(
270 | lerp(n00, n10, u),
271 | lerp(n01, n11, u),
272 | fade(y));
273 | };
274 |
275 | // 3D Perlin Noise
276 | module.perlin3 = function(x, y, z) {
277 | // Find unit grid cell containing point
278 | var X = Math.floor(x), Y = Math.floor(y), Z = Math.floor(z);
279 | // Get relative xyz coordinates of point within that cell
280 | x = x - X; y = y - Y; z = z - Z;
281 | // Wrap the integer cells at 255 (smaller integer period can be introduced here)
282 | X = X & 255; Y = Y & 255; Z = Z & 255;
283 |
284 | // Calculate noise contributions from each of the eight corners
285 | var n000 = gradP[X+ perm[Y+ perm[Z ]]].dot3(x, y, z);
286 | var n001 = gradP[X+ perm[Y+ perm[Z+1]]].dot3(x, y, z-1);
287 | var n010 = gradP[X+ perm[Y+1+perm[Z ]]].dot3(x, y-1, z);
288 | var n011 = gradP[X+ perm[Y+1+perm[Z+1]]].dot3(x, y-1, z-1);
289 | var n100 = gradP[X+1+perm[Y+ perm[Z ]]].dot3(x-1, y, z);
290 | var n101 = gradP[X+1+perm[Y+ perm[Z+1]]].dot3(x-1, y, z-1);
291 | var n110 = gradP[X+1+perm[Y+1+perm[Z ]]].dot3(x-1, y-1, z);
292 | var n111 = gradP[X+1+perm[Y+1+perm[Z+1]]].dot3(x-1, y-1, z-1);
293 |
294 | // Compute the fade curve value for x, y, z
295 | var u = fade(x);
296 | var v = fade(y);
297 | var w = fade(z);
298 |
299 | // Interpolate
300 | return lerp(
301 | lerp(
302 | lerp(n000, n100, u),
303 | lerp(n001, n101, u), w),
304 | lerp(
305 | lerp(n010, n110, u),
306 | lerp(n011, n111, u), w),
307 | v);
308 | };
309 |
310 | })(this);
311 |
--------------------------------------------------------------------------------