akima interpolation to the WeatherBox

This commit is contained in:
minjaesong
2023-09-02 22:37:07 +09:00
parent 38c91bd8ec
commit 1e98353b50
9 changed files with 125 additions and 119 deletions

View File

@@ -87,6 +87,7 @@
<element id="extracted-dir" path="$PROJECT_DIR$/lib/regex-22.3.1-edit.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/js-22.3.1-edit.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/graal-sdk-22.3.1.jar" path-in-jar="/" />
<element id="extracted-dir" path="$PROJECT_DIR$/lib/commons-math3-3.6.1.jar" path-in-jar="/" />
</root>
</artifact>
</component>

14
.idea/libraries/apache_commons_math3.xml generated Normal file
View File

@@ -0,0 +1,14 @@
<component name="libraryTable">
<library name="apache.commons.math3" type="repository">
<properties maven-id="org.apache.commons:commons-math3:3.6.1" />
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/commons-math3-3.6.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$PROJECT_DIR$/lib/commons-math3-3.6.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$PROJECT_DIR$/lib/commons-math3-3.6.1-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@@ -25,5 +25,6 @@
<orderEntry type="library" name="jetbrains.kotlin.reflect" level="project" />
<orderEntry type="library" name="jetbrains.kotlin.test" level="project" />
<orderEntry type="library" name="io.github.classgraph" level="project" />
<orderEntry type="library" name="apache.commons.math3" level="project" />
</component>
</module>

Binary file not shown.

Binary file not shown.

BIN
lib/commons-math3-3.6.1.jar LFS Normal file

Binary file not shown.

View File

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

View File

@@ -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

View File

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