Skip to content

Commit e9281b0

Browse files
committed
implement 3D hashing and point generation
1 parent d21b1af commit e9281b0

File tree

1 file changed

+146
-0
lines changed

1 file changed

+146
-0
lines changed

sources/noise/cell.swift

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,149 @@ struct CellNoise2D:Noise
139139
return self.evaluate(x, y)
140140
}
141141
}
142+
143+
public
144+
struct CellNoise3D:Noise
145+
{
146+
private
147+
struct Point3
148+
{
149+
let x:Double, y:Double, z:Double
150+
151+
init(_ x:Double, _ y:Double, _ z:Double)
152+
{
153+
self.x = x
154+
self.y = y
155+
self.z = z
156+
}
157+
}
158+
159+
private
160+
let permutation_table:PermutationTable,
161+
amplitude:Double,
162+
frequency:Double
163+
164+
public
165+
init(amplitude:Double, frequency:Double, seed:Int = 0)
166+
{
167+
self.amplitude = 2.squareRoot() * amplitude
168+
self.frequency = frequency
169+
self.permutation_table = PermutationTable(seed: seed)
170+
}
171+
172+
private
173+
func distance(from sample_point:(x:Double, y:Double, z:Double), generating_point:(a:Int, b:Int, c:Int)) -> Double
174+
{
175+
let hash:Int = self.permutation_table.hash(generating_point.a, generating_point.b, generating_point.c)
176+
// hash is within 0 ... 255, take it to 0 ... 0.5
177+
178+
// Notice that we have 256 possible hashes, and therefore 8 bits of entropy,
179+
// to be divided up between three axes. We can assign 3 bits to the x and
180+
// 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
182+
// point by half a level.
183+
184+
// 0b XXX YYY ZZ
185+
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
189+
190+
let dx:Double = Double(generating_point.a) + dpx - sample_point.x,
191+
dy:Double = Double(generating_point.b) + dpy - sample_point.y,
192+
dz:Double = Double(generating_point.c) + dpz - sample_point.z
193+
return dx*dx + dy*dy + dz*dz
194+
}
195+
196+
public
197+
func evaluate(_ x:Double, _ y:Double) -> Double
198+
{
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+
*/
273+
return 0
274+
}
275+
276+
public
277+
func evaluate(_ x:Double, _ y:Double, _:Double) -> Double
278+
{
279+
return self.evaluate(x, y)
280+
}
281+
282+
public
283+
func evaluate(_ x:Double, _ y:Double, _:Double, _:Double) -> Double
284+
{
285+
return self.evaluate(x, y)
286+
}
287+
}

0 commit comments

Comments
 (0)