cloud stage has more depth

This commit is contained in:
minjaesong
2023-09-06 18:01:33 +09:00
parent 7259ca616c
commit 6c97a9d5ab
13 changed files with 93 additions and 48 deletions

View File

@@ -21,5 +21,6 @@
"altLow": 1080, "altHigh": 1800
}
},
"atmoTurbidity": 3.5
"atmoTurbidity": 3.5,
"shaderVibrancy": [1.0, 1.0]
}

View File

@@ -27,5 +27,6 @@
}
},
"atmoTurbidity": 3.5,
"shaderVibrancy": [1.0, 1.0],
"__comment__": "Make a texture for altocumulus so that this weather can be realised with less than 1000 sprites"
}

View File

@@ -8,7 +8,7 @@
"cloudGammaVariance": [0.0, 0.0],
"windSpeed": 10.45,
"windSpeedVariance": 0.5,
"windSpeedDamping": 0.0,
"windSpeedDamping": 0.5,
"clouds": {
"cumulus": {
"filename": "cloud_normal.png", "tw": 1024, "th": 512, "probability": 0.1,
@@ -26,5 +26,6 @@
"altLow": 1100, "altHigh": 1500
}
},
"atmoTurbidity": 9.5
"atmoTurbidity": 9.5,
"shaderVibrancy": [0.93, 0.9]
}

View File

@@ -13,6 +13,7 @@ import net.torvald.terrarum.App.IS_DEVELOPMENT_BUILD
import net.torvald.terrarum.gamecontroller.KeyToggler
import net.torvald.terrarum.ui.BasicDebugInfoWindow
import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarum.weather.WeatherMixer
/**
* Must be called by the App Loader
@@ -236,6 +237,13 @@ object TerrarumPostProcessor : Disposable {
else
shaderPostNoDither
val (vo, vg) = INGAME.world.weatherbox.let {
if (it.currentWeather.identifier == "titlescreen")
1f to 1f
else
it.currentVibrancy.x to it.currentVibrancy.y
}
App.getCurrentDitherTex().bind(1)
fbo.colorBufferTexture.bind(0)
@@ -245,6 +253,7 @@ object TerrarumPostProcessor : Disposable {
shader.setUniformi("rnd", rng.nextInt(8192), rng.nextInt(8192))
shader.setUniformi("u_pattern", 1)
shader.setUniformf("quant", shaderQuant[App.getConfigInt("displaycolourdepth")] ?: 255f)
shader.setUniformf("vibrancy", 1f, vo, vg, 1f)
shader.setUniformMatrix4fv("swizzler", swizzler, rng.nextInt(24), 16*4)
App.fullscreenQuad.render(shader, GL20.GL_TRIANGLES)

View File

@@ -153,7 +153,7 @@ open class GameWorld(
var weatherbox = Weatherbox()
init {
weatherbox.initWith(WeatherMixer.weatherDict["generic01"]!!, 7200L)
weatherbox.initWith(WeatherMixer.weatherDict["generic01"]!!, 3600L)
val currentWeather = weatherbox.currentWeather
// TEST FILL WITH RANDOM VALUES
(0..6).map { WeatherMixer.takeUniformRand(0f..1f) }.let {
@@ -165,12 +165,13 @@ open class GameWorld(
weatherbox.windDir.p3 = it[6]
}
(0..6).map { WeatherMixer.takeUniformRand(-1f..1f) }.let {
weatherbox.windSpeed.pM2 = currentWeather.getRandomWindSpeed(it[0], it[1])
weatherbox.windSpeed.pM1 = currentWeather.getRandomWindSpeed(it[1], it[2])
weatherbox.windSpeed.p0 = currentWeather.getRandomWindSpeed(it[2], it[3])
weatherbox.windSpeed.p1 = currentWeather.getRandomWindSpeed(it[3], it[4])
weatherbox.windSpeed.p2 = currentWeather.getRandomWindSpeed(it[4], it[5])
weatherbox.windSpeed.p3 = currentWeather.getRandomWindSpeed(it[5], it[6])
val pM3 = currentWeather.getRandomWindSpeed(it[1])
weatherbox.windSpeed.pM2 = currentWeather.getRandomWindSpeed(pM3, it[1])
weatherbox.windSpeed.pM1 = currentWeather.getRandomWindSpeed(weatherbox.windSpeed.pM2, it[2])
weatherbox.windSpeed.p0 = currentWeather.getRandomWindSpeed(weatherbox.windSpeed.pM1, it[3])
weatherbox.windSpeed.p1 = currentWeather.getRandomWindSpeed(weatherbox.windSpeed.p0, it[4])
weatherbox.windSpeed.p2 = currentWeather.getRandomWindSpeed(weatherbox.windSpeed.p1, it[5])
weatherbox.windSpeed.p3 = currentWeather.getRandomWindSpeed(weatherbox.windSpeed.p2, it[6])
}
// the savegame loader will overwrite whatever the initial value we have here

View File

@@ -177,6 +177,7 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
printdbg(this, "Demo world not found, using empty world")
}
this.world = demoWorld
// set initial time to summer
demoWorld.worldTime.addTime(WorldTime.DAY_LENGTH * 32)
@@ -213,7 +214,7 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
IngameRenderer.setRenderedWorld(demoWorld)
WeatherMixer.internalReset(this)
WeatherMixer.titleScreenInitWeather(this.world.weatherbox)
WeatherMixer.titleScreenInitWeather(demoWorld.weatherbox)
// load a half-gradient texture that would be used throughout the titlescreen and its sub UIs

View File

@@ -359,8 +359,10 @@ class BasicDebugInfoWindow : UICanvas() {
val schedYstart = App.scr.height - 140 - 120 - 13f * (weatherbox.weatherSchedule.size + 3)
App.fontSmallNumbers.draw(batch, "$ccY== WeatherSched [${weatherbox.weatherSchedule.size}] ==", drawXf, schedYstart)
weatherbox.weatherSchedule.forEachIndexed { index, weather ->
val sek = if (index == 0) weatherbox.updateAkku else 0
App.fontSmallNumbers.draw(batch, "$ccY${weather.weather.identifier} $ccG${weather.duration - sek}", drawXf, schedYstart + 13 * (index + 1))
val sek = if (index == 1) weatherbox.updateAkku else 0
val cc1 = if (index == 0) ccK else ccY
val cc2 = if (index == 0) ccK else ccG
App.fontSmallNumbers.draw(batch, "$cc1${weather.weather.identifier} $cc2${weather.duration - sek}", drawXf, schedYstart + 13 * (index + 1))
}
}

View File

@@ -29,18 +29,26 @@ data class BaseModularWeather(
val cloudGamma: Vector2,
val cloudGammaVariance: Vector2,
var clouds: List<CloudProps>, // sorted by CloudProps.probability
val shaderVibrancy: FloatArray,
val mixFrom: String? = null,
val mixPercentage: Double? = null,
) {
/**
* @param rnd random number between -1 and +1
*/
fun getRandomWindSpeed(old: Float, rnd: Float): Float {
val v = 1f + rnd.absoluteValue * windSpeedVariance
val r = if (rnd < 0) windSpeed / v else windSpeed * v
return FastMath.interpolateLinear(windSpeedDamping, old, r)
return FastMath.interpolateLinear(1f - windSpeedDamping, old, r)
}
fun getRandomWindSpeed(rnd: Float): Float {
val v = 1f + rnd.absoluteValue * windSpeedVariance
val r = if (rnd < 0) windSpeed / v else windSpeed * v
return r
}
fun getRandomCloudGamma(rnd1: Float, rnd2: Float): Vector2 {

View File

@@ -64,7 +64,8 @@ internal object WeatherMixer : RNGConsumer {
0f,
Vector2(1f, 1f),
Vector2(0f, 0f),
listOf()
listOf(),
floatArrayOf(1f, 1f)
)
override val RNG = HQRNG()
@@ -169,7 +170,7 @@ internal object WeatherMixer : RNGConsumer {
weatherDB[weather.classification]!!.add(weather)
}
weatherDict["titlescreen"] = weatherDB[WEATHER_GENERIC]!![0].copy(windSpeed = 1f)
weatherDict["titlescreen"] = weatherDB[WEATHER_GENERIC]!![0].copy(identifier = "titlescreen", windSpeed = 1f)
}
/**
@@ -459,7 +460,8 @@ internal object WeatherMixer : RNGConsumer {
}
private fun initClouds(currentWeather: BaseModularWeather) {
val hCloudSize = 1024f
clouds.clear()
cloudsSpawned = 0
// multiplier is an empirical value that depends on the 'rZ'
// it does converge at ~6, but having it as an initial state does not make it stay converged
repeat((currentWeather.cloudChance * 1.333f).ceilToInt()) {
@@ -731,13 +733,6 @@ internal object WeatherMixer : RNGConsumer {
val skyboxInJson = JSON.getString("skyboxGradColourMap")
val lightbox = JSON.getString("daylightClut")
val skybox = GdxColorMap(ModMgr.getGdxFile(modname, "$pathToImage/${skyboxInJson}"))
val daylight = GdxColorMap(ModMgr.getGdxFile(modname, "$pathToImage/${lightbox}"))
val identifier = JSON.getString("identifier")
val classification = JSON.getString("classification")
val cloudsMap = ArrayList<CloudProps>()
val clouds = JSON["clouds"]
clouds.forEachSiblings { name, json ->
@@ -755,13 +750,12 @@ internal object WeatherMixer : RNGConsumer {
return BaseModularWeather(
identifier = identifier,
identifier = JSON.getString("identifier"),
json = JSON,
skyboxGradColourMap = skybox,
daylightClut = daylight,
classification = classification,
skyboxGradColourMap = GdxColorMap(ModMgr.getGdxFile(modname, "$pathToImage/${skyboxInJson}")),
daylightClut = GdxColorMap(ModMgr.getGdxFile(modname, "$pathToImage/${lightbox}")),
classification = JSON.getString("classification"),
cloudChance = JSON.getFloat("cloudChance"),
windSpeed = JSON.getFloat("windSpeed"),
windSpeedVariance = JSON.getFloat("windSpeedVariance"),
@@ -769,6 +763,7 @@ internal object WeatherMixer : RNGConsumer {
cloudGamma = JSON["cloudGamma"].asFloatArray().let { Vector2(it[0], it[1]) },
cloudGammaVariance = JSON["cloudGammaVariance"].asFloatArray().let { Vector2(it[0], it[1]) },
clouds = cloudsMap,
shaderVibrancy = JSON["shaderVibrancy"].asFloatArray()
)
}

View File

@@ -5,12 +5,11 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.g2d.TextureRegion
import com.badlogic.gdx.math.Vector3
import com.jme3.math.FastMath
import com.jme3.math.FastMath.PI
import com.jme3.math.FastMath.sin
import net.torvald.terrarum.App
import net.torvald.terrarum.gameworld.GameWorld
import kotlin.math.absoluteValue
import kotlin.math.pow
import kotlin.math.roundToInt
import kotlin.math.sign
import kotlin.math.*
/**
* Created by minjaesong on 2023-08-21.
@@ -47,7 +46,7 @@ class WeatherObjectCloud(
scl(world.worldTime.timeDelta.toFloat())
)
eigenAlpha = if (posZ < 1f) posZ.pow(0.5f) else -((posZ - 1f) / ALPHA_ROLLOFF_Z) + 1f
eigenAlpha = if (posZ < 1f) posZ.pow(0.5f) else cosh((posZ - CLOUD_STAGE_DEPTH) / (0.75636f * CLOUD_STAGE_DEPTH)) - 1f //-((posZ - 1f) / CLOUD_STAGE_DEPTH) + 1f
val alphaMult = if (life < NEWBORN_GROWTH_TIME)
life / NEWBORN_GROWTH_TIME
@@ -60,13 +59,13 @@ class WeatherObjectCloud(
val lrCoord = screenCoordBottomLRforDespawnCalculation
if (lrCoord.x > WeatherMixer.oobMarginR || lrCoord.z < WeatherMixer.oobMarginL || posZ !in 0.0001f..ALPHA_ROLLOFF_Z + 1f || alpha < 0f) {
if (lrCoord.x > WeatherMixer.oobMarginR || lrCoord.z < WeatherMixer.oobMarginL || posZ !in 0.0001f..CLOUD_STAGE_DEPTH + 1f || alpha < 0f) {
flagToDespawn = true
despawnCode = if (lrCoord.x > WeatherMixer.oobMarginR) "OUT_OF_SCREEN_RIGHT"
else if (lrCoord.z < WeatherMixer.oobMarginL) "OUT_OF_SCREEN_LEFT"
else if (posZ < 0.0001f) "OUT_OF_SCREEN_TOO_CLOSE"
else if (posZ > ALPHA_ROLLOFF_Z + 1f) "OUT_OF_SCREEN_TOO_FAR"
else if (posZ > CLOUD_STAGE_DEPTH + 1f) "OUT_OF_SCREEN_TOO_FAR"
else if (life >= lifespan + OLD_AGE_DECAY) "OLD_AGE"
else if (alpha < 0f) "ALPHA_BELOW_ZERO"
else "UNKNOWN"
@@ -180,6 +179,7 @@ class WeatherObjectCloud(
fun worldYtoWorldZforScreenYof0(y: Float) = 1f - (y / H) // rearrange screenCoord equations to derive this eq :p
const val ALPHA_ROLLOFF_Z = 64f
const val CLOUD_STAGE_DEPTH = 256f
const val OLD_AGE_DECAY = 5000f
const val NEWBORN_GROWTH_TIME = 1000f

View File

@@ -1,12 +1,11 @@
package net.torvald.terrarum.weather
import com.badlogic.gdx.math.Vector2
import com.jme3.math.FastMath
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.UnivariateFunction
import org.apache.commons.math3.analysis.interpolation.NevilleInterpolator
import org.apache.commons.math3.analysis.interpolation.SplineInterpolator
import java.util.*
data class WeatherSchedule(val weather: BaseModularWeather = WeatherMixer.DEFAULT_WEATHER, val duration: Long = 3600)
@@ -37,25 +36,37 @@ class Weatherbox {
val windDir = WeatherDirBox() // 0 .. 1.0
val windSpeed = WeatherStateBox() // 0 .. arbitrarily large number
val weatherSchedule: MutableList<WeatherSchedule> = mutableListOf<WeatherSchedule>()
val currentWeather: BaseModularWeather
val weatherSchedule = ArrayList<WeatherSchedule>()
val oldWeather: BaseModularWeather
get() = weatherSchedule[0].weather
val currentWeatherDuration: Long
val currentWeather: BaseModularWeather
get() = weatherSchedule[1].weather
// val nextWeather: BaseModularWeather
// get() = weatherSchedule[2].weather
val oldWeatherDuration: Long
get() = weatherSchedule[0].duration
val currentWeatherDuration: Long
get() = weatherSchedule[1].duration
@Transient val currentVibrancy = Vector2(1f, 1f)
val weatherBlend: Float
get() = updateAkku.toFloat() / currentWeatherDuration
fun initWith(initWeather: BaseModularWeather, duration: Long) {
weatherSchedule.clear()
weatherSchedule.add(WeatherSchedule(initWeather, duration))
weatherSchedule.add(WeatherSchedule(initWeather, duration))
}
var updateAkku = 0L; private set
fun update(world: GameWorld) {
updateShaderParams()
updateWind(world)
if (updateAkku >= currentWeatherDuration) {
// TODO add more random weathers
if (weatherSchedule.size == 1) {
while (weatherSchedule.size < 3) {
val newName = if (currentWeather.identifier == "generic01") "overcast01" else "generic01"
val newDuration = 7200L
weatherSchedule.add(WeatherSchedule(WeatherMixer.weatherDict[newName]!!, newDuration))
@@ -64,7 +75,10 @@ class Weatherbox {
}
// subtract akku by old currentWeatherDuration
updateAkku -= weatherSchedule.removeAt(0).duration
// printdbg(this, "Dequeueing a weather")
weatherSchedule.removeAt(0)
updateAkku -= oldWeatherDuration
}
updateAkku += world.worldTime.timeDelta
@@ -76,6 +90,15 @@ class Weatherbox {
}
windDir.update( world.worldTime.timeDelta / WIND_DIR_TIME_UNIT) { RNG.nextFloat() * 4f }
}
private fun updateShaderParams() {
val (co, cg) = oldWeather.shaderVibrancy
val (no, ng) = currentWeather.shaderVibrancy
currentVibrancy.set(
FastMath.interpolateLinear(weatherBlend * 2, co, no),
FastMath.interpolateLinear(weatherBlend * 2, cg, ng),
)
}
}

View File

@@ -31,6 +31,9 @@ const mat4 swizzler = mat4(
);
uniform float quant = 255.0; // 64 steps -> 63.0; 256 steps -> 255.0
vec4 quantVec = vec4(quant);
vec4 invQuant = vec4(1.0 / quant);
out vec4 fragColor;
const vec2 boolean = vec2(0.0, 1.0);
@@ -54,12 +57,12 @@ const mat4 ycocg_to_rgb = mat4(
vec4 nearestColour(vec4 inColor) {
return floor(vec4(quant) * inColor) * vec4(1.0 / quant);
return floor(quantVec * inColor) * invQuant;
}
vec4 getDitherredDot(vec4 inColor) {
vec4 bayerThreshold = swizzler * vec4(matrixNormaliser + texture(u_pattern, (gl_FragCoord.xy + rnd) * patternsize));
return nearestColour(bayerThreshold * vec4(1.0 / quant) + inColor);
return nearestColour(fma(bayerThreshold, invQuant, inColor));
}