300 lines
8.3 KiB
Kotlin
300 lines
8.3 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 java.util.concurrent.CompletableFuture
|
|
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
|
|
|
|
var seed: Long = 0L
|
|
private set
|
|
|
|
protected data class Setup(val b0: Int, val b1: Int, val r0: Double, val r1: Double)
|
|
|
|
protected val p by lazy(LazyThreadSafetyMode.NONE) { IntArray(parameters.scale * 2 + 2) }
|
|
protected val g1 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(parameters.scale * 2 + 2) }
|
|
|
|
// flat arrays for performance
|
|
protected val g2_0 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(parameters.scale * 2 + 2) }
|
|
protected val g2_1 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(parameters.scale * 2 + 2) }
|
|
|
|
protected val g3_0 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(parameters.scale * 2 + 2) }
|
|
protected val g3_1 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(parameters.scale * 2 + 2) }
|
|
protected val g3_2 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(parameters.scale * 2 + 2) }
|
|
|
|
private var init = false
|
|
private val initLock = Any()
|
|
|
|
init {
|
|
if (parameters.seed != null && parameters.type != PerlinNoiseParameters.Type.UNITIALIZED) {
|
|
init(parameters.seed)
|
|
}
|
|
}
|
|
|
|
protected fun checkInit() {
|
|
check(hasSeedSpecified) { "No noise seed specified" }
|
|
|
|
if (!init) {
|
|
synchronized(initLock) {
|
|
if (!init) {
|
|
init0(seed)
|
|
init = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fun init(seed: Long) {
|
|
if (parameters.type == PerlinNoiseParameters.Type.UNITIALIZED)
|
|
return
|
|
|
|
synchronized(initLock) {
|
|
this.init = false
|
|
this.hasSeedSpecified = true
|
|
this.seed = seed
|
|
}
|
|
}
|
|
|
|
private fun init0(seed: Long) {
|
|
val p = p
|
|
val g1 = g1
|
|
val g2_0 = g2_0
|
|
val g2_1 = g2_1
|
|
val g3_0 = g3_0
|
|
val g3_1 = g3_1
|
|
val g3_2 = g3_2
|
|
|
|
p.fill(0)
|
|
g1.fill(0.0)
|
|
g2_0.fill(0.0)
|
|
g2_1.fill(0.0)
|
|
g3_0.fill(0.0)
|
|
g3_1.fill(0.0)
|
|
g3_2.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) / parameters.scale.toDouble()
|
|
|
|
g2_0[i] = random.nextInt(-parameters.scale, parameters.scale) / parameters.scale.toDouble()
|
|
g2_1[i] = random.nextInt(-parameters.scale, parameters.scale) / parameters.scale.toDouble()
|
|
|
|
g3_0[i] = random.nextInt(-parameters.scale, parameters.scale) / parameters.scale.toDouble()
|
|
g3_1[i] = random.nextInt(-parameters.scale, parameters.scale) / parameters.scale.toDouble()
|
|
g3_2[i] = random.nextInt(-parameters.scale, parameters.scale) / parameters.scale.toDouble()
|
|
|
|
val l2 = sqrt(g2_0[i] * g2_0[i] + g2_1[i] * g2_1[i])
|
|
val l3 = sqrt(g3_0[i] * g3_0[i] + g3_1[i] * g3_1[i] + g3_2[i] * g3_2[i])
|
|
|
|
if (l2 == 0.0) {
|
|
g2_0[i] = 1.0
|
|
g2_1[i] = 0.0
|
|
} else {
|
|
g2_0[i] = g2_0[i] / l2
|
|
g2_1[i] = g2_1[i] / l2
|
|
}
|
|
|
|
if (l3 == 0.0) {
|
|
g3_0[i] = 1.0
|
|
g3_1[i] = 0.0
|
|
g3_2[i] = 0.0
|
|
} else {
|
|
g3_0[i] = g3_0[i] / l3
|
|
g3_1[i] = g3_1[i] / l3
|
|
g3_2[i] = g3_2[i] / 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_0[parameters.scale + i] = g2_0[i]
|
|
g2_1[parameters.scale + i] = g2_1[i]
|
|
|
|
g3_0[parameters.scale + i] = g3_0[i]
|
|
g3_1[parameters.scale + i] = g3_1[i]
|
|
g3_2[parameters.scale + i] = g3_2[i]
|
|
}
|
|
}
|
|
|
|
@Suppress("NOTHING_TO_INLINE")
|
|
protected inline fun curve(value: Double): Double {
|
|
return value * value * (3.0 - 2.0 * value)
|
|
}
|
|
|
|
@Suppress("NOTHING_TO_INLINE")
|
|
protected inline 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)
|
|
}
|
|
|
|
@Suppress("NOTHING_TO_INLINE")
|
|
protected inline fun at2(x: Double, y: Double, rx: Double, ry: Double): Double {
|
|
return rx * x + ry * y
|
|
}
|
|
|
|
@Suppress("NOTHING_TO_INLINE")
|
|
protected inline fun at3(x: Double, y: Double, z: Double, rx: Double, ry: Double, rz: Double): Double {
|
|
return rx * x + ry * y + rz * z
|
|
}
|
|
|
|
@Suppress("NOTHING_TO_INLINE")
|
|
protected inline 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)
|
|
}
|
|
|
|
@Suppress("NOTHING_TO_INLINE")
|
|
protected inline 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_0[b00], g2_1[b00], rx0, ry0)
|
|
var v = at2(g2_0[b10], g2_1[b10], rx1, ry0)
|
|
val a = linearInterpolation(sx, u, v)
|
|
|
|
u = at2(g2_0[b01], g2_1[b01], rx0, ry1)
|
|
v = at2(g2_0[b11], g2_1[b11], rx1, ry1)
|
|
val b = linearInterpolation(sx, u, v)
|
|
|
|
return linearInterpolation(sy, a, b)
|
|
}
|
|
|
|
@Suppress("NOTHING_TO_INLINE")
|
|
protected inline 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_0[b00 + bz0], g3_1[b00 + bz0], g3_2[b00 + bz0], rx0, ry0, rz0)
|
|
var v = at3(g3_0[b10 + bz0], g3_1[b10 + bz0], g3_2[b10 + bz0], rx1, ry0, rz0)
|
|
var a = linearInterpolation(sx, u, v)
|
|
|
|
u = at3(g3_0[b01 + bz0], g3_1[b01 + bz0], g3_2[b01 + bz0], rx0, ry1, rz0)
|
|
v = at3(g3_0[b11 + bz0], g3_1[b11 + bz0], g3_2[b11 + bz0], rx1, ry1, rz0)
|
|
var b = linearInterpolation(sx, u, v)
|
|
|
|
val c = linearInterpolation(sy, a, b)
|
|
|
|
u = at3(g3_0[b00 + bz1], g3_1[b00 + bz1], g3_2[b00 + bz1], rx0, ry0, rz1)
|
|
v = at3(g3_0[b10 + bz1], g3_1[b10 + bz1], g3_2[b10 + bz1], rx1, ry0, rz1)
|
|
a = linearInterpolation(sx, u, v)
|
|
|
|
u = at3(g3_0[b01 + bz1], g3_1[b01 + bz1], g3_2[b01 + bz1], rx0, ry1, rz1)
|
|
v = at3(g3_0[b11 + bz1], g3_1[b11 + bz1], g3_2[b11 + bz1], 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
|
|
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
|
|
}
|
|
}
|
|
}
|