diff --git a/Sources/libnoise/Cache.hx b/Sources/libnoise/Cache.hx new file mode 100644 index 0000000..417759f --- /dev/null +++ b/Sources/libnoise/Cache.hx @@ -0,0 +1,72 @@ +package libnoise; + +/** + * A 3D array of data, used internally. + */ +class Cache { + + var data : Array>> = null; + + public var empty(get, never) : Bool; + + public var xMin(default, null) : Int = 0; + public var yMin(default, null) : Int = 0; + public var zMin(default, null) : Int = 0; + public var xMax(default, null) : Int = -1; + public var yMax(default, null) : Int = -1; + public var zMax(default, null) : Int = -1; + + public inline function new() { + } + + /** + * Allocates enough space to cache the given region. This will overwrite any existing data. + */ + public inline function allocate(xMin : Int, yMin : Int, zMin : Int, xMax : Int, yMax : Int, zMax : Int) : Void { + data = []; + for (x in 0...(xMax - xMin + 1)) { + data.push([]); + for (y in 0...(yMax - yMin + 1)) { + data[x].push([]); + + // `resize()` will insert appropriate default values. + data[x][y].resize(zMax - zMin + 1); + } + } + + this.xMin = xMin; + this.yMin = yMin; + this.zMin = zMin; + this.xMax = xMax; + this.yMax = yMax; + this.zMax = zMax; + } + + public inline function clear() : Void { + data = null; + xMin = 0; + xMax = -1; + } + + public inline function inBounds(x : Int, y : Int, z : Int) : Bool { + return x >= xMin && x <= xMax && y >= yMin && y <= yMax && z >= zMin && z <= zMax; + } + + /** + * Precondition: (`x`, `y`, `z`) is in bounds. + */ + public inline function get(x : Int, y : Int, z : Int) : T { + return data[x - xMin][y - yMin][z - zMin]; + } + + /** + * Precondition: (`x`, `y`, `z`) is in bounds. + */ + public inline function set(x : Int, y : Int, z : Int, value : T) : T { + return data[x - xMin][y - yMin][z - zMin] = value; + } + + inline function get_empty() : Bool { + return data == null; + } +} diff --git a/Sources/libnoise/ModuleBase.hx b/Sources/libnoise/ModuleBase.hx index 91205bf..157d4a5 100644 --- a/Sources/libnoise/ModuleBase.hx +++ b/Sources/libnoise/ModuleBase.hx @@ -26,6 +26,39 @@ class ModuleBase { return 0; } + /** + * Caches values within the given region of space. Certain classes will use this to improve performance + * when you call `getValue()` within that region. + * + * Only one region can be cached at a time. Trying to cache another will overwrite the first. To avoid + * this, create a new module instance if `isCacheEmpty()` returns false. + */ + public function cache(xMin : Float, yMin : Float, zMin : Float, xMax : Float, yMax : Float, zMax : Float) : Void { + if (modules != null) { + for (module in modules) { + module.cache(xMin, yMin, zMin, xMax, yMax, zMax); + } + } + } + + public function isCacheEmpty() : Bool { + if (modules != null) { + for (module in modules) { + if (!module.isCacheEmpty()) { + return false; + } + } + } + + return true; + } + + public function clearCache() : Void { + for (module in modules) { + module.clearCache(); + } + } + //TODO: see if dispose functions need to be implemented (cf https://github.com/ricardojmendez/LibNoise.Unity/blob/master/ModuleBase.cs#L158) } diff --git a/Sources/libnoise/generator/Voronoi.hx b/Sources/libnoise/generator/Voronoi.hx index dfc8607..586fcfa 100644 --- a/Sources/libnoise/generator/Voronoi.hx +++ b/Sources/libnoise/generator/Voronoi.hx @@ -1,17 +1,30 @@ package libnoise.generator; +import libnoise.Cache; + +/** + * Provides a noise module that outputs Voronoi cells. [GENERATOR] + */ class Voronoi extends ModuleBase { + var sites : Cache; var frequency : Float; var displacement : Float; var seed : Int; - var distance : Bool; + var useDistance : Bool; - public function new(frequency : Float, displacement : Float, seed : Int, distance : Bool) { + /** + * @param frequency The frequency of the site points. + * @param displacement The intensity of the output value. Multiplies everything except distance. + * @param seed The random seed. + * @param useDistance Indicates whether the distance from the nearest site is added to the output value. + */ + public function new(frequency : Float, displacement : Float, seed : Int, useDistance : Bool) { this.frequency = frequency; this.displacement = displacement; this.seed = seed; - this.distance = distance; + this.useDistance = useDistance; + sites = new Cache(); super(0); } @@ -19,43 +32,100 @@ class Voronoi extends ModuleBase { x *= frequency; y *= frequency; z *= frequency; - var xi = (x > 0.0 ? Std.int(x) : Std.int(x - 1)); - var iy = (y > 0.0 ? Std.int(y) : Std.int(y - 1)); - var iz = (z > 0.0 ? Std.int(z) : Std.int(z - 1)); - var md = 2147483647.0; - var xc : Float = 0; - var yc : Float = 0; - var zc : Float = 0; - for (zcu in (iz - 2)...(iz + 3)) { - for (ycu in (iy - 2)...(iy + 3)) { - for (xcu in (xi - 2)...(xi + 3)) { - var xp = xcu + Utils.ValueNoise3D(xcu, ycu, zcu, seed); - var yp = ycu + Utils.ValueNoise3D(xcu, ycu, zcu, seed + 1); - var zp = zcu + Utils.ValueNoise3D(xcu, ycu, zcu, seed + 2); - var xd = xp - x; - var yd = yp - y; - var zd = zp - z; - var d = xd * xd + yd * yd + zd * zd; - if (d < md) { - md = d; - xc = xp; - yc = yp; - zc = zp; + + var xInt = Math.floor(x); + var yInt = Math.floor(y); + var zInt = Math.floor(z); + + var minDist = Math.POSITIVE_INFINITY; + var xCandidate : Float = 0; + var yCandidate : Float = 0; + var zCandidate : Float = 0; + + // Inside each unit cube, there is a site at a random position. Go + // through each of the nearby cubes until we find a cube with a site + // that is closest to (x, y, z). + for (zCur in (zInt - 2)...(zInt + 3)) { + for (yCur in (yInt - 2)...(yInt + 3)) { + for (xCur in (xInt - 2)...(xInt + 3)) { + // Calculate the position and distance to the site inside of + // this unit cube. + var siteX : Float; + var siteY : Float; + var siteZ : Float; + if (sites.inBounds(xCur, yCur, zCur)) { + var site : Site = sites.get(xCur, yCur, zCur); + siteX = site.x; + siteY = site.y; + siteZ = site.z; + } else { + siteX = xCur + Utils.ValueNoise3D(xCur, yCur, zCur, seed); + siteY = yCur + Utils.ValueNoise3D(xCur, yCur, zCur, seed + 1); + siteZ = zCur + Utils.ValueNoise3D(xCur, yCur, zCur, seed + 2); + } + + var dist = (siteX - x) * (siteX - x) + (siteY - y) * (siteY - y) + (siteZ - z) * (siteZ - z); + if (dist < minDist) { + // This site is closer than any others found so far. + minDist = dist; + xCandidate = siteX; + yCandidate = siteY; + zCandidate = siteZ; } } } } - var v : Float; - if (distance) { - var xd = xc - x; - var yd = yc - y; - var zd = zc - z; - v = (Math.sqrt(xd * xd + yd * yd + zd * zd)) * Utils.SQRT3 - 1.0; + + var value : Float; + if (useDistance) { + // Determine the distance to the nearest site. + value = Math.sqrt(minDist) * Utils.SQRT3 - 1.0; } else { - v = 0.0; + value = 0.0; } - return v + (displacement * Utils.ValueNoise3D(Std.int(Math.floor(xc)), Std.int(Math.floor(yc)), - Std.int(Math.floor(zc)), 0)); + return value + (displacement * Utils.ValueNoise3D(Std.int(Math.floor(xCandidate)), Std.int(Math.floor(yCandidate)), + Std.int(Math.floor(zCandidate)), 0)); + } + + @:inheritDoc + public override function cache(xMin : Float, yMin : Float, zMin : Float, xMax : Float, yMax : Float, zMax : Float) { + sites.allocate( + Math.floor(xMin * frequency) - 2, + Math.floor(yMin * frequency) - 2, + Math.floor(zMin * frequency) - 2, + Math.floor(xMax * frequency) + 2, + Math.floor(yMax * frequency) + 2, + Math.floor(zMax * frequency) + 2); + + for(z in sites.zMin...(sites.zMax + 1)) { + for(y in sites.yMin...(sites.yMax + 1)) { + for(x in sites.xMin...(sites.xMax + 1)) { + sites.set(x, y, z, new Site(x + Utils.ValueNoise3D(x, y, z, seed), + y + Utils.ValueNoise3D(x, y, z, seed + 1), + z + Utils.ValueNoise3D(z, y, z, seed + 2))); + } + } + } + } + + @:inheritDoc + public override function clearCache() : Void { + sites.clear(); + } +} + +/** + * The central point defining a Voronoi cell. Also known as a seed or a generator. + */ +class Site { + public var x : Float; + public var y : Float; + public var z : Float; + + public inline function new(x : Float, y : Float, z : Float) { + this.x = x; + this.y = y; + this.z = z; } }