diff --git a/.idea/artifacts/TerrarumBuild.xml b/.idea/artifacts/TerrarumBuild.xml index 3f1077feb..9f3fb9511 100644 --- a/.idea/artifacts/TerrarumBuild.xml +++ b/.idea/artifacts/TerrarumBuild.xml @@ -87,6 +87,7 @@ + \ No newline at end of file diff --git a/.idea/libraries/apache_commons_math3.xml b/.idea/libraries/apache_commons_math3.xml new file mode 100644 index 000000000..9287a3b9f --- /dev/null +++ b/.idea/libraries/apache_commons_math3.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TerrarumBuild.iml b/TerrarumBuild.iml index 0bad4ebf2..1095d70ee 100644 --- a/TerrarumBuild.iml +++ b/TerrarumBuild.iml @@ -25,5 +25,6 @@ + \ No newline at end of file diff --git a/lib/commons-math3-3.6.1-javadoc.jar b/lib/commons-math3-3.6.1-javadoc.jar new file mode 100644 index 000000000..bca7959af --- /dev/null +++ b/lib/commons-math3-3.6.1-javadoc.jar @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10ed884e29ca17cb59d6940a49e6ffe72b87a59078c53f07c2a173ed325bca34 +size 6840211 diff --git a/lib/commons-math3-3.6.1-sources.jar b/lib/commons-math3-3.6.1-sources.jar new file mode 100644 index 000000000..f0424fe21 --- /dev/null +++ b/lib/commons-math3-3.6.1-sources.jar @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2ff85a3c360d56c51a7021614a194f3fbaf224054642ac535016f118322934d +size 2514690 diff --git a/lib/commons-math3-3.6.1.jar b/lib/commons-math3-3.6.1.jar new file mode 100644 index 000000000..b113fef7f --- /dev/null +++ b/lib/commons-math3-3.6.1.jar @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e56d7b058d28b65abd256b8458e3885b674c1d588fa43cd7d1cbb9c7ef2b308 +size 2213560 diff --git a/src/net/torvald/terrarum/weather/WeatherMixer.kt b/src/net/torvald/terrarum/weather/WeatherMixer.kt index 9eccc76bd..a85bd817b 100644 --- a/src/net/torvald/terrarum/weather/WeatherMixer.kt +++ b/src/net/torvald/terrarum/weather/WeatherMixer.kt @@ -149,19 +149,21 @@ internal object WeatherMixer : RNGConsumer { weatherbox.initWith(weatherDict["generic01"]!!, 7200L) // TEST FILL WITH RANDOM VALUES - (0..4).map { takeUniformRand(0f..1f) }.let { - weatherbox.windDir.pM1 = it[0] - weatherbox.windDir.p0 = it[1] - weatherbox.windDir.p1 = it[2] - weatherbox.windDir.p2 = it[3] - weatherbox.windDir.p3 = it[4] + (0..6).map { takeUniformRand(0f..1f) }.let { + weatherbox.windDir.pM2 = it[1] + weatherbox.windDir.pM1 = it[2] + weatherbox.windDir.p0 = it[3] + weatherbox.windDir.p1 = it[4] + weatherbox.windDir.p2 = it[5] + weatherbox.windDir.p3 = it[6] } - (0..4).map { takeUniformRand(-1f..1f) }.let { - weatherbox.windSpeed.pM1 = currentWeather.getRandomWindSpeed(it[0]) - weatherbox.windSpeed.p0 = currentWeather.getRandomWindSpeed(it[1]) - weatherbox.windSpeed.p1 = currentWeather.getRandomWindSpeed(it[2]) - weatherbox.windSpeed.p2 = currentWeather.getRandomWindSpeed(it[3]) - weatherbox.windSpeed.p3 = currentWeather.getRandomWindSpeed(it[4]) + (0..6).map { takeUniformRand(-1f..1f) }.let { + weatherbox.windSpeed.pM2 = currentWeather.getRandomWindSpeed(it[1]) + weatherbox.windSpeed.pM1 = currentWeather.getRandomWindSpeed(it[2]) + weatherbox.windSpeed.p0 = currentWeather.getRandomWindSpeed(it[3]) + weatherbox.windSpeed.p1 = currentWeather.getRandomWindSpeed(it[4]) + weatherbox.windSpeed.p2 = currentWeather.getRandomWindSpeed(it[5]) + weatherbox.windSpeed.p3 = currentWeather.getRandomWindSpeed(it[6]) } } @@ -405,8 +407,8 @@ internal object WeatherMixer : RNGConsumer { Vector3(x, y, z) } -1, 3, 7 -> { // z = 0 - val z = 0.1f val posXscr = FastMath.interpolateLinear(rr, -halfCloudSize, App.scr.width + halfCloudSize) + val z = WeatherObjectCloud.worldYtoWorldZforScreenYof0(y) val x = WeatherObjectCloud.screenXtoWorldX(posXscr, Z_LIM) Vector3(x, y, z) } diff --git a/src/net/torvald/terrarum/weather/WeatherObjectCloud.kt b/src/net/torvald/terrarum/weather/WeatherObjectCloud.kt index 9ea40951a..5ec20c6d1 100644 --- a/src/net/torvald/terrarum/weather/WeatherObjectCloud.kt +++ b/src/net/torvald/terrarum/weather/WeatherObjectCloud.kt @@ -6,7 +6,6 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion import com.badlogic.gdx.math.Vector3 import com.jme3.math.FastMath import net.torvald.terrarum.App -import net.torvald.terrarum.App.printdbg import net.torvald.terrarum.gameworld.GameWorld import kotlin.math.absoluteValue import kotlin.math.pow @@ -70,9 +69,7 @@ class WeatherObjectCloud( } } - private val w = App.scr.halfwf - private val h = App.scr.hf * 0.5f - private val vecMult = Vector3(1f, 1f, 1f / (4f * h)) + private val vecMult = Vector3(1f, 1f, 1f / (4f * H)) private fun roundRgbGamma(x: Float): Int { return RGB_GAMMA_TABLE.mapIndexed { i, f -> (f - x).absoluteValue to i }.minBy { it.first }.second @@ -118,10 +115,10 @@ class WeatherObjectCloud( get() { val x = posX - texture.regionWidth * scale * 0.5f val y = posY - texture.regionHeight * scale - val z = posZ // must be at least 1.0 + val z = posZ // must be larger than 0 - val drawX = (x + w * (z-1)) / z - val drawY = (y + h * (z-1)) / z + val drawX = (x + W * (z-1)) / z + val drawY = (y + H * (z-1)) / z val drawScale = scale / z return Vector3(drawX, drawY, drawScale) @@ -137,9 +134,9 @@ class WeatherObjectCloud( val y = posY - texture.regionHeight * scale val z = posZ // must be larger than 0 - val drawXL = (xL + w * (z-1)) / z - val drawXR = (xR + w * (z-1)) / z - val drawY = (y + h * (z-1)) / z + val drawXL = (xL + W * (z-1)) / z + val drawXR = (xR + W * (z-1)) / z + val drawY = (y + H * (z-1)) / z return Vector3(drawXL, drawY, drawXR) } @@ -151,9 +148,9 @@ class WeatherObjectCloud( val y = posY - texture.regionHeight * scale val z = FastMath.interpolateLinear(posZ / ALPHA_ROLLOFF_Z, ALPHA_ROLLOFF_Z / 4f, ALPHA_ROLLOFF_Z) - val drawXL = (xL + w * (z-1)) / z - val drawXR = (xR + w * (z-1)) / z - val drawY = (y + h * (z-1)) / z + val drawXL = (xL + W * (z-1)) / z + val drawXR = (xR + W * (z-1)) / z + val drawY = (y + H * (z-1)) / z return Vector3(drawXL, drawY, drawXR) } @@ -162,7 +159,19 @@ class WeatherObjectCloud( override fun compareTo(other: WeatherObjectCloud): Int = (other.posZ - this.posZ).sign.toInt() companion object { - fun screenXtoWorldX(screenX: Float, z: Float) = screenX * z - App.scr.halfwf * (z - 1f) + private val W = App.scr.halfwf + private val H = App.scr.hf * 0.5f + + /** + * Given screen-x and world-z position, calculates a world-x position that would make the cloud appear at the given screen-x position + */ + fun screenXtoWorldX(screenX: Float, z: Float) = screenX * z - W * (z - 1f) // rearrange screenCoord equations to derive this eq :p + + /** + * Given a world-y position, calculates a world-z position that would put the cloud to screen-y of zero + */ + fun worldYtoWorldZforScreenYof0(y: Float) = 1f - (y / H) // rearrange screenCoord equations to derive this eq :p + const val ALPHA_ROLLOFF_Z = 64f const val OLD_AGE_DECAY = 4000f diff --git a/src/net/torvald/terrarum/weather/Weatherbox.kt b/src/net/torvald/terrarum/weather/Weatherbox.kt index e2630cc57..be831207a 100644 --- a/src/net/torvald/terrarum/weather/Weatherbox.kt +++ b/src/net/torvald/terrarum/weather/Weatherbox.kt @@ -5,6 +5,8 @@ import net.torvald.terrarum.App.printdbg import net.torvald.terrarum.floorToInt import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.fmod +import org.apache.commons.math3.analysis.interpolation.AkimaSplineInterpolator +import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction import java.util.* import kotlin.math.max import kotlin.math.pow @@ -92,18 +94,22 @@ open class WeatherStateBox( // - removing p4 and beyond: for faster response to the changing weather schedule and make the forecasting less accurate like irl ) { - open val value: Float; get() = interpolate(x, p0, p1, p2, p3) - open fun valueAt(x: Float) = when (x.floorToInt()) { - -2 -> interpolate(x + 2, pM2,pM1, p0, p1) - -1 -> interpolate(x + 1, pM1, p0, p1, p2) - 0 -> interpolate(x - 0, p0, p1, p2, p3) - 1 -> interpolate(x - 1, p1, p2, p3, p3) - 2 -> interpolate(x - 2, p2, p3, p3, p3) - 3 -> interpolate(x - 3, p3, p3, p3, p3) - else -> throw IllegalArgumentException() + protected lateinit var polynomial: PolynomialSplineFunction + protected val interpolator = AkimaSplineInterpolator() + + open val value: Float; get() = valueAt(x) + open fun valueAt(x: Float) = polynomial.value(x + 1.0).toFloat() + + open protected fun updatePolynomials() { + polynomial = interpolator.interpolate( + doubleArrayOf(-2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0), + doubleArrayOf(pM2.toDouble(), pM1.toDouble(), p0.toDouble(), p1.toDouble(), p2.toDouble(), p3.toDouble(), p3.toDouble()) + ) } open fun update(xdelta: Float, next: () -> Float) { + if (!::polynomial.isInitialized) updatePolynomials() + synchronized(WeatherMixer.RNG) { x += xdelta while (x >= 1.0) { @@ -114,25 +120,10 @@ open class WeatherStateBox( p1 = p2 p2 = p3 p3 = next() - - -// p3 = p4 -// p4 = p5 -// p5 = next() } + updatePolynomials() } } - protected fun interpolate(u: Float, p0: Float, p1: Float, p2: Float, p3: Float): Float { - val T = FastMath.interpolateLinear(u, p1, p2).div(max(p0, p3).coerceAtLeast(1f)).toDouble().coerceIn(0.0, 0.5) -// if (u == x) printdbg(this, "u=$u, p1=$p1, p2=$p2; T=$T") - - val c1 = p1.toDouble() - val c2 = -1.0 * T * p0 + T * p2 - val c3 = 2 * T * p0 + (T - 3) * p1 + (3 - 2 * T) * p2 + -T * p3 - val c4 = -T * p0 + (2 - T) * p1 + (T - 2) * p2 + T * p3 - - return (((c4 * u + c3) * u + c2) * u + c1).toFloat() - } } /** @@ -147,9 +138,9 @@ class WeatherDirBox( p2: Float = 0f, p3: Float = 0f, ) : WeatherStateBox(x, pM2, pM1, p0, p1, p2, p3) { - override val value; get() = valueAt(x) + override fun valueAt(x: Float) = polynomial.value(x + 1.0).plus(2.0).fmod(4.0).minus(2.0).toFloat() - override fun valueAt(x: Float): Float { + override fun updatePolynomials() { var pM2 = pM2 var pM1 = pM1 var p0 = p0 @@ -157,55 +148,50 @@ class WeatherDirBox( var p2 = p2 var p3 = p3 - if (x < -2f) { - if (pM1 - pM2 > 2f) { - pM2 -= 4f - pM1 -= 4f - p0 -= 4f - p1 -= 4f - p2 -= 4f - p3 -= 4f - } - else if (pM1 - pM2 < -2f) { - pM2 += 4f - pM1 += 4f - p0 += 4f - p1 += 4f - p2 += 4f - p3 += 4f - } + + if (pM1 - pM2 > 2f) { + pM2 -= 4f + pM1 -= 4f + p0 -= 4f + p1 -= 4f + p2 -= 4f + p3 -= 4f + } + else if (pM1 - pM2 < -2f) { + pM2 += 4f + pM1 += 4f + p0 += 4f + p1 += 4f + p2 += 4f + p3 += 4f } - if (x < -1f) { - if (pM1 - pM2 > 2f) { - pM1 -= 4f - p0 -= 4f - p1 -= 4f - p2 -= 4f - p3 -= 4f - } - else if (pM1 - pM2 < -2f) { - pM1 += 4f - p0 += 4f - p1 += 4f - p2 += 4f - p3 += 4f - } + if (pM1 - pM2 > 2f) { + pM1 -= 4f + p0 -= 4f + p1 -= 4f + p2 -= 4f + p3 -= 4f + } + else if (pM1 - pM2 < -2f) { + pM1 += 4f + p0 += 4f + p1 += 4f + p2 += 4f + p3 += 4f } - if (x < 0f) { - if (p0 - pM1 > 2f) { - p0 -= 4f - p1 -= 4f - p2 -= 4f - p3 -= 4f - } - else if (p0 - pM1 < -2f) { - p0 += 4f - p1 += 4f - p2 += 4f - p3 += 4f - } + if (p0 - pM1 > 2f) { + p0 -= 4f + p1 -= 4f + p2 -= 4f + p3 -= 4f + } + else if (p0 - pM1 < -2f) { + p0 += 4f + p1 += 4f + p2 += 4f + p3 += 4f } if (p1 - p0 > 2f) { @@ -235,25 +221,9 @@ class WeatherDirBox( p3 += 4f } - return when (x.floorToInt()) { - -2 -> interpolate2(x + 2, pM2,pM1, p0, p1) - -1 -> interpolate2(x + 1, pM1, p0, p1, p2) - 0 -> interpolate2(x - 0, p0, p1, p2, p3) - 1 -> interpolate2(x - 1, p1, p2, p3, p3) - 2 -> interpolate2(x - 2, p2, p3, p3, p3) - 3 -> interpolate2(x - 3, p3, p3, p3, p3) - else -> throw IllegalArgumentException() - }.plus(2f).fmod(4f).minus(2f) - } - - private fun interpolate2(u: Float, p0: Float, p1: Float, p2: Float, p3: Float): Float { - val T = 0.5 - - val c1 = p1.toDouble() - val c2 = -1.0 * T * p0 + T * p2 - val c3 = 2 * T * p0 + (T - 3) * p1 + (3 - 2 * T) * p2 + -T * p3 - val c4 = -T * p0 + (2 - T) * p1 + (T - 2) * p2 + T * p3 - - return (((c4 * u + c3) * u + c2) * u + c1).toFloat() + polynomial = interpolator.interpolate( + doubleArrayOf(-2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0), + doubleArrayOf(pM2.toDouble(), pM1.toDouble(), p0.toDouble(), p1.toDouble(), p2.toDouble(), p3.toDouble(), p3.toDouble()) + ) } } \ No newline at end of file