Skip to content

Commit 43a6ec6

Browse files
committed
fix cell noise kernels, use uniform hashing to generate better feature points
1 parent e9281b0 commit 43a6ec6

File tree

2 files changed

+94
-120
lines changed

2 files changed

+94
-120
lines changed

sources/noise/cell.swift

Lines changed: 94 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ struct CellNoise2D:Noise
99
public
1010
init(amplitude:Double, frequency:Double, seed:Int = 0)
1111
{
12-
self.amplitude = 2.squareRoot() * amplitude
12+
self.amplitude = amplitude * 1/2.squareRoot()
1313
self.frequency = frequency
1414
self.permutation_table = PermutationTable(seed: seed)
1515
}
@@ -19,31 +19,15 @@ struct CellNoise2D:Noise
1919
{
2020
let hash:Int = self.permutation_table.hash(generating_point.a, generating_point.b)
2121
// 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())
2422

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-
}
23+
// Notice that we have 256 possible hashes, and therefore 8 bits of entropy,
24+
// to be divided up between 2 axes. We can assign 4 bits to the x and y
25+
// axes each (16 levels each)
26+
27+
// 0b XXXX YYYY
28+
29+
let dpx:Double = (Double(hash >> 4 ) - 15/2) * 1/16,
30+
dpy:Double = (Double(hash & 0b1111) - 15/2) * 1/16
4731

4832
let dx:Double = Double(generating_point.a) + dpx - sample_point.x,
4933
dy:Double = Double(generating_point.b) + dpy - sample_point.y
@@ -60,7 +44,7 @@ struct CellNoise2D:Noise
6044

6145
// determine kernel
6246

63-
// The control points do not live within the grid cells, rather they float
47+
// The feature points do not live within the grid cells, rather they float
6448
// around the *corners* of the grid cells (the ‘O’s in the diagram).
6549
// The grid cell that the sample point has been binned into is shaded.
6650

@@ -73,28 +57,29 @@ struct CellNoise2D:Noise
7357
// | | | |
7458
// +------------------+
7559

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*.
60+
// The bin itself is divided into quadrants to classify the four corners
61+
// as “near” and “far” points. We call these points the *generating points*.
7862
// The sample point (example) has been marked with an ‘*’.
7963

80-
// O ------- far
64+
// A ------- far
8165
// | | |
8266
// |----+----|
8367
// | * | |
84-
// near ------- O
68+
// near ------- A
8569

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.
70+
// The actual feature points never spawn outside of the unit square surrounding
71+
// their generating points. Therefore, the boundaries of the generating
72+
// squares serve as a useful means of early exit if the square is farther
73+
// away than a point already found, then there is no point checking that
74+
// square since it cannot produce a feature point closer than we have already
75+
// found.
9276

9377
let quadrant:(x:Bool, y:Bool) = (offset.x > 0.5, offset.y > 0.5),
9478
near:(a:Int, b:Int) = (bin.a + (quadrant.x ? 1 : 0), bin.b + (quadrant.y ? 1 : 0)),
9579
far:(a:Int, b:Int) = (bin.a + (quadrant.x ? 0 : 1), bin.b + (quadrant.y ? 0 : 1))
9680

97-
let divider_distance:(x:Double, y:Double) = ((offset.x - 0.5) * (offset.x - 0.5), (offset.y - 0.5) * (offset.y - 0.5))
81+
let nearpoint_disp:(x:Double, y:Double) = (abs(offset.x - (quadrant.x ? 1 : 0)),
82+
abs(offset.y - (quadrant.y ? 1 : 0)))
9883

9984
var r2_min:Double = self.distance(from: sample, generating_point: near)
10085

@@ -109,21 +94,84 @@ struct CellNoise2D:Noise
10994
}
11095
}
11196

112-
if divider_distance.x < r2_min
97+
// A points
98+
if (0.5 - nearpoint_disp.y) * (0.5 - nearpoint_disp.y) < r2_min
11399
{
114-
test(generating_point: (far.a, near.b)) // near point horizontal
100+
test(generating_point: (near.a, far.b))
115101
}
116102

117-
if divider_distance.y < r2_min
103+
if (0.5 - nearpoint_disp.x) * (0.5 - nearpoint_disp.x) < r2_min
118104
{
119-
test(generating_point: (near.a, far.b)) // near point vertical
105+
test(generating_point: (far.a, near.b))
120106
}
121107

122-
if divider_distance.x < r2_min && divider_distance.y < r2_min
108+
// far point
109+
if (0.5 - nearpoint_disp.x) * (0.5 - nearpoint_disp.x) + (0.5 - nearpoint_disp.y) * (0.5 - nearpoint_disp.y) < r2_min
123110
{
124111
test(generating_point: far)
125112
}
126113

114+
// This is the part where shit hits the fan. (`inner` and `outer` are never
115+
// sampled directly, they are used for calculating the coordinates of the
116+
// generating point.)
117+
118+
// +-------- D ------- E ----- outer
119+
// | | | | | | |
120+
// |----+----|----+----|----+----|
121+
// | | | | | | |
122+
// C ------- A —————— far ------ E
123+
// | | | | | | |
124+
// |----+----|----+----|----+----|
125+
// | | | * | | | |
126+
// B ----- near —————— A ------- D
127+
// | | | | | | |
128+
// |----+----|----+----|----+----|
129+
// | | | | | | |
130+
// inner ----- B ------- C --------+
131+
132+
let inner:(a:Int, b:Int) = (bin.a + (quadrant.x ? 2 : -1), bin.b + (quadrant.y ? 2 : -1)),
133+
outer:(a:Int, b:Int) = (bin.a + (quadrant.x ? -1 : 2), bin.b + (quadrant.y ? -1 : 2))
134+
135+
// B points
136+
if (nearpoint_disp.x + 0.5) * (nearpoint_disp.x + 0.5) < r2_min
137+
{
138+
test(generating_point: (inner.a, near.b))
139+
}
140+
if (nearpoint_disp.y + 0.5) * (nearpoint_disp.y + 0.5) < r2_min
141+
{
142+
test(generating_point: (near.a, inner.b))
143+
}
144+
145+
// C points
146+
if (nearpoint_disp.x + 0.5) * (nearpoint_disp.x + 0.5) + (0.5 - nearpoint_disp.y) * (0.5 - nearpoint_disp.y) < r2_min
147+
{
148+
test(generating_point: (inner.a, far.b))
149+
}
150+
if (nearpoint_disp.y + 0.5) * (nearpoint_disp.y + 0.5) + (0.5 - nearpoint_disp.x) * (0.5 - nearpoint_disp.x) < r2_min
151+
{
152+
test(generating_point: (far.a, inner.b))
153+
}
154+
155+
// D points
156+
if (1.5 - nearpoint_disp.y) * (1.5 - nearpoint_disp.y) < r2_min
157+
{
158+
test(generating_point: (near.a, outer.b))
159+
}
160+
if (1.5 - nearpoint_disp.x) * (1.5 - nearpoint_disp.x) < r2_min
161+
{
162+
test(generating_point: (outer.a, near.b))
163+
}
164+
165+
// E points
166+
if (0.5 - nearpoint_disp.x) * (0.5 - nearpoint_disp.x) + (1.5 - nearpoint_disp.y) * (1.5 - nearpoint_disp.y) < r2_min
167+
{
168+
test(generating_point: (far.a, outer.b))
169+
}
170+
if (0.5 - nearpoint_disp.y) * (0.5 - nearpoint_disp.y) + (1.5 - nearpoint_disp.x) * (1.5 - nearpoint_disp.x) < r2_min
171+
{
172+
test(generating_point: (outer.a, far.b))
173+
}
174+
127175
return self.amplitude * r2_min
128176
}
129177

@@ -178,14 +226,14 @@ struct CellNoise3D:Noise
178226
// Notice that we have 256 possible hashes, and therefore 8 bits of entropy,
179227
// to be divided up between three axes. We can assign 3 bits to the x and
180228
// y axes each (8 levels each), and 2 bits to the z axis (4 levels). To
181-
// compensate for the lack of z resolution, we bump up every other control
229+
// compensate for the lack of z resolution, we bump up every other feature
182230
// point by half a level.
183231

184232
// 0b XXX YYY ZZ
185233

186-
let dpx:Double = (Double(hash >> 5 ) - 3.5) * 0.25,
187-
dpy:Double = (Double(hash >> 2 & 0b0111 ) - 3.5) * 0.25,
188-
dpz:Double = (Double(hash << 1 & 0b0111 + ((hash >> 5 ^ hash >> 2) & 1)) - 3.5) * 0.25
234+
let dpx:Double = (Double(hash >> 5 ) - 7/2) * 1/8,
235+
dpy:Double = (Double(hash >> 2 & 0b0111 ) - 7/2) * 1/8,
236+
dpz:Double = (Double(hash << 1 & 0b0111 + ((hash >> 5 ^ hash >> 2) & 1)) - 7/2) * 1/8
189237

190238
let dx:Double = Double(generating_point.a) + dpx - sample_point.x,
191239
dy:Double = Double(generating_point.b) + dpy - sample_point.y,
@@ -196,80 +244,6 @@ struct CellNoise3D:Noise
196244
public
197245
func evaluate(_ x:Double, _ y:Double) -> Double
198246
{
199-
/*
200-
let sample:(x:Double, y:Double) = (x * self.frequency, y * self.frequency)
201-
202-
let bin:(a:Int, b:Int) = (floor(sample.x), floor(sample.y)),
203-
offset:(x:Double, y:Double) = (sample.x - Double(bin.a), sample.y - Double(bin.b))
204-
205-
// determine kernel
206-
207-
// The control points do not live within the grid cells, rather they float
208-
// around the *corners* of the grid cells (the ‘O’s in the diagram).
209-
// The grid cell that the sample point has been binned into is shaded.
210-
211-
// xb xb + 1
212-
// +------------------+
213-
// | | | |
214-
// yb |---- O//////O ----|
215-
// | |//////| |
216-
// yb + 1 |---- O//////O ----|
217-
// | | | |
218-
// +------------------+
219-
220-
// The bin itself is divided into quadrants to classify the four corners as
221-
// “near” and “far” points. We call these points the *generating points*.
222-
// The sample point (example) has been marked with an ‘*’.
223-
224-
// O ------- far
225-
// | | |
226-
// |----+----|
227-
// | * | |
228-
// near ------- O
229-
230-
// The actual control points never spawn more than 0.5 normalized units
231-
// away from the near and far points, and their cross-analogues. Therefore,
232-
// the quadrants also provide a means of early exit, since if a sample is
233-
// closer to a control point than the quadrant dividers, it is impossible
234-
// for the sample to be closer to the control point that lives on the
235-
// other side of the divider.
236-
237-
let quadrant:(x:Bool, y:Bool) = (offset.x > 0.5, offset.y > 0.5),
238-
near:(a:Int, b:Int) = (bin.a + (quadrant.x ? 1 : 0), bin.b + (quadrant.y ? 1 : 0)),
239-
far:(a:Int, b:Int) = (bin.a + (quadrant.x ? 0 : 1), bin.b + (quadrant.y ? 0 : 1))
240-
241-
let divider_distance:(x:Double, y:Double) = ((offset.x - 0.5) * (offset.x - 0.5), (offset.y - 0.5) * (offset.y - 0.5))
242-
243-
var r2_min:Double = self.distance(from: sample, generating_point: near)
244-
245-
@inline(__always)
246-
func test(generating_point:(a:Int, b:Int))
247-
{
248-
let r2:Double = self.distance(from: sample, generating_point: generating_point)
249-
250-
if r2 < r2_min
251-
{
252-
r2_min = r2
253-
}
254-
}
255-
256-
if divider_distance.x < r2_min
257-
{
258-
test(generating_point: (far.a, near.b)) // near point horizontal
259-
}
260-
261-
if divider_distance.y < r2_min
262-
{
263-
test(generating_point: (near.a, far.b)) // near point vertical
264-
}
265-
266-
if divider_distance.x < r2_min && divider_distance.y < r2_min
267-
{
268-
test(generating_point: far)
269-
}
270-
271-
return self.amplitude * r2_min
272-
*/
273247
return 0
274248
}
275249

voronoi.png

-16.8 KB
Loading

0 commit comments

Comments
 (0)