Skip to content

Commit a7a1444

Browse files
committed
working compact 3D worley noise implementation
1 parent 85da3d9 commit a7a1444

File tree

6 files changed

+131
-32
lines changed

6 files changed

+131
-32
lines changed

Package.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,5 @@ let package = Package(
2121
name: "NoiseTests",
2222
dependencies: ["Noise"],
2323
path: "tests/noise"),
24-
],
25-
exclude: ["sources/meta"]
24+
]
2625
)

readme.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,17 @@
2424

2525
Also known as Worley noise, produces a cellular, bulby texture.
2626

27+
* Voronoi noise (3D)
28+
29+
3 dimensional cell/worley noise.
30+
2731
* Poisson sample noise (2D)
2832

2933
Two dimensional point noise with a visually uniform distribution, and no clumping.
3034

3135

3236
![](super_simplex3D.png)
33-
![](voronoi.png)
37+
![](voronoi3D.png)
3438
![](poisson.png)
3539

3640
### A note on building

sources/meta/cell.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,9 @@ func kernel3d()
153153
for r2:Double in cell_classes.keys.sorted()
154154
{
155155
let cells:[Cell3D] = cell_classes[r2]!
156-
let class_vector:String = cells.map{ String(describing: -$0.root) }.joined(separator: ", ")
157-
print("\(Colors.bold)\(pad(String(r2), to: 5))\(Colors.off): \(pad(String(cells.count), to: 2)) cells [\(class_vector)]")
156+
let class_vector:String = cells.reversed().map{ String(describing: $0.root) }.joined(separator: ", ")
157+
//print("\(Colors.bold)\(pad(String(r2), to: 5))\(Colors.off): \(pad(String(cells.count), to: 2)) cells [\(class_vector)]")
158+
print("(\(Colors.bold)\(pad(String(r2), to: 5))\(Colors.off), [\(class_vector)]),")
158159
}
159160
}
160161

sources/noise/cell.swift

Lines changed: 116 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
public
22
struct CellNoise2D:Noise
33
{
4+
private
5+
typealias IntV2 = (a:Int, b:Int)
6+
private
7+
typealias DoubleV2 = (x:Double, y:Double)
8+
49
private
510
let permutation_table:PermutationTable,
611
amplitude:Double,
@@ -15,7 +20,7 @@ struct CellNoise2D:Noise
1520
}
1621

1722
private
18-
func distance(from sample_point:(x:Double, y:Double), generating_point:(a:Int, b:Int)) -> Double
23+
func distance(from sample_point:DoubleV2, generating_point:IntV2) -> Double
1924
{
2025
let hash:Int = self.permutation_table.hash(generating_point.a, generating_point.b)
2126
// hash is within 0 ... 255, take it to 0 ... 0.5
@@ -37,10 +42,10 @@ struct CellNoise2D:Noise
3742
public
3843
func evaluate(_ x:Double, _ y:Double) -> Double
3944
{
40-
let sample:(x:Double, y:Double) = (x * self.frequency, y * self.frequency)
45+
let sample:DoubleV2 = (x * self.frequency, y * self.frequency)
4146

42-
let bin:(a:Int, b:Int) = (floor(sample.x), floor(sample.y)),
43-
offset:(x:Double, y:Double) = (sample.x - Double(bin.a), sample.y - Double(bin.b))
47+
let bin:IntV2 = (floor(sample.x), floor(sample.y)),
48+
offset:DoubleV2 = (sample.x - Double(bin.a), sample.y - Double(bin.b))
4449

4550
// determine kernel
4651

@@ -75,16 +80,16 @@ struct CellNoise2D:Noise
7580
// found.
7681

7782
let quadrant:(x:Bool, y:Bool) = (offset.x > 0.5, offset.y > 0.5),
78-
near:(a:Int, b:Int) = (bin.a + (quadrant.x ? 1 : 0), bin.b + (quadrant.y ? 1 : 0)),
79-
far:(a:Int, b:Int) = (bin.a + (quadrant.x ? 0 : 1), bin.b + (quadrant.y ? 0 : 1))
83+
near:IntV2 = (bin.a + (quadrant.x ? 1 : 0), bin.b + (quadrant.y ? 1 : 0)),
84+
far:IntV2 = (bin.a + (quadrant.x ? 0 : 1), bin.b + (quadrant.y ? 0 : 1))
8085

81-
let nearpoint_disp:(x:Double, y:Double) = (abs(offset.x - (quadrant.x ? 1 : 0)),
86+
let nearpoint_disp:DoubleV2 = (abs(offset.x - (quadrant.x ? 1 : 0)),
8287
abs(offset.y - (quadrant.y ? 1 : 0)))
8388

8489
var r2_min:Double = self.distance(from: sample, generating_point: near)
8590

8691
@inline(__always)
87-
func test(generating_point:(a:Int, b:Int))
92+
func test(generating_point:IntV2)
8893
{
8994
let r2:Double = self.distance(from: sample, generating_point: generating_point)
9095

@@ -136,8 +141,8 @@ struct CellNoise2D:Noise
136141
// | | | | | | |
137142
// inner ----- B ------- C --------+
138143

139-
let inner:(a:Int, b:Int) = (bin.a + (quadrant.x ? 2 : -1), bin.b + (quadrant.y ? 2 : -1)),
140-
outer:(a:Int, b:Int) = (bin.a + (quadrant.x ? -1 : 2), bin.b + (quadrant.y ? -1 : 2))
144+
let inner:IntV2 = (bin.a + (quadrant.x ? 2 : -1), bin.b + (quadrant.y ? 2 : -1)),
145+
outer:IntV2 = (bin.a + (quadrant.x ? -1 : 2), bin.b + (quadrant.y ? -1 : 2))
141146

142147
// B points
143148
if (nearpoint_disp.x + 0.5) * (nearpoint_disp.x + 0.5) < r2_min
@@ -206,17 +211,9 @@ public
206211
struct CellNoise3D:Noise
207212
{
208213
private
209-
struct Point3
210-
{
211-
let x:Double, y:Double, z:Double
212-
213-
init(_ x:Double, _ y:Double, _ z:Double)
214-
{
215-
self.x = x
216-
self.y = y
217-
self.z = z
218-
}
219-
}
214+
typealias IntV3 = (a:Int, b:Int, c:Int)
215+
private
216+
typealias DoubleV3 = (x:Double, y:Double, z:Double)
220217

221218
private
222219
let permutation_table:PermutationTable,
@@ -226,13 +223,13 @@ struct CellNoise3D:Noise
226223
public
227224
init(amplitude:Double, frequency:Double, seed:Int = 0)
228225
{
229-
self.amplitude = 2.squareRoot() * amplitude
226+
self.amplitude = amplitude * 1/3.squareRoot()
230227
self.frequency = frequency
231228
self.permutation_table = PermutationTable(seed: seed)
232229
}
233230

234231
private
235-
func distance(from sample_point:(x:Double, y:Double, z:Double), generating_point:(a:Int, b:Int, c:Int)) -> Double
232+
func distance(from sample_point:DoubleV3, generating_point:IntV3) -> Double
236233
{
237234
let hash:Int = self.permutation_table.hash(generating_point.a, generating_point.b, generating_point.c)
238235
// hash is within 0 ... 255, take it to 0 ... 0.5
@@ -258,17 +255,109 @@ struct CellNoise3D:Noise
258255
public
259256
func evaluate(_ x:Double, _ y:Double) -> Double
260257
{
261-
return 0
258+
return self.evaluate(x, y, 0)
262259
}
263260

264261
public
265-
func evaluate(_ x:Double, _ y:Double, _:Double) -> Double
262+
func evaluate(_ x:Double, _ y:Double, _ z:Double) -> Double
266263
{
267-
return self.evaluate(x, y)
264+
let sample:DoubleV3 = (x * self.frequency, y * self.frequency, z * self.frequency)
265+
266+
let bin:IntV3 = (floor(sample.x), floor(sample.y), floor(sample.z)),
267+
offset:DoubleV3 = (sample.x - Double(bin.a), sample.y - Double(bin.b), sample.z - Double(bin.c))
268+
269+
// determine kernel
270+
271+
// Same idea as with the 2D points, except in 3 dimensions
272+
273+
// near - quadrant ---- near - quadrant.y
274+
// | | |
275+
// |----+----|
276+
// | | * |
277+
// near - quadrant.x -- near quadrant →
278+
// ↓
279+
280+
let quadrant:IntV3 = (offset.x > 0.5 ? 1 : -1, offset.y > 0.5 ? 1 : -1, offset.z > 0.5 ? 1 : -1),
281+
near:IntV3 = (bin.a + (quadrant.a + 1) >> 1, bin.b + (quadrant.b + 1) >> 1, bin.c + (quadrant.c + 1) >> 1)
282+
283+
let nearpoint_disp:DoubleV3 = (abs(offset.x - Double((quadrant.a + 1) >> 1)),
284+
abs(offset.y - Double((quadrant.b + 1) >> 1)),
285+
abs(offset.z - Double((quadrant.c + 1) >> 1)))
286+
287+
var r2_min:Double = self.distance(from: sample, generating_point: near)
288+
289+
let kernel:[(r:Double, cell_offsets:[(Int, Int, Int)])] =
290+
[
291+
(0.0 , [/*(0, 0, 0), */(-1, 0, 0), (0, -1, 0), (-1, -1, 0), (0, 0, -1), (-1, 0, -1), (0, -1, -1), (-1, -1, -1)]),
292+
(0.25, [(0, 0, 1), (-1, 0, 1), (0, -1, 1), (-1, -1, 1), (0, 1, 0), (-1, 1, 0), (1, 0, 0), (1, -1, 0),
293+
(0, 1, -1), (-1, 1, -1), (1, 0, -1), (1, -1, -1)]),
294+
(0.5 , [(0, 1, 1), (-1, 1, 1), (1, 0, 1), (1, -1, 1), (1, 1, 0), (1, 1, -1)]),
295+
(0.75, [(1, 1, 1)]),
296+
(1.0 , [(-2, 0, 0), (-2, -1, 0), (0, -2, 0), (-1, -2, 0), (-2, 0, -1), (-2, -1, -1), (0, -2, -1), (-1, -2, -1),
297+
(0, 0, -2), (-1, 0, -2), (0, -1, -2), (-1, -1, -2)]),
298+
(1.25, [(-2, 0, 1), (-2, -1, 1), (0, -2, 1), (-1, -2, 1), (-2, 1, 0), (1, -2, 0), (-2, 1, -1), (1, -2, -1),
299+
(0, 1, -2), (-1, 1, -2), (1, 0, -2), (1, -1, -2)]),
300+
(1.5 , [(-2, 1, 1), (1, -2, 1), (1, 1, -2)]),
301+
(2.0 , [(-2, -2, 0), (-2, -2, -1), (-2, 0, -2), (-2, -1, -2), (0, -2, -2), (-1, -2, -2)]),
302+
(2.25, [(0, 0, 2), (-1, 0, 2), (0, -1, 2), (-1, -1, 2), (-2, -2, 1), (0, 2, 0), (-1, 2, 0), (2, 0, 0),
303+
(2, -1, 0), (0, 2, -1), (-1, 2, -1), (2, 0, -1), (2, -1, -1), (-2, 1, -2), (1, -2, -2)]),
304+
(2.5 , [(0, 1, 2), (-1, 1, 2), (1, 0, 2), (1, -1, 2), (0, 2, 1), (-1, 2, 1), (2, 0, 1), (2, -1, 1),
305+
(1, 2, 0), (2, 1, 0), (1, 2, -1), (2, 1, -1)]),
306+
(2.75, [(1, 1, 2), (1, 2, 1), (2, 1, 1)])
307+
]
308+
309+
for (kernel_radius, cell_offsets):(r:Double, cell_offsets:[(Int, Int, Int)]) in kernel
310+
{
311+
if r2_min < kernel_radius
312+
{
313+
break // EARLY EXIT
314+
}
315+
316+
for cell_offset:IntV3 in cell_offsets
317+
{
318+
// calculate distance from quadrant volume to kernel cell
319+
var cell_distance2:Double
320+
if cell_offset.a == 0
321+
{
322+
cell_distance2 = 0
323+
}
324+
else
325+
{ // move by 0.5 towards zero
326+
let dx:Double = Double(cell_offset.a) + (cell_offset.a > 0 ? -0.5 : 0.5) + nearpoint_disp.x
327+
cell_distance2 = dx*dx
328+
}
329+
330+
if cell_offset.b != 0
331+
{ // move by 0.5 towards zero
332+
let dy:Double = Double(cell_offset.b) + (cell_offset.b > 0 ? -0.5 : 0.5) + nearpoint_disp.y
333+
cell_distance2 += dy*dy
334+
}
335+
336+
if cell_offset.c != 0
337+
{ // move by 0.5 towards zero
338+
let dz:Double = Double(cell_offset.c) + (cell_offset.c > 0 ? -0.5 : 0.5) + nearpoint_disp.z
339+
cell_distance2 += dz*dz
340+
}
341+
342+
guard cell_distance2 < r2_min
343+
else
344+
{
345+
continue
346+
}
347+
348+
let generating_point:IntV3 = (near.a + quadrant.a*cell_offset.a,
349+
near.b + quadrant.b*cell_offset.b,
350+
near.c + quadrant.c*cell_offset.c)
351+
let r2:Double = self.distance(from: sample, generating_point: generating_point)
352+
r2_min = min(r2, r2_min)
353+
}
354+
}
355+
356+
return self.amplitude * r2_min
268357
}
269358

270359
public
271-
func evaluate(_ x:Double, _ y:Double, _:Double, _:Double) -> Double
360+
func evaluate(_ x:Double, _ y:Double, _ z:Double, _:Double) -> Double
272361
{
273362
return self.evaluate(x, y)
274363
}

tests/LinuxMain.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ pixbuf = V.sample_area_saturated_to_u8(width: viewer_size, height: viewer_size,
4444
print(clock() - t0)
4545
try png_encode(path: "voronoi.png", raw_data: pixbuf, properties: png_properties)
4646

47+
let V3:CellNoise3D = CellNoise3D(amplitude: 255, frequency: 0.01)
48+
t0 = clock()
49+
pixbuf = V3.sample_area_saturated_to_u8(width: viewer_size, height: viewer_size, offset: 0)
50+
print(clock() - t0)
51+
try png_encode(path: "voronoi3D.png", raw_data: pixbuf, properties: png_properties)
52+
4753

4854
let S:fBm<SimplexNoise2D> = fBm<SimplexNoise2D>(amplitude: 0.5*127.5, frequency: 0.001, octaves: 10)
4955
t0 = clock()

voronoi3D.png

120 KB
Loading

0 commit comments

Comments
 (0)