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 create(gson: Gson, type: TypeToken): TypeAdapter? { if (AbstractPerlinNoise::class.java.isAssignableFrom(type.rawType)) { return object : TypeAdapter() { 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 } return null } } }