From dc83e12170ee2b1330acb876515af6e23e4ddb32 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Mon, 21 Aug 2023 21:39:11 +0900 Subject: [PATCH] more clouds --- .../basegame/weathers/WeatherGeneric.json | 8 +- assets/mods/basegame/weathers/cloud_large.kra | 4 +- assets/mods/basegame/weathers/cloud_large.png | 4 +- .../mods/basegame/weathers/cloud_normal.kra | 4 +- .../mods/basegame/weathers/cloud_normal.png | 4 +- .../terrarum/modulebasegame/BuildingMaker.kt | 1 + .../terrarum/modulebasegame/TitleScreen.kt | 1 + .../terrarum/weather/BaseModularWeather.kt | 20 ++- .../torvald/terrarum/weather/WeatherMixer.kt | 169 ++++++++++++++++-- .../torvald/terrarum/weather/WeatherObject.kt | 30 ++++ .../terrarum/weather/WeatherObjectCloud.kt | 52 ++++++ src/shaders/clouds.frag | 8 +- 12 files changed, 267 insertions(+), 38 deletions(-) create mode 100644 src/net/torvald/terrarum/weather/WeatherObject.kt create mode 100644 src/net/torvald/terrarum/weather/WeatherObjectCloud.kt diff --git a/assets/mods/basegame/weathers/WeatherGeneric.json b/assets/mods/basegame/weathers/WeatherGeneric.json index 818a8dab5..73311740d 100644 --- a/assets/mods/basegame/weathers/WeatherGeneric.json +++ b/assets/mods/basegame/weathers/WeatherGeneric.json @@ -2,15 +2,17 @@ "skyboxGradColourMap": "generic_skybox.tga", "daylightClut": "clut_daylight.tga", "classification": "generic", - "cloudGamma": [0.44, 2.0], + "cloudChance": 6, + "cloudGamma": [0.59, 2.0], + "cloudDriftSpeed": 0.4, "clouds": { "normal": { "filename": "cloud_normal.png", - "tw": 1024, "th": 512 + "tw": 1024, "th": 512, "probability": 1.0, "baseScale": 0.4, "scaleVariance": 0.2 }, "large": { "filename": "cloud_large.png", - "tw": 2048, "th": 1024 + "tw": 2048, "th": 1024, "probability": 0.1, "baseScale": 0.26, "scaleVariance": 0.2 } } } \ No newline at end of file diff --git a/assets/mods/basegame/weathers/cloud_large.kra b/assets/mods/basegame/weathers/cloud_large.kra index a7868c660..beeb4eda1 100644 --- a/assets/mods/basegame/weathers/cloud_large.kra +++ b/assets/mods/basegame/weathers/cloud_large.kra @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44df971ff0412128b06e4580a6c80c350280bdd4dd53456a8281559c328514d7 -size 6261901 +oid sha256:600a9165fe18e895064465c560b872abeb30ae2df576ec11c67de32a49a1431f +size 7084003 diff --git a/assets/mods/basegame/weathers/cloud_large.png b/assets/mods/basegame/weathers/cloud_large.png index 4c6793260..da6680c38 100644 --- a/assets/mods/basegame/weathers/cloud_large.png +++ b/assets/mods/basegame/weathers/cloud_large.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:741d9a1e00d0a9bccc4fb151b1f1bdfeb1e34906dc74c22ebc1ae552ea5d3758 -size 959425 +oid sha256:5959d9ae7b73a33098b3a0302f41c7cee0bff956d9e7ff570b58a39dab531d61 +size 1505631 diff --git a/assets/mods/basegame/weathers/cloud_normal.kra b/assets/mods/basegame/weathers/cloud_normal.kra index 548cb7c86..644b366f8 100644 --- a/assets/mods/basegame/weathers/cloud_normal.kra +++ b/assets/mods/basegame/weathers/cloud_normal.kra @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f38981c48584e9b541a869ee5a1d599e0d5186095a89559bf21500f6fde25ea -size 1084674 +oid sha256:a993447e75e5d9430b8e187befdf04eef1cb01ccd209785a8c2e25bc745a3f78 +size 2115503 diff --git a/assets/mods/basegame/weathers/cloud_normal.png b/assets/mods/basegame/weathers/cloud_normal.png index a60d771fd..1f547d6b6 100644 --- a/assets/mods/basegame/weathers/cloud_normal.png +++ b/assets/mods/basegame/weathers/cloud_normal.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a83b7e880871329fdfa8ccfe509a9d470155206935348de5e0f9de3605f5e0d3 -size 349899 +oid sha256:884594d5fbb8893edcba938ab82e33792c37e366dd2e8dad518b25ad0a65d580 +size 360226 diff --git a/src/net/torvald/terrarum/modulebasegame/BuildingMaker.kt b/src/net/torvald/terrarum/modulebasegame/BuildingMaker.kt index d03c5a14c..b0f5115ea 100644 --- a/src/net/torvald/terrarum/modulebasegame/BuildingMaker.kt +++ b/src/net/torvald/terrarum/modulebasegame/BuildingMaker.kt @@ -302,6 +302,7 @@ class BuildingMaker(batch: FlippingSpriteBatch) : IngameInstance(batch) { IngameRenderer.setRenderedWorld(gameWorld) + WeatherMixer.internalReset() } override fun show() { diff --git a/src/net/torvald/terrarum/modulebasegame/TitleScreen.kt b/src/net/torvald/terrarum/modulebasegame/TitleScreen.kt index bc59dc94c..f53abe071 100644 --- a/src/net/torvald/terrarum/modulebasegame/TitleScreen.kt +++ b/src/net/torvald/terrarum/modulebasegame/TitleScreen.kt @@ -210,6 +210,7 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) { IngameRenderer.setRenderedWorld(demoWorld) + WeatherMixer.internalReset() // load a half-gradient texture that would be used throughout the titlescreen and its sub UIs diff --git a/src/net/torvald/terrarum/weather/BaseModularWeather.kt b/src/net/torvald/terrarum/weather/BaseModularWeather.kt index a1f6ee36a..5bc58acb7 100644 --- a/src/net/torvald/terrarum/weather/BaseModularWeather.kt +++ b/src/net/torvald/terrarum/weather/BaseModularWeather.kt @@ -1,9 +1,9 @@ package net.torvald.terrarum.weather -import com.badlogic.gdx.graphics.Texture +import com.badlogic.gdx.math.Vector2 +import com.badlogic.gdx.utils.JsonValue import net.torvald.terrarum.GdxColorMap import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack -import java.util.* /** * Note: Colour maps are likely to have sparse data points @@ -13,11 +13,23 @@ import java.util.* * Created by minjaesong on 2016-07-11. */ data class BaseModularWeather( + val json: JsonValue, var skyboxGradColourMap: GdxColorMap, // row 0: skybox grad top, row 1: skybox grad bottom, row 2: sunlight (RGBA) val daylightClut: GdxColorMap, val classification: String, - val cloudGamma: FloatArray, - var clouds: HashMap, + val cloudChance: Float, + val cloudDriftSpeed: Float, + val cloudGamma: Vector2, + var clouds: List, // sorted by CloudProps.probability + val mixFrom: String? = null, val mixPercentage: Double? = null +) + +data class CloudProps( + val category: String, + val spriteSheet: TextureRegionPack, + val probability: Float, + val baseScale: Float, + val scaleVariance: Float ) \ No newline at end of file diff --git a/src/net/torvald/terrarum/weather/WeatherMixer.kt b/src/net/torvald/terrarum/weather/WeatherMixer.kt index 267767791..7ff251fab 100644 --- a/src/net/torvald/terrarum/weather/WeatherMixer.kt +++ b/src/net/torvald/terrarum/weather/WeatherMixer.kt @@ -3,6 +3,9 @@ package net.torvald.terrarum.weather import com.badlogic.gdx.Gdx import com.badlogic.gdx.graphics.* import com.badlogic.gdx.graphics.g2d.TextureRegion +import com.badlogic.gdx.math.Vector2 +import com.badlogic.gdx.math.Vector3 +import com.badlogic.gdx.utils.JsonValue import com.jme3.math.FastMath import net.torvald.gdx.graphics.Cvec import net.torvald.random.HQRNG @@ -21,6 +24,11 @@ import net.torvald.terrarum.worlddrawer.WorldCamera import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack import java.io.File import java.io.FileFilter +import java.lang.Double.doubleToLongBits +import java.util.* +import kotlin.collections.ArrayList +import kotlin.collections.HashMap +import kotlin.math.absoluteValue /** * Currently there is a debate whether this module must be part of the engine or the basegame @@ -52,6 +60,7 @@ internal object WeatherMixer : RNGConsumer { lateinit var mixedWeather: BaseModularWeather val globalLightNow = Cvec(0) + private val moonlightMax = Cvec(0.23f, 0.24f, 0.25f, 0.21f) // actual moonlight is around ~4100K but our mesopic vision makes it appear blueish (wikipedia: Purkinje effect) // Weather indices const val WEATHER_GENERIC = "generic" @@ -76,10 +85,10 @@ internal object WeatherMixer : RNGConsumer { private var astrumOffX = 0f private var astrumOffY = 0f - private var cloudGamma1 = 0.5f - private var cloudGamma2 = 2f + private val clouds = Array(256) { null } + private var cloudsSpawned = 0 + private var cloudDriftVector = Vector3(-1f, 0f, 1f) // this is a direction vector - private val moonlightMax = Cvec(0.23f, 0.24f, 0.25f, 0.21f) // actual moonlight is around ~4100K but our mesopic vision makes it appear blueish (wikipedia: Purkinje effect) override fun loadFromSave(s0: Long, s1: Long) { super.loadFromSave(s0, s1) @@ -96,6 +105,10 @@ internal object WeatherMixer : RNGConsumer { astrumOffX = s0.and(0xFFFFL).toFloat() / 65535f * starmapTex.regionWidth astrumOffY = s1.and(0xFFFFL).toFloat() / 65535f * starmapTex.regionHeight + + Arrays.fill(clouds, null) + cloudsSpawned = 0 + cloudDriftVector = Vector3(-1f, 0f, 1f) } init { @@ -134,11 +147,14 @@ internal object WeatherMixer : RNGConsumer { 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", - floatArrayOf(1f, 1f), - HashMap() + 0f, + 0f, + Vector2(1f, 1f), + listOf() ) currentWeather = defaultWeather @@ -154,9 +170,7 @@ internal object WeatherMixer : RNGConsumer { // currentWeather = weatherList[WEATHER_GENERIC]!![0] // force set weather - // update clouds - cloudGamma1 = currentWeather.cloudGamma[0] - cloudGamma2 = currentWeather.cloudGamma[1] + updateClouds(delta, world) if (!globalLightOverridden) { @@ -165,6 +179,104 @@ internal object WeatherMixer : RNGConsumer { } + private var cloudUpdateAkku = 0f + private fun updateClouds(delta: Float, world: GameWorld) { + val cloudChanceEveryMin = 60f / (currentWeather.cloudChance * currentWeather.cloudDriftSpeed) // if chance = 0, the result will be +inf + + if (cloudUpdateAkku >= cloudChanceEveryMin) { + cloudUpdateAkku -= cloudChanceEveryMin + tryToSpawnCloud(currentWeather) + } + + clouds.forEach { + it?.let { + it.update(cloudDriftVector, currentWeather.cloudDriftSpeed) + + if (it.posX < -1500f || it.posX > App.scr.width + 1500f || it.scale < 1f / 2048f) { + it.flagToDespawn = true + } + } + } + + clouds.indices.forEach { i -> + if (clouds[i]?.flagToDespawn == true) { + clouds[i]?.dispose() + clouds[i] = null + cloudsSpawned -= 1 + } + } + + cloudUpdateAkku += delta + } + + private val scrHscaler = App.scr.height / 720f + + private fun tryToSpawnCloud(currentWeather: BaseModularWeather) { + printdbg(this, "Trying to spawn a cloud... (${cloudsSpawned} / ${clouds.size})") + + if (cloudsSpawned < clouds.size) { + val rC = Math.random().toFloat() + val r0 = (Math.random() * 2.0 - 1.0).toFloat() // -1..1 + val r1 = (Math.random() * 2.0 - 1.0).toFloat() // -1..1 + val r2 = (Math.random() * 2.0 - 1.0).toFloat() // -1..1 + val rT1 = ((Math.random() + Math.random()) - 1.0).toFloat() // -1..1 + val (rA, rB) = doubleToLongBits(Math.random()).let { + it.ushr(20).and(0xFFFF).toInt() to it.ushr(36).and(0xFFFF).toInt() + } + + var cloudsToSpawn: CloudProps? = null + var c = 0 + while (c < currentWeather.clouds.size) { + if (rC < currentWeather.clouds[c].probability) { + cloudsToSpawn = currentWeather.clouds[c] + break + } + c += 1 + } + + cloudsToSpawn?.let { cloud -> + val scaleVariance = 1f + rT1.absoluteValue * cloud.scaleVariance + val cloudScale = cloud.baseScale * (if (rT1 < 0) 1f / scaleVariance else scaleVariance) + val hCloudSize = cloud.spriteSheet.tileW * cloudScale + 1f + val posX = if (cloudDriftVector.x < 0) App.scr.width + hCloudSize else -hCloudSize + val posY = when (cloud.category) { + "large" -> (100f + r0 * 80f) * scrHscaler + else -> (150f + r0 * 50f) * scrHscaler + } + val sheetX = rA % cloud.spriteSheet.horizontalCount + val sheetY = rB % cloud.spriteSheet.verticalCount + WeatherObjectCloud(cloud.spriteSheet.get(sheetX, sheetY)).also { + it.scale = cloudScale + + it.darkness.set(currentWeather.cloudGamma) + it.darkness.x *= it.scale + it.darkness.x *= 1f + r1 * 0.1f + it.darkness.y *= 1f + r2 * 0.1f + + it.posX = posX + it.posY = posY + + clouds.addAtFreeSpot(it) + cloudsSpawned += 1 + + + printdbg(this, "... Spawning ${cloud.category}($sheetX, $sheetY) cloud at pos ${it.pos}, invGamma ${it.darkness}") + } + + } + } + } + + private fun Array.addAtFreeSpot(obj: T) { + var c = 0 + while (true) { + if (this[c] == null) break + c += 1 + } + this[c] = obj + } + + var turbidity = 1.0; private set private var gH = 1.8f * App.scr.height // private var gH = 0.8f * App.scr.height @@ -185,15 +297,16 @@ internal object WeatherMixer : RNGConsumer { it.a = 1f } // TODO add cloud-only colour strip on the CLUT batch.shader = shaderClouds - batch.inUse { - batch.shader.setUniformf("inverseGamma", cloudGamma1, cloudGamma2) - currentWeather.clouds["large"]?.get(0, 0)?.let { batch.draw(it, -400f - INGAME.WORLD_UPDATE_TIMER * 0.06f, -600f) } - currentWeather.clouds["normal"]?.get(0, 1)?.let { batch.draw(it, 600f - INGAME.WORLD_UPDATE_TIMER * 0.09f, -300f) } - currentWeather.clouds["normal"]?.get(0, 0)?.let { batch.draw(it, 200f - INGAME.WORLD_UPDATE_TIMER * 0.13f, -150f) } + clouds.forEach { + it?.let { + batch.inUse { _ -> + batch.shader.setUniformf("gamma", it.darkness) + it.render(batch, 0f, 0f) // TODO parallax + } + } } - batch.color = Color.WHITE } @@ -402,13 +515,27 @@ internal object WeatherMixer : RNGConsumer { val classification = JSON.getString("classification") - val cloudGamma = JSON["cloudGamma"].asFloatArray() - val cloudsMap = HashMap() + val cloudsMap = ArrayList() val clouds = JSON["clouds"] clouds.forEachSiblings { name, json -> - cloudsMap[name] = TextureRegionPack(ModMgr.getGdxFile(modname, "$pathToImage/${json.getString("filename")}"), json.getInt("tw"), json.getInt("th")) + cloudsMap.add(CloudProps( + name, + TextureRegionPack(ModMgr.getGdxFile(modname, "$pathToImage/${json.getString("filename")}"), json.getInt("tw"), json.getInt("th")), + json.getFloat("probability"), + json.getFloat("baseScale"), + json.getFloat("scaleVariance") + )) } + cloudsMap.sortBy { it.probability } + + + + + + + + var mixFrom: String? try { mixFrom = JSON.getString("mixFrom") } @@ -423,10 +550,13 @@ internal object WeatherMixer : RNGConsumer { return BaseModularWeather( + json = JSON, skyboxGradColourMap = skybox, daylightClut = daylight, classification = classification, - cloudGamma = cloudGamma, + cloudChance = JSON.getFloat("cloudChance"), + cloudDriftSpeed = JSON.getFloat("cloudDriftSpeed"), + cloudGamma = JSON["cloudGamma"].asFloatArray().let { Vector2(it[0], it[1]) }, clouds = cloudsMap, ) } @@ -434,7 +564,7 @@ internal object WeatherMixer : RNGConsumer { fun dispose() { weatherList.values.forEach { list -> list.forEach { weather -> - weather.clouds.forEach { it.value.dispose() } + weather.clouds.forEach { it.spriteSheet.dispose() } } } starmapTex.texture.dispose() @@ -442,3 +572,4 @@ internal object WeatherMixer : RNGConsumer { shaderClouds.dispose() } } + diff --git a/src/net/torvald/terrarum/weather/WeatherObject.kt b/src/net/torvald/terrarum/weather/WeatherObject.kt new file mode 100644 index 000000000..9bbf7a2a9 --- /dev/null +++ b/src/net/torvald/terrarum/weather/WeatherObject.kt @@ -0,0 +1,30 @@ +package net.torvald.terrarum.weather + +import com.badlogic.gdx.graphics.g2d.SpriteBatch +import com.badlogic.gdx.math.Vector3 +import com.badlogic.gdx.utils.Disposable + +/** + * Created by minjaesong on 2023-08-21. + */ +abstract class WeatherObject : Disposable { + + /** vec3(posX, posY, scale) */ + var pos: Vector3 = Vector3() + + var posX: Float + get() = pos.x + set(value) { pos.x = value } + var posY: Float + get() = pos.y + set(value) { pos.y = value } + var scale: Float + get() = pos.z + set(value) { pos.z = value } + + var flagToDespawn = false + + abstract fun update() + abstract fun render(batch: SpriteBatch, offsetX: Float, offsetY: Float) + +} \ No newline at end of file diff --git a/src/net/torvald/terrarum/weather/WeatherObjectCloud.kt b/src/net/torvald/terrarum/weather/WeatherObjectCloud.kt new file mode 100644 index 000000000..117efd1a9 --- /dev/null +++ b/src/net/torvald/terrarum/weather/WeatherObjectCloud.kt @@ -0,0 +1,52 @@ +package net.torvald.terrarum.weather + +import com.badlogic.gdx.graphics.g2d.SpriteBatch +import com.badlogic.gdx.graphics.g2d.TextureRegion +import com.badlogic.gdx.math.Vector2 +import com.badlogic.gdx.math.Vector3 +import kotlin.math.sign + +/** + * Created by minjaesong on 2023-08-21. + */ +class WeatherObjectCloud(private val texture: TextureRegion) : WeatherObject() { + + /** + * To actually utilise this value, your render code must begin the spritebatch per-object, like so: + * ``` + * batch.shader = cloudShader + * for (it in clouds) { + * batch.begin() + * batch.shader.setUniformf("gamma", it.darkness) + * batch.draw(it, ...) + * batch.end() + * } + */ + var darkness: Vector2 = Vector2(0.5f, 2.0f) // the "gamma" value fed into the clouds shader + + override fun update() { + } + + /** + * FlowVector: In which direction the cloud flows. Vec3(dX, dY, dScale) + * Resulting vector: (x + dX, y + dY, scale * dScale) + */ + fun update(flowVector: Vector3, gait: Float) { + posX += flowVector.x * gait * scale * scale + posY += flowVector.y * gait * scale * scale + scale *= flowVector.z + } + + /** + * X/Y position is a bottom-centre point of the image + * Shader must be prepared prior to the render() call + */ + override fun render(batch: SpriteBatch, offsetX: Float, offsetY: Float) { + val x = posX + offsetX - texture.regionWidth * scale * 0.5f + val y = posY + offsetY - texture.regionHeight * scale + + batch.draw(texture, x, y, texture.regionWidth * scale, texture.regionHeight * scale) + } + + override fun dispose() { /* cloud texture will be disposed of by the WeatherMixer */ } +} \ No newline at end of file diff --git a/src/shaders/clouds.frag b/src/shaders/clouds.frag index 78ee21690..5b083d082 100644 --- a/src/shaders/clouds.frag +++ b/src/shaders/clouds.frag @@ -14,12 +14,12 @@ out vec4 fragColor; const vec2 boolean = vec2(0.0, 1.0); -uniform vec2 inverseGamma = vec2(10, 2.0); // vec2(inverse gamma RGB, inverse gamma RGA) +uniform vec2 gamma = vec2(10, 2.0); // vec2(gamma for RGB, gamma for A) void main() { - vec4 inCol = v_color * texture(u_texture, v_texCoords); + vec4 inCol = texture(u_texture, v_texCoords); - vec4 outCol = pow(inCol, inverseGamma.xxxy); + vec4 outCol = pow(inCol, gamma.xxxy); - fragColor = outCol; + fragColor = outCol * v_color; } \ No newline at end of file