Files
Terrarum/src/net/torvald/random/TileableValueNoise.kt
2017-09-20 14:36:40 +09:00

95 lines
3.6 KiB
Kotlin

package net.torvald.random
import com.jme3.math.FastMath
import net.torvald.terrarum.gameactors.floorInt
import net.torvald.terrarum.gameactors.round
import net.torvald.terrarum.gameactors.roundInt
import net.torvald.terrarum.gameworld.fmod
import java.util.*
/**
* Generate value noise that is always "tileably looped" every x in loopSize.
*
* @param width: power of 2's are recommended.
* Created by minjaesong on 2016-10-28.
*/
class TileableValueNoise(
val octaves: Int, val persistency: Float, val width: Int, val initSamples: Int = 4) {
init {
// FIXME wow, such primitive!
if (!FastMath.isPowerOfTwo(width)) throw Error("width is not power of two!")
if (!FastMath.isPowerOfTwo(initSamples)) throw Error("initSamples is not power of two!")
if (initSamples < 2) throw Error("initSamples must be equal to or greater than 2, and power of two!")
}
private val noiseData = Array<Float>(width + 1, { 0f })
private var noiseGenerated = false
fun generate(seed: Long) {
val rng = HQRNG(seed)
// initialise
Arrays.fill(noiseData, 0f)
val octavesIntStream = Array(octaves, { 1.shl(it) }) // array of (1, 2, 4, 8, ...)
octaveLoop@ for (octcnt in 0..octaves - 1) { // 1, 2, 3, 4, ...
val i = octavesIntStream[octcnt] // 1, 2, 4, 8, ...
// octave 1 samples four points
val samples = initSamples * i
val amp = FastMath.pow(persistency, octcnt.toFloat()) // 1/1, 1/2, 1/3, 1/4, ...
var pointThis = 0f
var pointNext = rng.nextBipolarFloat()
var pointLoop = pointThis
try {
for (x in 0..width) {
val thisSampleStart: Int = // 0-256 -> 0-4 -> 0-256(qnt)
(x / width.toFloat() * samples).floorInt() * (width / samples)
val nextSampleStart: Int =
(x / width.toFloat() * samples).floorInt().plus(1) * (width / samples)
val stepWithinWindow: Int = x % (nextSampleStart - thisSampleStart)
val windowScale: Float = stepWithinWindow.toFloat() / (width / samples)
// next pair of points
if (stepWithinWindow == 0 && x > 0) {
pointThis = pointNext
pointNext = if (nextSampleStart >= width) pointLoop else rng.nextBipolarFloat()
}
// additive mix
val noiseValue = FastMath.interpolateLinear(windowScale, pointThis, pointNext) * amp
noiseData[x] += noiseValue
/*println("x: $x\tstart: $thisSampleStart\tnext: $nextSampleStart\t" +
"window: $stepWithinWindow\t" +
"pThis: $pointThis\tpNext: $pointNext\tvalue: $noiseValue")*/
}
}
catch (e: ArithmeticException) {
//println("[TileableValueNoise] division by zero error occured, aborting further octave iteration.")
break@octaveLoop
} // division by zero; which means octave value was too high
}
for (x in 0..width - 1) {
//println(noiseData[x])
}
noiseGenerated = true
}
operator fun get(x: Int): Float {
if (!noiseGenerated) throw Error("Noise not generated; use 'generate(seed: Long)'")
return noiseData[x fmod width]
}
private fun Random.nextBipolarFloat(): Float {
val d = this.nextFloat()
return d.times(2f).minus(1f)
}
}