Skip to content

Commit d21b1af

Browse files
committed
worley noise
1 parent 950e122 commit d21b1af

File tree

5 files changed

+130
-3
lines changed

5 files changed

+130
-3
lines changed

readme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,17 @@
2020

2121
3D SuperSimplex volumetric noise, suitable for texturing any 3D object without seams or texture unwrapping.
2222

23+
* Voronoi noise (2D)
24+
25+
Also known as Worley noise, produces a cellular, bulby texture.
26+
2327
* Poisson sample noise (2D)
2428

2529
Two dimensional point noise with a visually uniform distribution, and no clumping.
2630

2731

2832
![](super_simplex3D.png)
33+
![](voronoi.png)
2934
![](poisson.png)
3035

3136
### A note on building

sources/noise/cell.swift

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,130 @@
11
public
2-
struct CellNoise2D:Noise
2+
struct CellNoise2D:Noise
33
{
4+
private
5+
let permutation_table:PermutationTable,
6+
amplitude:Double,
7+
frequency:Double
48

59
public
610
init(amplitude:Double, frequency:Double, seed:Int = 0)
711
{
12+
self.amplitude = 2.squareRoot() * amplitude
13+
self.frequency = frequency
14+
self.permutation_table = PermutationTable(seed: seed)
15+
}
16+
17+
private
18+
func distance(from sample_point:(x:Double, y:Double), generating_point:(a:Int, b:Int)) -> Double
19+
{
20+
let hash:Int = self.permutation_table.hash(generating_point.a, generating_point.b)
21+
// hash is within 0 ... 255, take it to 0 ... 0.5
22+
let length:Double = Double(hash) * 0.5/255,
23+
diagonal:Double = length * (1 / 2.squareRoot())
24+
25+
let (dpx, dpy):(Double, Double)
26+
switch hash & 0b0111
27+
{
28+
case 0:
29+
(dpx, dpy) = ( diagonal, diagonal)
30+
case 1:
31+
(dpx, dpy) = (-diagonal, diagonal)
32+
case 2:
33+
(dpx, dpy) = (-diagonal, -diagonal)
34+
case 3:
35+
(dpx, dpy) = ( diagonal, -diagonal)
36+
case 4:
37+
(dpx, dpy) = ( length, 0)
38+
case 5:
39+
(dpx, dpy) = (0, length)
40+
case 6:
41+
(dpx, dpy) = (-length, 0)
42+
case 7:
43+
(dpx, dpy) = ( 0, -length)
44+
default:
45+
fatalError("unreachable")
46+
}
47+
48+
let dx:Double = Double(generating_point.a) + dpx - sample_point.x,
49+
dy:Double = Double(generating_point.b) + dpy - sample_point.y
50+
return dx*dx + dy*dy
851
}
952

1053
public
1154
func evaluate(_ x:Double, _ y:Double) -> Double
1255
{
13-
return 0
56+
let sample:(x:Double, y:Double) = (x * self.frequency, y * self.frequency)
57+
58+
let bin:(a:Int, b:Int) = (floor(sample.x), floor(sample.y)),
59+
offset:(x:Double, y:Double) = (sample.x - Double(bin.a), sample.y - Double(bin.b))
60+
61+
// determine kernel
62+
63+
// The control points do not live within the grid cells, rather they float
64+
// around the *corners* of the grid cells (the ‘O’s in the diagram).
65+
// The grid cell that the sample point has been binned into is shaded.
66+
67+
// xb xb + 1
68+
// +------------------+
69+
// | | | |
70+
// yb |---- O//////O ----|
71+
// | |//////| |
72+
// yb + 1 |---- O//////O ----|
73+
// | | | |
74+
// +------------------+
75+
76+
// The bin itself is divided into quadrants to classify the four corners as
77+
// “near” and “far” points. We call these points the *generating points*.
78+
// The sample point (example) has been marked with an ‘*’.
79+
80+
// O ------- far
81+
// | | |
82+
// |----+----|
83+
// | * | |
84+
// near ------- O
85+
86+
// The actual control points never spawn more than 0.5 normalized units
87+
// away from the near and far points, and their cross-analogues. Therefore,
88+
// the quadrants also provide a means of early exit, since if a sample is
89+
// closer to a control point than the quadrant dividers, it is impossible
90+
// for the sample to be closer to the control point that lives on the
91+
// other side of the divider.
92+
93+
let quadrant:(x:Bool, y:Bool) = (offset.x > 0.5, offset.y > 0.5),
94+
near:(a:Int, b:Int) = (bin.a + (quadrant.x ? 1 : 0), bin.b + (quadrant.y ? 1 : 0)),
95+
far:(a:Int, b:Int) = (bin.a + (quadrant.x ? 0 : 1), bin.b + (quadrant.y ? 0 : 1))
96+
97+
let divider_distance:(x:Double, y:Double) = ((offset.x - 0.5) * (offset.x - 0.5), (offset.y - 0.5) * (offset.y - 0.5))
98+
99+
var r2_min:Double = self.distance(from: sample, generating_point: near)
100+
101+
@inline(__always)
102+
func test(generating_point:(a:Int, b:Int))
103+
{
104+
let r2:Double = self.distance(from: sample, generating_point: generating_point)
105+
106+
if r2 < r2_min
107+
{
108+
r2_min = r2
109+
}
110+
}
111+
112+
if divider_distance.x < r2_min
113+
{
114+
test(generating_point: (far.a, near.b)) // near point horizontal
115+
}
116+
117+
if divider_distance.y < r2_min
118+
{
119+
test(generating_point: (near.a, far.b)) // near point vertical
120+
}
121+
122+
if divider_distance.x < r2_min && divider_distance.y < r2_min
123+
{
124+
test(generating_point: far)
125+
}
126+
127+
return self.amplitude * r2_min
14128
}
15129

16130
public

sources/noise/gradient.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ struct SimplexNoise2D:GradientNoise2D
4949
let permutation_table:PermutationTable
5050

5151
private
52-
let amplitude:Double, // this is not necissaryly the same amplitude passed into the initializer
52+
let amplitude:Double, // this is not necessarily the same amplitude passed into the initializer
5353
frequency:Double
5454

5555
public

tests/LinuxMain.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ for point:PoissonSampler.Point in poisson.generate(radius: 10, width: viewer_siz
3737
print(clock() - t0)
3838
try png_encode(path: "poisson.png", raw_data: pixbuf, properties: png_properties)
3939

40+
41+
let V:CellNoise2D = CellNoise2D(amplitude: 255, frequency: 0.01)
42+
t0 = clock()
43+
pixbuf = V.sample_area_saturated_to_u8(width: viewer_size, height: viewer_size, offset: 0)
44+
print(clock() - t0)
45+
try png_encode(path: "voronoi.png", raw_data: pixbuf, properties: png_properties)
46+
47+
4048
let S:fBm<SimplexNoise2D> = fBm<SimplexNoise2D>(amplitude: 0.5*127.5, frequency: 0.001, octaves: 10)
4149
t0 = clock()
4250
pixbuf = S.sample_area_saturated_to_u8(width: viewer_size, height: viewer_size, offset: 127.5)

voronoi.png

143 KB
Loading

0 commit comments

Comments
 (0)