mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 20:31:51 +09:00
more clouds
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -302,6 +302,7 @@ class BuildingMaker(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
||||
|
||||
|
||||
IngameRenderer.setRenderedWorld(gameWorld)
|
||||
WeatherMixer.internalReset()
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<String, TextureRegionPack>,
|
||||
val cloudChance: Float,
|
||||
val cloudDriftSpeed: Float,
|
||||
val cloudGamma: Vector2,
|
||||
var clouds: List<CloudProps>, // 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
|
||||
)
|
||||
@@ -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<WeatherObjectCloud?>(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 <T> Array<T?>.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<String, TextureRegionPack>()
|
||||
val cloudsMap = ArrayList<CloudProps>()
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
30
src/net/torvald/terrarum/weather/WeatherObject.kt
Normal file
30
src/net/torvald/terrarum/weather/WeatherObject.kt
Normal file
@@ -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)
|
||||
|
||||
}
|
||||
52
src/net/torvald/terrarum/weather/WeatherObjectCloud.kt
Normal file
52
src/net/torvald/terrarum/weather/WeatherObjectCloud.kt
Normal file
@@ -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 */ }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user