KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/AbstractPerlinNoise.kt

281 lines
7.6 KiB
Kotlin

package ru.dbotthepony.kstarbound.util.random
import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.arrays.Double2DArray
import ru.dbotthepony.kommons.math.linearInterpolation
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.gson.value
import kotlin.math.floor
import kotlin.math.sqrt
/**
* Base class for noise generator.
*
* Once [init]ialized, generator is thread-safe, and can be sampled
* concurrently by any amount of threads.
*/
abstract class AbstractPerlinNoise(val parameters: PerlinNoiseParameters) {
abstract operator fun get(x: Double): Double
abstract operator fun get(x: Double, y: Double): Double
abstract operator fun get(x: Double, y: Double, z: Double): Double
var hasSeedSpecified = false
private set
private var isInitialized = false
private val initLock = Any()
var seed: Long = 0L
private set
val scaleD = parameters.scale.toDouble()
protected data class Setup(val b0: Int, val b1: Int, val r0: Double, val r1: Double)
protected val p by lazy { IntArray(parameters.scale * 2 + 2) }
protected val g1 by lazy { DoubleArray(parameters.scale * 2 + 2) }
protected val g2 by lazy { Double2DArray.allocate(parameters.scale * 2 + 2, 2) }
protected val g3 by lazy { Double2DArray.allocate(parameters.scale * 2 + 2, 3) }
init {
if (parameters.seed != null && parameters.type != PerlinNoiseParameters.Type.UNITIALIZED) {
init(parameters.seed)
}
}
private fun doInit(seed: Long) {
p.fill(0)
g1.fill(0.0)
g2.fill(0.0)
g3.fill(0.0)
val random = random(seed)
for (i in 0 until parameters.scale) {
p[i] = i
g1[i] = random.nextInt(-parameters.scale, parameters.scale) / scaleD
g2[i, 0] = random.nextInt(-parameters.scale, parameters.scale) / scaleD
g2[i, 1] = random.nextInt(-parameters.scale, parameters.scale) / scaleD
g3[i, 0] = random.nextInt(-parameters.scale, parameters.scale) / scaleD
g3[i, 1] = random.nextInt(-parameters.scale, parameters.scale) / scaleD
g3[i, 2] = random.nextInt(-parameters.scale, parameters.scale) / scaleD
val l2 = sqrt(g2[i, 0] * g2[i, 0] + g2[i, 1] * g2[i, 1])
val l3 = sqrt(g3[i, 0] * g3[i, 0] + g3[i, 1] * g3[i, 1] + g3[i, 2] * g3[i, 2])
if (l2 == 0.0) {
g2[i, 0] = 1.0
g2[i, 1] = 0.0
} else {
g2[i, 0] = g2[i, 0] / l2
g2[i, 1] = g2[i, 1] / l2
}
if (l3 == 0.0) {
g3[i, 0] = 1.0
g3[i, 1] = 0.0
g3[i, 2] = 0.0
} else {
g3[i, 0] = g3[i, 0] / l3
g3[i, 1] = g3[i, 1] / l3
g3[i, 2] = g3[i, 2] / l3
}
}
for (i in parameters.scale downTo 1) {
val k = p[i]
val j = random.nextInt(0, parameters.scale - 1)
p[i] = p[j]
p[j] = k
}
for (i in 0 until parameters.scale + 2) {
p[parameters.scale + i] = p[i]
g1[parameters.scale + i] = g1[i]
g2[parameters.scale + i, 0] = g2[i, 0]
g2[parameters.scale + i, 1] = g2[i, 1]
g3[parameters.scale + i, 0] = g3[i, 0]
g3[parameters.scale + i, 1] = g3[i, 1]
g3[parameters.scale + i, 2] = g3[i, 2]
}
}
protected fun checkInit() {
synchronized(initLock) {
check(hasSeedSpecified) { "Tried to use perlin noise without seed specified" }
if (!isInitialized) {
doInit(seed)
isInitialized = true
}
}
}
fun init(seed: Long, now: Boolean = false) {
if (parameters.type == PerlinNoiseParameters.Type.UNITIALIZED)
return
this.hasSeedSpecified = true
this.isInitialized = false
this.seed = seed
if (now) {
checkInit()
}
}
protected fun curve(value: Double): Double {
return value * value * (3.0 - 2.0 * value)
}
protected fun setup(value: Double): Setup {
val iv: Int = floor(value).toInt()
val fv: Double = value - iv
val b0 = iv and (parameters.scale - 1)
val b1 = (iv + 1) and (parameters.scale - 1)
val r1 = fv - 1.0
return Setup(b0, b1, fv, r1)
}
protected fun at2(x: Double, y: Double, rx: Double, ry: Double): Double {
return rx * x + ry * y
}
protected fun at3(x: Double, y: Double, z: Double, rx: Double, ry: Double, rz: Double): Double {
return rx * x + ry * y + rz * z
}
protected fun noise1(x: Double): Double {
val (bx0, bx1, rx0, rx1) = setup(x)
val sx = curve(rx0)
val u = rx0 * g1[p[bx0]]
val v = rx1 * g1[p[bx1]]
return linearInterpolation(sx, u, v)
}
protected fun noise2(x: Double, y: Double): Double {
val (bx0, bx1, rx0, rx1) = setup(x)
val (by0, by1, ry0, ry1) = setup(y)
val i = p[bx0]
val j = p[bx1]
val b00 = p[i + by0]
val b10 = p[j + by0]
val b01 = p[i + by1]
val b11 = p[j + by1]
val sx = curve(rx0)
val sy = curve(ry0)
var u = at2(g2[b00, 0], g2[b00, 1], rx0, ry0)
var v = at2(g2[b10, 0], g2[b10, 1], rx1, ry0)
val a = linearInterpolation(sx, u, v)
u = at2(g2[b01, 0], g2[b01, 1], rx0, ry1)
v = at2(g2[b11, 0], g2[b11, 1], rx1, ry1)
val b = linearInterpolation(sx, u, v)
return linearInterpolation(sy, a, b)
}
protected fun noise3(x: Double, y: Double, z: Double): Double {
val (bx0, bx1, rx0, rx1) = setup(x)
val (by0, by1, ry0, ry1) = setup(y)
val (bz0, bz1, rz0, rz1) = setup(z)
val i = p[bx0];
val j = p[bx1];
val b00 = p[i + by0];
val b10 = p[j + by0];
val b01 = p[i + by1];
val b11 = p[j + by1];
val sx = curve(rx0);
val sy = curve(ry0);
val sz = curve(rz0);
var u = at3(g3[b00 + bz0, 0], g3[b00 + bz0, 1], g3[b00 + bz0, 2], rx0, ry0, rz0)
var v = at3(g3[b10 + bz0, 0], g3[b10 + bz0, 1], g3[b10 + bz0, 2], rx1, ry0, rz0)
var a = linearInterpolation(sx, u, v)
u = at3(g3[b01 + bz0, 0], g3[b01 + bz0, 1], g3[b01 + bz0, 2], rx0, ry1, rz0)
v = at3(g3[b11 + bz0, 0], g3[b11 + bz0, 1], g3[b11 + bz0, 2], rx1, ry1, rz0)
var b = linearInterpolation(sx, u, v)
val c = linearInterpolation(sy, a, b)
u = at3(g3[b00 + bz1, 0], g3[b00 + bz1, 1], g3[b00 + bz1, 2], rx0, ry0, rz1)
v = at3(g3[b10 + bz1, 0], g3[b10 + bz1, 1], g3[b10 + bz1, 2], rx1, ry0, rz1)
a = linearInterpolation(sx, u, v)
u = at3(g3[b01 + bz1, 0], g3[b01 + bz1, 1], g3[b01 + bz1, 2], rx0, ry1, rz1)
v = at3(g3[b11 + bz1, 0], g3[b11 + bz1, 1], g3[b11 + bz1, 2], rx1, ry1, rz1)
b = linearInterpolation(sx, u, v)
val d = linearInterpolation(sy, a, b)
return linearInterpolation(sz, c, d)
}
companion object : TypeAdapterFactory {
fun of(parameters: PerlinNoiseParameters): AbstractPerlinNoise {
return when (parameters.type) {
PerlinNoiseParameters.Type.PERLIN -> PerlinNoise(parameters)
PerlinNoiseParameters.Type.BILLOW -> BillowNoise(parameters)
PerlinNoiseParameters.Type.RIDGED_MULTI -> RidgedNoise(parameters)
PerlinNoiseParameters.Type.UNITIALIZED -> EmptyNoise(parameters)
}
}
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (AbstractPerlinNoise::class.java.isAssignableFrom(type.rawType)) {
return object : TypeAdapter<AbstractPerlinNoise>() {
private val parent = gson.getAdapter(PerlinNoiseParameters::class.java)
override fun write(out: JsonWriter, value: AbstractPerlinNoise?) {
if (value == null)
out.nullValue()
else {
val json = parent.toJsonTree(value.parameters) as JsonObject
if (value.seed != 0L) {
json["seed"] = value.seed
}
out.value(json)
}
}
override fun read(`in`: JsonReader): AbstractPerlinNoise? {
if (`in`.consumeNull())
return null
return of(parent.read(`in`)!!)
}
} as TypeAdapter<T>
}
return null
}
}
}