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(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) } }