weather sched progression wip

This commit is contained in:
minjaesong
2023-09-01 23:56:48 +09:00
parent abee176ef4
commit 66a103bd3a
8 changed files with 178 additions and 94 deletions

View File

@@ -1,8 +1,9 @@
{ {
"identifier": "generic01",
"skyboxGradColourMap": "generic_skybox.tga", "skyboxGradColourMap": "generic_skybox.tga",
"daylightClut": "clut_daylight.tga", "daylightClut": "clut_daylight.tga",
"classification": "generic", "classification": "generic",
"cloudChance": 250, "cloudChance": 125,
"cloudGamma": [0.48, 2.4], "cloudGamma": [0.48, 2.4],
"cloudGammaVariance": [0.1, 0.1], "cloudGammaVariance": [0.1, 0.1],
"windSpeed": 0.25, "windSpeed": 0.25,
@@ -10,13 +11,13 @@
"clouds": { "clouds": {
"cumulonimbus": { "cumulonimbus": {
"filename": "cloud_large.png", "tw": 2048, "th": 1024, "probability": 0.2, "filename": "cloud_large.png", "tw": 2048, "th": 1024, "probability": 0.2,
"baseScale": 2.0, "scaleVariance": 0.3333333, "baseScale": 4.0, "scaleVariance": 0.3333333,
"altLow": 80, "altHigh": 120 "altLow": 1080, "altHigh": 1120
}, },
"cumulus": { "cumulus": {
"filename": "cloud_normal.png", "tw": 1024, "th": 512, "probability": 1.0, "filename": "cloud_normal.png", "tw": 1024, "th": 512, "probability": 1.0,
"baseScale": 1.0, "scaleVariance": 0.6, "baseScale": 2.0, "scaleVariance": 0.6,
"altLow": 80, "altHigh": 800 "altLow": 1080, "altHigh": 1800
} }
}, },
"atmoTurbidity": 3.5 "atmoTurbidity": 3.5

View File

@@ -1,8 +1,9 @@
{ {
"identifier": "generic02",
"skyboxGradColourMap": "generic_skybox.tga", "skyboxGradColourMap": "generic_skybox.tga",
"daylightClut": "clut_daylight.tga", "daylightClut": "clut_daylight.tga",
"classification": "generic2", "classification": "generic2",
"cloudChance": 1600, "cloudChance": 800,
"cloudGamma": [0.48, 2.4], "cloudGamma": [0.48, 2.4],
"cloudGammaVariance": [0.1, 0.1], "cloudGammaVariance": [0.1, 0.1],
"windSpeed": 0.25, "windSpeed": 0.25,
@@ -10,18 +11,18 @@
"clouds": { "clouds": {
"cumulonimbus": { "cumulonimbus": {
"filename": "cloud_large.png", "tw": 2048, "th": 1024, "probability": 0.02, "filename": "cloud_large.png", "tw": 2048, "th": 1024, "probability": 0.02,
"baseScale": 2.0, "scaleVariance": 0.3333333, "baseScale": 4.0, "scaleVariance": 0.3333333,
"altLow": 80, "altHigh": 120 "altLow": 1080, "altHigh": 1120
}, },
"cumulus": { "cumulus": {
"filename": "cloud_normal.png", "tw": 1024, "th": 512, "probability": 0.05, "filename": "cloud_normal.png", "tw": 1024, "th": 512, "probability": 0.05,
"baseScale": 1.0, "scaleVariance": 0.6, "baseScale": 2.0, "scaleVariance": 0.6,
"altLow": 80, "altHigh": 800 "altLow": 1080, "altHigh": 1800
}, },
"altocumulus": { "altocumulus": {
"filename": "cloud_normal.png", "tw": 1024, "th": 512, "probability": 1.0, "filename": "cloud_normal.png", "tw": 1024, "th": 512, "probability": 1.0,
"baseScale": 0.5, "scaleVariance": 0.1, "baseScale": 1.0, "scaleVariance": 0.1,
"altLow": 1600, "altHigh": 2400 "altLow": 2600, "altHigh": 3400
} }
}, },
"atmoTurbidity": 3.5, "atmoTurbidity": 3.5,

View File

@@ -1,4 +1,5 @@
{ {
"identifier": "overcast01",
"skyboxGradColourMap": "generic_skybox.tga", "skyboxGradColourMap": "generic_skybox.tga",
"daylightClut": "clut_daylight.tga", "daylightClut": "clut_daylight.tga",
"classification": "overcast", "classification": "overcast",
@@ -10,18 +11,18 @@
"clouds": { "clouds": {
"cumulus": { "cumulus": {
"filename": "cloud_normal.png", "tw": 1024, "th": 512, "probability": 0.1, "filename": "cloud_normal.png", "tw": 1024, "th": 512, "probability": 0.1,
"baseScale": 0.8, "scaleVariance": 0.6, "baseScale": 1.6, "scaleVariance": 0.6,
"altLow": 80, "altHigh": 800 "altLow": 580, "altHigh": 1300
}, },
"cumulonimbus": { "cumulonimbus": {
"filename": "cloud_large.png", "tw": 2048, "th": 1024, "probability": 0.4, "filename": "cloud_large.png", "tw": 2048, "th": 1024, "probability": 0.4,
"baseScale": 2.0, "scaleVariance": 0.3333333, "baseScale": 4.0, "scaleVariance": 0.3333333,
"altLow": 90, "altHigh": 120 "altLow": 590, "altHigh":60
}, },
"nimbostratus": { "nimbostratus": {
"filename": "cloud_wide.png", "tw": 4096, "th": 1024, "probability": 1.0, "filename": "cloud_wide.png", "tw": 4096, "th": 1024, "probability": 1.0,
"baseScale": 4.0, "scaleVariance": 0.1, "baseScale": 8.0, "scaleVariance": 0.1,
"altLow": 100, "altHigh": 100 "altLow": 600, "altHigh": 700
} }
}, },
"atmoTurbidity": 9.5 "atmoTurbidity": 9.5

View File

@@ -14,6 +14,8 @@ import net.torvald.terrarum.savegame.ByteArray64GrowableOutputStream
import net.torvald.terrarum.savegame.ByteArray64InputStream import net.torvald.terrarum.savegame.ByteArray64InputStream
import net.torvald.terrarum.savegame.ByteArray64Reader import net.torvald.terrarum.savegame.ByteArray64Reader
import net.torvald.terrarum.utils.* import net.torvald.terrarum.utils.*
import net.torvald.terrarum.weather.BaseModularWeather
import net.torvald.terrarum.weather.WeatherMixer
import net.torvald.terrarum.weather.WeatherStateBox import net.torvald.terrarum.weather.WeatherStateBox
import org.apache.commons.codec.digest.DigestUtils import org.apache.commons.codec.digest.DigestUtils
import java.io.File import java.io.File
@@ -217,15 +219,25 @@ object Common {
// WeatherStateBox // WeatherStateBox
jsoner.setSerializer(WeatherStateBox::class.java, object : Json.Serializer<WeatherStateBox> { jsoner.setSerializer(WeatherStateBox::class.java, object : Json.Serializer<WeatherStateBox> {
override fun write(json: Json, obj: WeatherStateBox, knownType: Class<*>?) { override fun write(json: Json, obj: WeatherStateBox, knownType: Class<*>?) {
json.writeValue("${obj.x};${obj.p0};${obj.p1};${obj.p2};${obj.p3}") json.writeValue("${obj.x};${obj.pM2};${obj.pM1};${obj.p0};${obj.p1};${obj.p2};${obj.p3}")
} }
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): WeatherStateBox { override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): WeatherStateBox {
return jsonData.asString().split(';').map { it.toFloat() }.let { return jsonData.asString().split(';').map { it.toFloat() }.let {
WeatherStateBox(it[0], it[1], it[2], it[3], it[4]) WeatherStateBox(it[0], it[1], it[2], it[3], it[4], it[5], it[6])
} }
} }
}) })
// BaseModularWeather
jsoner.setSerializer(BaseModularWeather::class.java, object : Json.Serializer<BaseModularWeather> {
override fun write(json: Json, obj: BaseModularWeather, knownType: Class<*>?) {
json.writeValue(obj.identifier)
}
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): BaseModularWeather {
return WeatherMixer.weatherDict[jsonData.asString()]!!
}
})
} }

View File

@@ -112,6 +112,10 @@ class BasicDebugInfoWindow : UICanvas() {
if (this == null) "null" else if (this.isNaN()) "NaN" else if (this.isInfinite()) "${if (this >= 0.0) '+' else '-'}Inf" else if (this == null) "null" else if (this.isNaN()) "NaN" else if (this.isInfinite()) "${if (this >= 0.0) '+' else '-'}Inf" else
"${((if (this >= 0.0) "" else "-") + "${this.absoluteValue.toInt()}").padStart(intLen)}." + "${((if (this >= 0.0) "" else "-") + "${this.absoluteValue.toInt()}").padStart(intLen)}." +
(10.0 pow fracLen.toDouble()).let { d -> (this.absoluteValue.times(d) % d).toInt().toString().padStart(fracLen, '0').padEnd(fracLen) } (10.0 pow fracLen.toDouble()).let { d -> (this.absoluteValue.times(d) % d).toInt().toString().padStart(fracLen, '0').padEnd(fracLen) }
private fun Float?.toIntAndFrac(intLen: Int, fracLen: Int = 4): String =
if (this == null) "null" else if (this.isNaN()) "NaN" else if (this.isInfinite()) "${if (this >= 0.0) '+' else '-'}Inf" else
"${((if (this >= 0.0) "" else "-") + "${this.absoluteValue.toInt()}").padStart(intLen)}." +
(10.0 pow fracLen.toDouble()).let { d -> (this.absoluteValue.times(d) % d).toInt().toString().padStart(fracLen, '0').padEnd(fracLen) }
private val gap = 14f private val gap = 14f
@@ -236,10 +240,10 @@ class BasicDebugInfoWindow : UICanvas() {
try { try {
world?.let { world?.let {
val valRaw = LightmapRenderer.getLight(mouseTileX, mouseTileY) val valRaw = LightmapRenderer.getLight(mouseTileX, mouseTileY)
val rawR = valRaw?.r?.toDouble().toIntAndFrac(1,3) val rawR = valRaw?.r?.toIntAndFrac(1,3)
val rawG = valRaw?.g?.toDouble().toIntAndFrac(1,3) val rawG = valRaw?.g?.toIntAndFrac(1,3)
val rawB = valRaw?.b?.toDouble().toIntAndFrac(1,3) val rawB = valRaw?.b?.toIntAndFrac(1,3)
val rawA = valRaw?.a?.toDouble().toIntAndFrac(1,3) val rawA = valRaw?.a?.toIntAndFrac(1,3)
val wallNum = it.getTileFromWall(mouseTileX, mouseTileY) val wallNum = it.getTileFromWall(mouseTileX, mouseTileY)
val tileNum = it.getTileFromTerrain(mouseTileX, mouseTileY) val tileNum = it.getTileFromTerrain(mouseTileX, mouseTileY)
@@ -249,7 +253,7 @@ class BasicDebugInfoWindow : UICanvas() {
App.fontSmallNumbers.draw(batch, "$ccO$TERRAIN$ccG$tileNum", gap + 7f*(tileCursX + 3), line(tileCursY)) App.fontSmallNumbers.draw(batch, "$ccO$TERRAIN$ccG$tileNum", gap + 7f*(tileCursX + 3), line(tileCursY))
App.fontSmallNumbers.draw(batch, "$ccO$WALL$ccG$wallNum", gap + 7f*(tileCursX + 3), line(tileCursY + 1)) App.fontSmallNumbers.draw(batch, "$ccO$WALL$ccG$wallNum", gap + 7f*(tileCursX + 3), line(tileCursY + 1))
App.fontSmallNumbers.draw(batch, "$ccO$LIQUID$ccG${fluid.type.value.toString().padEnd(3)}$ccO$BEAKER$ccG${fluid.amount}f", gap + 7f*(tileCursX + 3), line(tileCursY + 2)) App.fontSmallNumbers.draw(batch, "$ccO$LIQUID$ccG${fluid.type.value.toString().padEnd(3)}$ccO$BEAKER$ccG${fluid.amount.toIntAndFrac(2)}", gap + 7f*(tileCursX + 3), line(tileCursY + 2))
App.fontSmallNumbers.draw(batch, "$ccO$WIRE$ccG$wireCount ${ccY}X$ccO$mouseTileX ${ccY}Y$ccO$mouseTileY", gap + 7f*(tileCursX + 3), line(tileCursY + 3)) App.fontSmallNumbers.draw(batch, "$ccO$WIRE$ccG$wireCount ${ccY}X$ccO$mouseTileX ${ccY}Y$ccO$mouseTileY", gap + 7f*(tileCursX + 3), line(tileCursY + 3))
App.fontSmallNumbers.draw(batch, "$ccR$rawR $ccG$rawG $ccB$rawB $ccW$rawA", gap + 7f*(tileCursX + 3), line(tileCursY + 4)) App.fontSmallNumbers.draw(batch, "$ccR$rawR $ccG$rawG $ccB$rawB $ccW$rawA", gap + 7f*(tileCursX + 3), line(tileCursY + 4))
@@ -344,8 +348,19 @@ class BasicDebugInfoWindow : UICanvas() {
private fun drawWeatherInfo(batch: SpriteBatch) { private fun drawWeatherInfo(batch: SpriteBatch) {
drawWeatherStateBox(batch, WeatherMixer.weatherbox.windSpeed, "WindSpd", App.scr.width - 170, App.scr.height - 140 - 120, WeatherMixer.currentWeather.windSpeed * (1.0 + WeatherMixer.currentWeather.windSpeedVariance)) val drawX = App.scr.width - 170
drawWeatherStateBox(batch, WeatherMixer.weatherbox.windDir, "WindDir", App.scr.width - 170, App.scr.height - 140) val drawXf = drawX.toFloat()
drawWeatherStateBox(batch, WeatherMixer.weatherbox.windSpeed, "WindSpd", drawX, App.scr.height - 140 - 120, WeatherMixer.currentWeather.windSpeed * (1.0 + WeatherMixer.currentWeather.windSpeedVariance))
drawWeatherStateBox(batch, WeatherMixer.weatherbox.windDir, "WindDir", drawX, App.scr.height - 140)
// draw weather schedule
val schedYstart = App.scr.height - 140 - 120 - 13f * (WeatherMixer.weatherbox.weatherSchedule.size + 3)
App.fontSmallNumbers.draw(batch, "$ccY== WeatherSched [${WeatherMixer.weatherbox.weatherSchedule.size}] ==", drawXf, schedYstart)
WeatherMixer.weatherbox.weatherSchedule.forEachIndexed { index, weather ->
val sek = if (index == 0) WeatherMixer.weatherbox.updateAkku else 0
App.fontSmallNumbers.draw(batch, "$ccY${weather.weather.identifier} $ccG${weather.duration - sek}", drawXf, schedYstart + 13 * (index + 1))
}
} }
private val colHairline = Color(0xf22100ff.toInt()) private val colHairline = Color(0xf22100ff.toInt())
@@ -468,7 +483,7 @@ class BasicDebugInfoWindow : UICanvas() {
// text // text
batch.color = Color.WHITE batch.color = Color.WHITE
App.fontSmallNumbers.draw(batch, "$ccY$label $ccG${box.value().toDouble().toIntAndFrac(3)}", x.toFloat(), y - 16f) App.fontSmallNumbers.draw(batch, "$ccY$label $ccG${box.value.toDouble().toIntAndFrac(3)}", x.toFloat(), y - 15f)
} }
private val processorName = App.processor.replace(Regex(""" Processor|( CPU)? @ [0-9.]+GHz"""), "") + if (App.is32BitJVM) " (32-bit)" else "" private val processorName = App.processor.replace(Regex(""" Processor|( CPU)? @ [0-9.]+GHz"""), "") + if (App.is32BitJVM) " (32-bit)" else ""

View File

@@ -17,6 +17,7 @@ import kotlin.math.absoluteValue
* Created by minjaesong on 2016-07-11. * Created by minjaesong on 2016-07-11.
*/ */
data class BaseModularWeather( data class BaseModularWeather(
val identifier: String,
val json: JsonValue, val json: JsonValue,
var skyboxGradColourMap: GdxColorMap, // row 0: skybox grad top, row 1: skybox grad bottom, row 2: sunlight (RGBA) var skyboxGradColourMap: GdxColorMap, // row 0: skybox grad top, row 1: skybox grad bottom, row 2: sunlight (RGBA)
val daylightClut: GdxColorMap, val daylightClut: GdxColorMap,
@@ -30,9 +31,8 @@ data class BaseModularWeather(
val mixFrom: String? = null, val mixFrom: String? = null,
val mixPercentage: Double? = null, val mixPercentage: Double? = null,
var forceWindVec: Vector3? = null
) { ) {
/** /**
* @param rnd random number between -1 and +1 * @param rnd random number between -1 and +1
*/ */

View File

@@ -29,7 +29,6 @@ import java.io.File
import java.io.FileFilter import java.io.FileFilter
import java.lang.Double.doubleToLongBits import java.lang.Double.doubleToLongBits
import java.lang.Math.toDegrees import java.lang.Math.toDegrees
import java.lang.Math.toRadians
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashMap import kotlin.collections.HashMap
import kotlin.math.* import kotlin.math.*
@@ -52,16 +51,32 @@ import kotlin.math.*
*/ */
internal object WeatherMixer : RNGConsumer { internal object WeatherMixer : RNGConsumer {
val DEFAULT_WEATHER = BaseModularWeather(
"default",
JsonValue(JsonValue.ValueType.`object`),
GdxColorMap(1, 3, Color(0x55aaffff), Color(0xaaffffff.toInt()), Color.WHITE),
GdxColorMap(2, 2, Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE),
"default",
0f,
0f,
0f,
Vector2(1f, 1f),
Vector2(0f, 0f),
listOf()
)
override val RNG = HQRNG() override val RNG = HQRNG()
var globalLightOverridden = false var globalLightOverridden = false
var weatherList: HashMap<String, ArrayList<BaseModularWeather>> val weatherDB: HashMap<String, ArrayList<BaseModularWeather>> // search by classification
val weatherDict: HashMap<String, BaseModularWeather> // search by identifier
var currentWeather: BaseModularWeather val currentWeather: BaseModularWeather
var nextWeather: BaseModularWeather get() = weatherbox.currentWeather
// var nextWeather: BaseModularWeather
lateinit var mixedWeather: BaseModularWeather private var forceWindVec: Vector3? = null
val globalLightNow = Cvec(0) val globalLightNow = Cvec(0)
private val cloudDrawColour = Color() private val cloudDrawColour = Color()
@@ -105,13 +120,11 @@ internal object WeatherMixer : RNGConsumer {
override fun loadFromSave(s0: Long, s1: Long) { override fun loadFromSave(s0: Long, s1: Long) {
super.loadFromSave(s0, s1) super.loadFromSave(s0, s1)
currentWeather = weatherList[WEATHER_GENERIC]!![0]
internalReset(s0, s1) internalReset(s0, s1)
initClouds() initClouds()
} }
fun internalReset() { fun internalReset() {
currentWeather = weatherList[WEATHER_GENERIC]!![0]
internalReset(RNG.state0, RNG.state1) internalReset(RNG.state0, RNG.state1)
initClouds() initClouds()
} }
@@ -127,11 +140,14 @@ internal object WeatherMixer : RNGConsumer {
clouds.clear() clouds.clear()
cloudsSpawned = 0 cloudsSpawned = 0
forceWindVec = null
windVector = Vector3(-0.98f, 0f, 0.21f) windVector = Vector3(-0.98f, 0f, 0.21f)
oldCamPos.set(WorldCamera.camVector) oldCamPos.set(WorldCamera.camVector)
weatherbox = Weatherbox() weatherbox = Weatherbox()
weatherbox.initWith(weatherDict["generic01"]!!, 3600L)
// TEST FILL WITH RANDOM VALUES // TEST FILL WITH RANDOM VALUES
(0..5).map { takeUniformRand(0f..1f) }.let { (0..5).map { takeUniformRand(0f..1f) }.let {
weatherbox.windDir.pM1 = it[0] weatherbox.windDir.pM1 = it[0]
@@ -152,7 +168,8 @@ internal object WeatherMixer : RNGConsumer {
} }
init { init {
weatherList = HashMap<String, ArrayList<BaseModularWeather>>() weatherDB = HashMap<String, ArrayList<BaseModularWeather>>()
weatherDict = HashMap<String, BaseModularWeather>()
// read weather descriptions from assets/weather (modular weather) // read weather descriptions from assets/weather (modular weather)
@@ -169,40 +186,16 @@ internal object WeatherMixer : RNGConsumer {
for ((modname, raw) in weatherRawValidList) { for ((modname, raw) in weatherRawValidList) {
val weather = readFromJson(modname, raw) val weather = readFromJson(modname, raw)
weatherDict[weather.identifier] = weather
// if List for the classification does not exist, make one // if List for the classification does not exist, make one
if (!weatherList.containsKey(weather.classification)) if (!weatherDB.containsKey(weather.classification))
weatherList.put(weather.classification, ArrayList()) weatherDB.put(weather.classification, ArrayList())
weatherList[weather.classification]!!.add(weather) weatherDB[weather.classification]!!.add(weather)
} }
weatherDict["titlescreen"] = weatherDB[WEATHER_GENERIC]!![0].copy(windSpeed = 1f)
// initialise
try {
weatherList["titlescreen"] = arrayListOf(weatherList[WEATHER_GENERIC]!![0].copy(windSpeed = 1f))
currentWeather = weatherList[WEATHER_GENERIC]!![0]
nextWeather = getRandomWeather(WEATHER_GENERIC)
}
catch (e: NullPointerException) {
e.printStackTrace()
val defaultWeather = BaseModularWeather(
JsonValue(JsonValue.ValueType.`object`),
GdxColorMap(1, 3, Color(0x55aaffff), Color(0xaaffffff.toInt()), Color.WHITE),
GdxColorMap(2, 2, Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE),
"default",
0f,
0f,
0f,
Vector2(1f, 1f),
Vector2(0f, 0f),
listOf()
)
currentWeather = defaultWeather
nextWeather = defaultWeather
}
} }
/** /**
@@ -213,7 +206,8 @@ internal object WeatherMixer : RNGConsumer {
// currentWeather = weatherList[WEATHER_GENERIC]!![0] // force set weather // currentWeather = weatherList[WEATHER_GENERIC]!![0] // force set weather
updateWind(delta, world) weatherbox.update(world)
updateWind()
updateClouds(delta, world) updateClouds(delta, world)
@@ -230,12 +224,11 @@ internal object WeatherMixer : RNGConsumer {
this[this.lastIndex] = f this[this.lastIndex] = f
} }
private val HALF_PI = 1.5707964f
private val PI = 3.1415927f private val PI = 3.1415927f
private val TWO_PI = 6.2831855f private val TWO_PI = 6.2831855f
private val THREE_PI = 9.424778f private val THREE_PI = 9.424778f
private val WIND_DIR_TIME_UNIT = 14400f // every 4hr
private val WIND_SPEED_TIME_UNIT = 3600f // every 1hr
// see: https://stackoverflow.com/questions/2708476/rotation-interpolation/14498790#14498790 // see: https://stackoverflow.com/questions/2708476/rotation-interpolation/14498790#14498790
private fun getShortestAngle(start: Float, end: Float) = private fun getShortestAngle(start: Float, end: Float) =
@@ -243,22 +236,20 @@ internal object WeatherMixer : RNGConsumer {
if (it > PI) it - TWO_PI else it if (it > PI) it - TWO_PI else it
} }
private fun updateWind(delta: Float, world: GameWorld) { private fun updateWind() {
val currentWindSpeed = weatherbox.windSpeed.value
val currentWindDir = weatherbox.windDir.value * HALF_PI
val currentWindSpeed = weatherbox.windSpeed.getAndUpdate( world.worldTime.timeDelta / WIND_SPEED_TIME_UNIT) { printdbg(this, "Wind speed = $currentWindSpeed")
currentWeather.getRandomWindSpeed(takeUniformRand(-1f..1f))
}
val currentWindDir = weatherbox.windDir.getAndUpdate( world.worldTime.timeDelta / WIND_DIR_TIME_UNIT) { RNG.nextFloat() * 4f } * Math.PI * 0.5
if (forceWindVec != null) {
if (currentWeather.forceWindVec != null) { windVector.set(forceWindVec)
windVector.set(currentWeather.forceWindVec)
} }
else { else {
windVector.set( windVector.set(
(cos(currentWindDir) * currentWindSpeed).toFloat(), (cos(currentWindDir) * currentWindSpeed),
0f, 0f,
(sin(currentWindDir) * currentWindSpeed).toFloat() (sin(currentWindDir) * currentWindSpeed)
) )
} }
} }
@@ -280,6 +271,7 @@ internal object WeatherMixer : RNGConsumer {
val camvec2 = camvec.cpy() val camvec2 = camvec.cpy()
val testCamDelta = camvec.cpy().sub(oldCamPos) val testCamDelta = camvec.cpy().sub(oldCamPos)
// adjust camDelta to accomodate ROUNDWORLD
if (testCamDelta.x.absoluteValue > world.width * TILE_SIZEF / 2f) { if (testCamDelta.x.absoluteValue > world.width * TILE_SIZEF / 2f) {
if (testCamDelta.x >= 0) if (testCamDelta.x >= 0)
camvec2.x -= world.width * TILE_SIZEF camvec2.x -= world.width * TILE_SIZEF
@@ -291,7 +283,7 @@ internal object WeatherMixer : RNGConsumer {
camDelta.set(testCamDelta) camDelta.set(testCamDelta)
// try to spawn an cloud
val cloudChanceEveryMin = 60f / (currentWeather.cloudChance * currentWeather.windSpeed) // if chance = 0, the result will be +inf val cloudChanceEveryMin = 60f / (currentWeather.cloudChance * currentWeather.windSpeed) // if chance = 0, the result will be +inf
while (cloudUpdateAkku >= cloudChanceEveryMin) { while (cloudUpdateAkku >= cloudChanceEveryMin) {
@@ -302,12 +294,13 @@ internal object WeatherMixer : RNGConsumer {
var immDespawnCount = 0 var immDespawnCount = 0
val immDespawnCauses = ArrayList<String>() val immDespawnCauses = ArrayList<String>()
printdbg(this, "Wind vector = $windVector")
// move the clouds
clouds.forEach { clouds.forEach {
// do parallax scrolling // do parallax scrolling
it.posX += camDelta.x * cloudParallaxMultX it.posX += camDelta.x * cloudParallaxMultX
it.posY += camDelta.y * cloudParallaxMultY it.posY += camDelta.y * cloudParallaxMultY
it.update(windVector) it.update(windVector)
if (DEBUG_CAUSE_OF_DESPAWN && it.life == 0) { if (DEBUG_CAUSE_OF_DESPAWN && it.life == 0) {
@@ -498,8 +491,8 @@ internal object WeatherMixer : RNGConsumer {
} }
internal fun titleScreenInitWeather() { internal fun titleScreenInitWeather() {
currentWeather = weatherList["titlescreen"]!![0] weatherbox.initWith(weatherDict["titlescreen"]!!, Long.MAX_VALUE)
currentWeather.forceWindVec = Vector3(-0.98f, 0f, -0.21f) forceWindVec = Vector3(-0.98f, 0f, -0.21f)
initClouds() initClouds()
} }
@@ -728,7 +721,7 @@ internal object WeatherMixer : RNGConsumer {
} }
} }
fun getWeatherList(classification: String) = weatherList[classification]!! fun getWeatherList(classification: String) = weatherDB[classification]!!
fun getRandomWeather(classification: String) = fun getRandomWeather(classification: String) =
getWeatherList(classification)[RNG.nextInt(getWeatherList(classification).size)] getWeatherList(classification)[RNG.nextInt(getWeatherList(classification).size)]
@@ -757,7 +750,7 @@ internal object WeatherMixer : RNGConsumer {
val skybox = GdxColorMap(ModMgr.getGdxFile(modname, "$pathToImage/${skyboxInJson}")) val skybox = GdxColorMap(ModMgr.getGdxFile(modname, "$pathToImage/${skyboxInJson}"))
val daylight = GdxColorMap(ModMgr.getGdxFile(modname, "$pathToImage/${lightbox}")) val daylight = GdxColorMap(ModMgr.getGdxFile(modname, "$pathToImage/${lightbox}"))
val identifier = JSON.getString("identifier")
val classification = JSON.getString("classification") val classification = JSON.getString("classification")
@@ -797,6 +790,7 @@ internal object WeatherMixer : RNGConsumer {
return BaseModularWeather( return BaseModularWeather(
identifier = identifier,
json = JSON, json = JSON,
skyboxGradColourMap = skybox, skyboxGradColourMap = skybox,
daylightClut = daylight, daylightClut = daylight,
@@ -811,7 +805,7 @@ internal object WeatherMixer : RNGConsumer {
} }
fun dispose() { fun dispose() {
weatherList.values.forEach { list -> weatherDB.values.forEach { list ->
list.forEach { weather -> list.forEach { weather ->
weather.clouds.forEach { it.spriteSheet.dispose() } weather.clouds.forEach { it.spriteSheet.dispose() }
} }

View File

@@ -1,15 +1,77 @@
package net.torvald.terrarum.weather package net.torvald.terrarum.weather
import com.jme3.math.FastMath
import net.torvald.terrarum.floorToInt import net.torvald.terrarum.floorToInt
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.gameworld.fmod import net.torvald.terrarum.gameworld.fmod
import kotlin.math.absoluteValue import java.util.*
data class WeatherSchedule(val weather: BaseModularWeather = WeatherMixer.DEFAULT_WEATHER, val duration: Long = 3600)
class Weatherbox { class Weatherbox {
companion object {
private val WIND_DIR_TIME_UNIT = 3600f * 5 // every 5hr
private val WIND_SPEED_TIME_UNIT = 3600f * 2 // every 2hr
private val HALF_PIF = 1.5707964f
private val PIF = 3.1415927f
private val TWO_PIF = 6.2831855f
private val THREE_PIF = 9.424778f
}
private fun takeUniformRand(range: ClosedFloatingPointRange<Float>) =
FastMath.interpolateLinear(RNG.nextFloat(), range.start, range.endInclusive)
private fun takeTriangularRand(range: ClosedFloatingPointRange<Float>) =
FastMath.interpolateLinear((RNG.nextFloat() + RNG.nextFloat()) / 2f, range.start, range.endInclusive)
private fun takeGaussianRand(range: ClosedFloatingPointRange<Float>) =
FastMath.interpolateLinear((RNG.nextFloat() + RNG.nextFloat() + RNG.nextFloat() + RNG.nextFloat() + RNG.nextFloat() + RNG.nextFloat() + RNG.nextFloat() + RNG.nextFloat()) / 8f, range.start, range.endInclusive)
val RNG: Random
get() = WeatherMixer.RNG
val windDir = WeatherDirBox() // 0 .. 1.0 val windDir = WeatherDirBox() // 0 .. 1.0
val windSpeed = WeatherStateBox() // 0 .. arbitrarily large number val windSpeed = WeatherStateBox() // 0 .. arbitrarily large number
val weatherSchedule: MutableList<WeatherSchedule> = mutableListOf<WeatherSchedule>()
val currentWeather: BaseModularWeather
get() = weatherSchedule[0].weather
val currentWeatherDuration: Long
get() = weatherSchedule[0].duration
fun initWith(initWeather: BaseModularWeather, duration: Long) {
weatherSchedule.add(WeatherSchedule(initWeather, duration))
}
var updateAkku = 0L; private set
fun update(world: GameWorld) {
updateWind(world)
if (updateAkku >= currentWeatherDuration) {
// TODO add more random weathers
if (weatherSchedule.size == 1) {
val newName = if (currentWeather.identifier == "generic01") "overcast01" else "generic01"
val newDuration = 3600L
weatherSchedule.add(WeatherSchedule(WeatherMixer.weatherDict[newName]!!, newDuration))
println("Queueing next weather '$newName' that will last $newDuration seconds")
}
// subtract akku by old currentWeatherDuration
updateAkku -= weatherSchedule.removeAt(0).duration
}
updateAkku += world.worldTime.timeDelta
}
private fun updateWind(world: GameWorld) {
windSpeed.update(world.worldTime.timeDelta / WIND_SPEED_TIME_UNIT) {
currentWeather.getRandomWindSpeed(takeUniformRand(-1f..1f))
}
windDir.update( world.worldTime.timeDelta / WIND_DIR_TIME_UNIT) { RNG.nextFloat() * 4f }
}
} }
@@ -27,7 +89,7 @@ open class WeatherStateBox(
// - removing p4 and beyond: for faster response to the changing weather schedule and make the forecasting less accurate like irl // - removing p4 and beyond: for faster response to the changing weather schedule and make the forecasting less accurate like irl
) { ) {
open fun value() = interpolate(x, p0, p1, p2, p3) open val value: Float; get() = interpolate(x, p0, p1, p2, p3)
open fun valueAt(x: Float) = when (x.floorToInt()) { open fun valueAt(x: Float) = when (x.floorToInt()) {
-2 -> interpolate(x + 2, pM2,pM1, p0, p1) -2 -> interpolate(x + 2, pM2,pM1, p0, p1)
-1 -> interpolate(x + 1, pM1, p0, p1, p2) -1 -> interpolate(x + 1, pM1, p0, p1, p2)
@@ -38,9 +100,8 @@ open class WeatherStateBox(
else -> throw IllegalArgumentException() else -> throw IllegalArgumentException()
} }
open fun getAndUpdate(xdelta: Float, next: () -> Float): Float { open fun update(xdelta: Float, next: () -> Float) {
synchronized(WeatherMixer.RNG) { synchronized(WeatherMixer.RNG) {
val y = value()
x += xdelta x += xdelta
while (x >= 1.0) { while (x >= 1.0) {
x -= 1.0f x -= 1.0f
@@ -56,7 +117,6 @@ open class WeatherStateBox(
// p4 = p5 // p4 = p5
// p5 = next() // p5 = next()
} }
return y
} }
} }
protected fun interpolate(u: Float, p0: Float, p1: Float, p2: Float, p3: Float): Float { protected fun interpolate(u: Float, p0: Float, p1: Float, p2: Float, p3: Float): Float {
@@ -85,7 +145,7 @@ class WeatherDirBox(
p2: Float = 0f, p2: Float = 0f,
p3: Float = 0f, p3: Float = 0f,
) : WeatherStateBox(x, pM2, pM1, p0, p1, p2, p3) { ) : WeatherStateBox(x, pM2, pM1, p0, p1, p2, p3) {
override fun value() = valueAt(x) override val value; get() = valueAt(x)
override fun valueAt(x: Float): Float { override fun valueAt(x: Float): Float {
var pM2 = pM2 var pM2 = pM2