Skip to content

Commit 5e81d72

Browse files
committed
Added PerlinNoise extra. More is planned for this, but here it is in the mean time!
1 parent ec351b3 commit 5e81d72

3 files changed

Lines changed: 314 additions & 0 deletions

File tree

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
class PerlinNoiseMachine {
2+
3+
constructor(){
4+
5+
this.permutation = new Uint8Array(512);
6+
7+
for(let i = 0; i < 256; i++){
8+
this.permutation[i] = i;
9+
}
10+
11+
// Fisher-Yates shuffle the array, and double it
12+
for(let i = 255; i > 0; i--){
13+
let swapIndex = (Math.random() * i) | 0;
14+
15+
let temp = this.permutation[i];
16+
this.permutation[i] = this.permutation[swapIndex];
17+
this.permutation[swapIndex] = temp;
18+
this.permutation[swapIndex+255] = temp;
19+
}
20+
}
21+
22+
noise(x, y = 0, z = 0){
23+
let cubeX = (x | 0) & 255, cubeY = (y | 0) & 255, cubeZ = (z | 0) & 255; // which 'unit cube' this point lies on
24+
let offsetX = x - (x | 0), offsetY = y - (y | 0), offsetZ = z - (z | 0); // The point's location in that cube
25+
26+
// Smoothing function:
27+
let u = this.fade(offsetX);
28+
let v = this.fade(offsetY);
29+
let w = this.fade(offsetZ);
30+
31+
let p = this.permutation;
32+
33+
// Hash all eight corners of the cube down to a single value using our precomputed permutation
34+
let aaa = p[p[p[ cubeX ]+ cubeY ]+ cubeZ ];
35+
let aba = p[p[p[ cubeX ]+ cubeY + 1 ]+ cubeZ ];
36+
let aab = p[p[p[ cubeX ]+ cubeY ]+ cubeZ + 1 ];
37+
let abb = p[p[p[ cubeX ]+ cubeY + 1 ]+ cubeZ + 1 ];
38+
let baa = p[p[p[ cubeX + 1 ]+ cubeY ]+ cubeZ ];
39+
let bba = p[p[p[ cubeX + 1 ]+ cubeY + 1 ]+ cubeZ ];
40+
let bab = p[p[p[ cubeX + 1 ]+ cubeY ]+ cubeZ + 1 ];
41+
let bbb = p[p[p[ cubeX + 1 ]+ cubeY + 1 ]+ cubeZ + 1 ];
42+
43+
let gr = this.gradient;
44+
45+
// Generate noise from the input. The gr function takes the hashed corner values and turns them into a
46+
// vector, then dot products that vector with the rest of the input, which in this case are the vectors from the
47+
// user-provided point to the corners of the unit cube it sits in.
48+
let x1 = this.lerp( gr(aaa, offsetX, offsetY , offsetZ), gr(baa, offsetX-1, offsetY , offsetZ), u );
49+
let x2 = this.lerp( gr(aba, offsetX, offsetY-1, offsetZ), gr(bba, offsetX-1, offsetY-1, offsetZ), u );
50+
let y1 = this.lerp(x1, x2, v);
51+
52+
x1 = this.lerp( gr(aab, offsetX, offsetY , offsetZ-1), gr(bab, offsetX-1, offsetY , offsetZ-1), u );
53+
x2 = this.lerp( gr(abb, offsetX, offsetY-1, offsetZ-1), gr(bbb, offsetX-1, offsetY-1, offsetZ-1), u );
54+
let y2 = this.lerp(x1, x2, v);
55+
56+
let out = this.lerp(y1, y2, w);
57+
58+
// Normalize to 0-1, as the above output can be -1 to 1
59+
return (out+1)/2
60+
}
61+
62+
fade(t){
63+
return t * t * t * (t * (t * 6 - 15) + 10);
64+
}
65+
66+
lerp(a, b, x){
67+
return a + x * (b - a);
68+
}
69+
70+
/**
71+
* Returns the dot product of the vector x,y,z with a pseudorandomly chosen vector from the list:
72+
* (1,1,0),(-1,1,0),(1,-1,0),(-1,-1,0),
73+
* (1,0,1),(-1,0,1),(1,0,-1),(-1,0,-1),
74+
* (0,1,1),(0,-1,1),(0,1,-1),(0,-1,-1)
75+
* based on the hash provided.
76+
* @param hash The hash to use to determine which vector is used in the dot product.
77+
* @param x
78+
* @param y
79+
* @param z
80+
*/
81+
gradient(hash, x, y , z){
82+
switch(hash & 0xF) {
83+
case 0x0: return x + y;
84+
case 0x1: return -x + y;
85+
case 0x2: return x - y;
86+
case 0x3: return -x - y;
87+
case 0x4: return x + z;
88+
case 0x5: return -x + z;
89+
case 0x6: return x - z;
90+
case 0x7: return -x - z;
91+
case 0x8: return y + z;
92+
case 0x9: return -y + z;
93+
case 0xA: return y - z;
94+
case 0xB: return -y - z;
95+
case 0xC: return y + x;
96+
case 0xD: return -y + z;
97+
case 0xE: return y - x;
98+
case 0xF: return -y - z;
99+
default: return 0; // never happens
100+
}
101+
}
102+
}

extras/PerlinNoise/index.html

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Perlin Noise Demo</title>
6+
<link rel="stylesheet" href="../../_assets/css/examples.css" />
7+
<link href="../../_assets/css/shared.css" rel="stylesheet" type="text/css"/>
8+
9+
<style>
10+
canvas {
11+
background-color: #EEEEEE;
12+
border: 1px solid #444444;
13+
}
14+
</style>
15+
16+
</head>
17+
<body>
18+
<header class="EaselJS">
19+
<h1>Perlin Noise Demo</h1>
20+
<p>Perlin noise visualized in various ways. All noise is generated randomly, so outputs will change on refresh.
21+
<span>Note that PerlinNoiseMachine is ES6, and will only run in browsers that support it, unless transpiled to
22+
ES5</span>
23+
</header>
24+
</p>
25+
26+
<h3>1D Perlin Noise</h3>
27+
<p>Perlin noise output with only one input variable changing, visualized as a graph. Here, the x axis is the changing
28+
input variable, and the y axis is the output of the noise function.</p>
29+
<canvas id="c1" width="960" height="600"></canvas>
30+
31+
<h3>2D Perlin Noise</h3>
32+
<p>Perlin noise output with two varying inputs. Each pixel's x and y coordinate are fed into the noise machine to
33+
generate the value for that pixel. Brighter pixels are higher return values.</p>
34+
<canvas id="c2" width="960" height="600"></canvas>
35+
36+
<h3>2D RGB Perlin Noise</h3>
37+
<p>3 separate 2D perlin noise outputs similar to the above, with the three noise channels being fed into the RGB
38+
values of each pixel.</p>
39+
<canvas id="c3" width="960" height="600"></canvas>
40+
41+
<h3>2D HSL Perlin Noise</h3>
42+
<p>Same as above, but using HSL colour space instead of RGB.</p>
43+
<canvas id="c4" width="960" height="600"></canvas>
44+
<!--<img id="canvasOutput">-->
45+
46+
<script type="text/javascript" src="../../lib/easeljs-NEXT.min.js"></script>
47+
<script type="text/javascript" src="./PerlinNoiseMachine.js"></script>
48+
<script type="text/javascript" src="./testHarness.js"></script>
49+
50+
</body>
51+
</html>

extras/PerlinNoise/testHarness.js

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
let stage = new createjs.Stage("c1");
2+
3+
stage.update();
4+
5+
let noiseMachine = new PerlinNoiseMachine();
6+
7+
// performance testing
8+
/*let total = 0;
9+
let count = 50;
10+
11+
for(let i = 0; i < count; i++){
12+
let start = performance.now();
13+
14+
for(let j = 0; j < 1000000; j++){
15+
noiseMachine.noise(j, j*2.2, j*3.589);
16+
}
17+
18+
let end = performance.now();
19+
20+
console.log(`Run ${i}: ${end-start}`);
21+
total += end-start;
22+
}
23+
24+
console.log(`Average: ${total/count}`);*/
25+
26+
27+
let graph = new createjs.Shape();
28+
let g = graph.graphics;
29+
g.beginStroke("#000")
30+
.moveTo(0, stage.canvas.height/2);
31+
for(let i = 0; i < 960; i ++){
32+
let y = noiseMachine.noise(i/10000, i/100, 0.0024*i) * stage.canvas.height;
33+
g.lineTo(i, y);
34+
}
35+
g.endStroke();
36+
37+
stage.addChild(graph);
38+
stage.update();
39+
40+
// 2D greyscale
41+
42+
let c2 = document.getElementById("c2");
43+
let ctx = c2.getContext("2d");
44+
let imgData = ctx.getImageData(0,0,c2.width, c2.height);
45+
46+
for(let i = 0; i < c2.width*c2.height; i++){
47+
let x = i % c2.width;
48+
let y = (i-x)/c2.width;
49+
let val = noiseMachine.noise(x/70, y/70, 0.012523);
50+
imgData.data[4*i ] = val*255 | 0; //r
51+
imgData.data[4*i +1] = val*255 | 0; //g
52+
imgData.data[4*i +2] = val*255 | 0; //b
53+
imgData.data[4*i +3] = 255; //a
54+
}
55+
56+
57+
ctx.putImageData(imgData, 0, 0);
58+
59+
// 2D Color
60+
61+
let c3 = document.getElementById("c3");
62+
ctx = c3.getContext("2d");
63+
imgData = ctx.getImageData(0,0,c3.width, c3.height);
64+
65+
for(let i = 0; i < c3.width*c3.height; i++){
66+
let x = i % c3.width;
67+
let y = (i-x)/c3.width;
68+
let scale = 413;
69+
let offsetRed = 240;
70+
let offsetGreen = 17;
71+
let offsetBlue = 0.0027472;
72+
let r = noiseMachine.noise(x/scale, y/scale, offsetRed);
73+
let g = noiseMachine.noise(x/scale, y/scale, offsetGreen);
74+
let b = noiseMachine.noise(x/scale, y/scale, offsetBlue);
75+
imgData.data[4*i ] = r*255 | 0; //r
76+
imgData.data[4*i +1] = g*255 | 0; //g
77+
imgData.data[4*i +2] = b*255 | 0; //b
78+
imgData.data[4*i +3] = 255; //a
79+
}
80+
81+
ctx.putImageData(imgData, 0, 0);
82+
83+
// RGB
84+
85+
/*let c4 = document.getElementById("c4");
86+
ctx = c4.getContext("2d");
87+
imgData = ctx.getImageData(0,0,c4.width, c4.height);
88+
89+
for(let i = 0; i < c4.width*c4.height; i++){
90+
let x = i % c4.width;
91+
let y = (i-x)/c4.width;
92+
let scale = 413;
93+
let offsetRed = 17;
94+
let offsetGreen = 240;
95+
let offsetBlue = 0.0027472;
96+
let r = noiseMachine.noise(x/scale, y/scale, offsetRed);
97+
let g = noiseMachine.noise(x/scale, y/scale, offsetGreen);
98+
let b = noiseMachine.noise(x/scale, y/scale, offsetBlue);
99+
imgData.data[4*i ] = r*255 | 0; //r
100+
imgData.data[4*i +1] = g*255 | 0; //g
101+
imgData.data[4*i +2] = b*255 | 0; //b
102+
imgData.data[4*i +3] = 255; //a
103+
}
104+
105+
ctx.putImageData(imgData, 0, 0);
106+
let dataurl = c4.toDataURL();
107+
document.getElementById("canvasOutput").src = dataurl;*/
108+
109+
// HSL
110+
111+
let c4 = document.getElementById("c4");
112+
ctx = c4.getContext("2d");
113+
imgData = ctx.getImageData(0,0,c4.width, c4.height);
114+
115+
for(let i = 0; i < c4.width*c4.height; i++){
116+
let x = i % c4.width;
117+
let y = (i-x)/c4.width;
118+
let scale = 413;
119+
let offsetH = 200;
120+
let offsetS = 240;
121+
let offsetL = 160;
122+
let h = (noiseMachine.noise(x/(scale/2), y/(scale/2), offsetH)*3/2)%360 ;
123+
let s = noiseMachine.noise(x/(scale/2), y/(scale/2), offsetS)/2 + 0.5;
124+
let l = noiseMachine.noise(x/(scale), y/(scale), offsetL)*2/3+0.1;
125+
126+
let rgb = hslToRgb(h, s, l);
127+
128+
imgData.data[4*i ] = rgb[0] | 0; //r
129+
imgData.data[4*i +1] = rgb[1] | 0; //g
130+
imgData.data[4*i +2] = rgb[2] | 0; //b
131+
imgData.data[4*i +3] = 255; //a
132+
}
133+
134+
ctx.putImageData(imgData, 0, 0);
135+
/*let dataurl = c4.toDataURL();
136+
document.getElementById("canvasOutput").src = dataurl;*/
137+
138+
function hslToRgb(h, s, l) {
139+
let r, g, b;
140+
141+
if (s === 0) {
142+
r = g = b = l; // achromatic
143+
} else {
144+
let hue2rgb = function hue2rgb(p, q, t) {
145+
if (t < 0) t += 1;
146+
if (t > 1) t -= 1;
147+
if (t < 1 / 6) return p + (q - p) * 6 * t;
148+
if (t < 1 / 2) return q;
149+
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
150+
return p;
151+
};
152+
153+
let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
154+
let p = 2 * l - q;
155+
r = hue2rgb(p, q, h + 1 / 3);
156+
g = hue2rgb(p, q, h);
157+
b = hue2rgb(p, q, h - 1 / 3);
158+
}
159+
160+
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
161+
}

0 commit comments

Comments
 (0)