Compare commits

..

5 Commits

Author SHA1 Message Date
minjaesong
cd00ab4c7f fix: hq2x results graphical issue on some systems 2023-08-07 14:30:36 +09:00
minjaesong
014306c209 2k skybox tex; trilinear blending of atmos vars 2023-08-07 13:59:45 +09:00
minjaesong
30fb57eca3 skybox: two different setup for AM/PN 2023-08-06 18:37:56 +09:00
minjaesong
52ad8f0c46 improved skybox model 2023-08-05 17:20:35 +09:00
minjaesong
1b08039018 updating numbers for v0.3.3 2023-08-05 00:45:35 +09:00
11 changed files with 272 additions and 112 deletions

Binary file not shown.

View File

@@ -0,0 +1,31 @@
{
"GAME_ITEM_CALENDAR": "Calendar",
"MENU_CALENDAR_CALENDAR": "Calendar",
"MENU_CALENDAR_EVENTS": "Events",
"MENU_CALENDAR_ADD_NEW_EVENT": "Add New Event…",
"CONTEXT_CALENDAR_SEASON_SPRING": "Spring",
"CONTEXT_CALENDAR_SEASON_SUMMER": "Summer",
"CONTEXT_CALENDAR_SEASON_AUTUMN": "Autumn",
"CONTEXT_CALENDAR_SEASON_WINTER": "Winter",
"CONTEXT_CALENDAR_SEASON_SPRI": "Spri",
"CONTEXT_CALENDAR_SEASON_SUMM": "Summ",
"CONTEXT_CALENDAR_SEASON_AUTM": "Autm",
"CONTEXT_CALENDAR_SEASON_WINT": "Wint",
"CONTEXT_CALENDAR_DAY_MONDAG_DNT": "Mondag",
"CONTEXT_CALENDAR_DAY_TYSDAG_DNT": "Tysdag",
"CONTEXT_CALENDAR_DAY_MIDTVEKE_DNT": "Midtveke",
"CONTEXT_CALENDAR_DAY_TORSDAG_DNT": "Torsdag",
"CONTEXT_CALENDAR_DAY_FREDAG_DNT": "Fredag",
"CONTEXT_CALENDAR_DAY_LAURDAG_DNT": "Laurdag",
"CONTEXT_CALENDAR_DAY_SUNDAG_DNT": "Sundag",
"CONTEXT_CALENDAR_DAY_VERDDAG_DNT": "Verddag",
"CONTEXT_CALENDAR_DAY_MON_DNT": "Mon",
"CONTEXT_CALENDAR_DAY_TYS_DNT": "Tys",
"CONTEXT_CALENDAR_DAY_MID_DNT": "Mid",
"CONTEXT_CALENDAR_DAY_TOR_DNT": "Tor",
"CONTEXT_CALENDAR_DAY_FRE_DNT": "Fre",
"CONTEXT_CALENDAR_DAY_LAU_DNT": "Lau",
"CONTEXT_CALENDAR_DAY_SUN_DNT": "Sun",
"CONTEXT_CALENDAR_DAY_VER_DNT": "Ver"
}

View File

@@ -0,0 +1,14 @@
{
"GAME_ITEM_CALENDAR": "달력",
"MENU_CALENDAR_CALENDAR": "달력",
"MENU_CALENDAR_EVENTS": "일정",
"MENU_CALENDAR_ADD_NEW_EVENT": "새 일정 추가…",
"CONTEXT_CALENDAR_SEASON_SPRING": "봄",
"CONTEXT_CALENDAR_SEASON_SUMMER": "여름",
"CONTEXT_CALENDAR_SEASON_AUTUMN": "가을",
"CONTEXT_CALENDAR_SEASON_WINTER": "겨울",
"CONTEXT_CALENDAR_SEASON_SPRI": "봄",
"CONTEXT_CALENDAR_SEASON_SUMM": "여름",
"CONTEXT_CALENDAR_SEASON_AUTM": "가을",
"CONTEXT_CALENDAR_SEASON_WINT": "겨울"
}

View File

@@ -192,6 +192,16 @@ fun CIEXYZ.toColorRaw(): Color {
return Color(rgb.r, rgb.g, rgb.b, rgb.alpha) return Color(rgb.r, rgb.g, rgb.b, rgb.alpha)
} }
fun CIEXYZ.toYXY(): CIEYXY {
val dot = this.X + this.Y + this.Z
return CIEYXY(
this.Y,
this.X / dot,
this.Y / dot,
this.alpha
)
}
fun CIEYXY.toXYZ(): CIEXYZ { fun CIEYXY.toXYZ(): CIEXYZ {
return CIEXYZ(x * yy / y, yy, (1f - x - y) * yy / y) return CIEXYZ(x * yy / y, yy, (1f - x - y) * yy / y)
} }

View File

@@ -12,16 +12,14 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch
import net.torvald.unicode.EMDASH import net.torvald.unicode.EMDASH
import net.torvald.colourutil.* import net.torvald.colourutil.*
import net.torvald.parametricsky.datasets.DatasetCIEXYZ import net.torvald.parametricsky.datasets.DatasetCIEXYZ
import net.torvald.parametricsky.datasets.DatasetRGB
import net.torvald.parametricsky.datasets.DatasetSpectral
import net.torvald.terrarum.abs import net.torvald.terrarum.abs
import net.torvald.terrarum.clut.Skybox
import net.torvald.terrarum.clut.Skybox.coerceInSmoothly
import net.torvald.terrarum.clut.Skybox.mapCircle
import net.torvald.terrarum.inUse import net.torvald.terrarum.inUse
import net.torvald.terrarum.modulebasegame.worldgenerator.HALF_PI import net.torvald.terrarum.modulebasegame.worldgenerator.HALF_PI
import net.torvald.terrarum.modulebasegame.worldgenerator.TWO_PI
import java.awt.BorderLayout import java.awt.BorderLayout
import java.awt.Dimension import java.awt.Dimension
import java.awt.FlowLayout
import java.awt.GridLayout
import java.lang.Math.pow import java.lang.Math.pow
import javax.swing.* import javax.swing.*
import kotlin.math.* import kotlin.math.*
@@ -94,7 +92,16 @@ class Application(val WIDTH: Int, val HEIGHT: Int) : Game() {
if (turbidity <= 0) throw IllegalStateException() if (turbidity <= 0) throw IllegalStateException()
// we need to use different model-state to accommodate different albedo for each spectral band but oh well... // we need to use different model-state to accommodate different albedo for each spectral band but oh well...
genTexLoop(model) genTexLoop(model, elevation)
// println("$elevation\t${ymaxDisp.text}\t${ymaxDisp2.text}")
/*for (elev in -75..75) {
val elevation = Math.toRadians(elev.toDouble())
val model = ArHosekSkyModel.arhosek_xyz_skymodelstate_alloc_init(turbidity, albedo, elevation.abs())
genTexLoop(model, elevation)
println("$elev\t${ymaxDisp.text}\t${ymaxDisp2.text}")
}*/
val tex = Texture(oneScreen) val tex = Texture(oneScreen)
@@ -127,7 +134,7 @@ class Application(val WIDTH: Int, val HEIGHT: Int) : Game() {
} }
val outTexWidth = 1 val outTexWidth = 1
val outTexHeight = 256 val outTexHeight = 128
private fun Float.scaleFun() = private fun Float.scaleFun() =
(1f - 1f / 2f.pow(this/6f)) * 0.97f (1f - 1f / 2f.pow(this/6f)) * 0.97f
@@ -142,14 +149,20 @@ class Application(val WIDTH: Int, val HEIGHT: Int) : Game() {
) )
} }
else { else {
val elevation1 = -Math.toDegrees(elevation) // maths model: https://www.desmos.com/calculator/cwi7iyzygg
val elevation2 = -Math.toDegrees(elevation) / 28.5
val scale = (1f - (1f - 1f / 1.8.pow(elevation1)) * 0.97f).toFloat() val x = -Math.toDegrees(elevation).toFloat()
val scale2 = (1.0 - (elevation2.pow(E) / E.pow(elevation2))*0.8).toFloat() // val elevation2 = -Math.toDegrees(elevation) / 28.5
val p = 3.5f
val q = 7.5f
val s = -0.2f
val f = (1f - (1f - 1f / 1.8f.pow(x)) * 0.97f).toFloat()
// val g = (1.0 - (elevation2.pow(E) / E.pow(elevation2))*0.8).toFloat()
val h = ((x / q).pow(p) + 1f).pow(s)
CIEXYZ( CIEXYZ(
this.X.scaleFun() * scale * scale2, this.X.scaleFun() * f * h,
this.Y.scaleFun() * scale * scale2, this.Y.scaleFun() * f * h,
this.Z.scaleFun() * scale * scale2, this.Z.scaleFun() * f * h,
this.alpha this.alpha
) )
} }
@@ -161,7 +174,7 @@ class Application(val WIDTH: Int, val HEIGHT: Int) : Game() {
* Generated texture is as if you took the panorama picture of sky: up 70deg to horizon, east-south-west; * Generated texture is as if you took the panorama picture of sky: up 70deg to horizon, east-south-west;
* with sun not moving (sun is at exact south, sun's height is adjustable) * with sun not moving (sun is at exact south, sun's height is adjustable)
*/ */
private fun genTexLoop(state: ArHosekSkyModelState) { private fun genTexLoop(state: ArHosekSkyModelState, elevation: Double) {
fun normaliseY(y: Double): Float { fun normaliseY(y: Double): Float {
var v = y.coerceAtLeast(0.0) var v = y.coerceAtLeast(0.0)
@@ -175,6 +188,7 @@ class Application(val WIDTH: Int, val HEIGHT: Int) : Game() {
val ys2 = ArrayList<Float>() val ys2 = ArrayList<Float>()
val halfHeight = oneScreen.height * 0.5 val halfHeight = oneScreen.height * 0.5
val elevationDeg = Math.toDegrees(elevation)
for (x in 0 until oneScreen.width) { for (x in 0 until oneScreen.width) {
for (y in 0 until oneScreen.height) { for (y in 0 until oneScreen.height) {
@@ -186,17 +200,19 @@ class Application(val WIDTH: Int, val HEIGHT: Int) : Game() {
val theta = sqrt(xf*xf + yf*yf) * HALF_PI*/ val theta = sqrt(xf*xf + yf*yf) * HALF_PI*/
// AM-PM mapping (use with WIDTH=1) // AM-PM mapping (use with WIDTH=1)
var yf = (y * 2.0 / oneScreen.height) % 1.0 val yp = y % (oneScreen.height / 2)
if (elevation < 0) yf *= 1.0 - pow(-elevation / HALF_PI, 0.333) val yi = yp - 3
val xf = -elevationDeg / 90.0
var yf = (yi / 58.0).coerceIn(0.0, 1.0).mapCircle().coerceInSmoothly(0.0, 0.95)
if (elevationDeg < 0) yf *= Skybox.superellipsoidDecay(1.0 / 3.0, xf)
val theta = yf * HALF_PI
val gamma = if (y < halfHeight) HALF_PI else 3 * HALF_PI val gamma = if (y < halfHeight) HALF_PI else 3 * HALF_PI
val theta = yf.mapCircle() * HALF_PI
val xyz = CIEXYZ( val xyz = CIEXYZ(
ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, gamma, 0).toFloat(), ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, gamma, 0).toFloat(),
ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, gamma, 1).toFloat(), ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, gamma, 1).toFloat(),
ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, gamma, 2).toFloat() ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, gamma, 2).toFloat(),
) )
val xyz2 = xyz.scaleToFit(elevation) val xyz2 = xyz.scaleToFit(elevation)
ys.add(xyz.Y) ys.add(xyz.Y)
@@ -204,12 +220,12 @@ class Application(val WIDTH: Int, val HEIGHT: Int) : Game() {
val rgb = xyz2.toRGB().toColor() val rgb = xyz2.toRGB().toColor()
rgb.a = 1f rgb.a = 1f
val rgb2 = Color( /*val rgb2 = Color(
((rgb.r * 255f).roundToInt() xor 0xAA) / 255f, ((rgb.r * 255f).roundToInt() xor 0xAA) / 255f,
((rgb.g * 255f).roundToInt() xor 0xAA) / 255f, ((rgb.g * 255f).roundToInt() xor 0xAA) / 255f,
((rgb.b * 255f).roundToInt() xor 0xAA) / 255f, ((rgb.b * 255f).roundToInt() xor 0xAA) / 255f,
rgb.a rgb.a
) )*/
oneScreen.setColor(rgb) oneScreen.setColor(rgb)
oneScreen.drawPixel(x, y) oneScreen.drawPixel(x, y)

View File

@@ -62,9 +62,10 @@ basegame
* e.g. 0x02010034 will be translated as 2.1.52 * e.g. 0x02010034 will be translated as 2.1.52
* *
*/ */
const val VERSION_RAW: Long = 0x0000_000003_000002 const val VERSION_RAW: Long = 0x0000_000003_000003
// Commit counts up to the Release 0.3.0: 2259 // Commit counts up to the Release 0.3.0: 2259
// Commit counts up to the Release 0.3.1: 2278 // Commit counts up to the Release 0.3.1: 2278
// Commit counts up to the Release 0.3.2: 2732
////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////
// CONFIGURATION FOR TILE MAKER // // CONFIGURATION FOR TILE MAKER //

View File

@@ -9,6 +9,7 @@ import com.badlogic.gdx.math.Matrix4
import com.badlogic.gdx.utils.Disposable import com.badlogic.gdx.utils.Disposable
import com.jme3.math.FastMath import com.jme3.math.FastMath
import net.torvald.random.HQRNG import net.torvald.random.HQRNG
import net.torvald.terrarum.App.IS_DEVELOPMENT_BUILD
import net.torvald.terrarum.gamecontroller.KeyToggler import net.torvald.terrarum.gamecontroller.KeyToggler
import net.torvald.terrarum.ui.BasicDebugInfoWindow import net.torvald.terrarum.ui.BasicDebugInfoWindow
import net.torvald.terrarum.ui.Toolkit import net.torvald.terrarum.ui.Toolkit
@@ -150,9 +151,10 @@ object TerrarumPostProcessor : Disposable {
} }
// draw dev build notifiers // draw dev build notifiers
if (App.IS_DEVELOPMENT_BUILD && Terrarum.ingame != null) { // omitting this screws up HQ2X render for some reason
if (Terrarum.ingame != null) {
batch.inUse { batch.inUse {
batch.color = safeAreaCol batch.color = if (IS_DEVELOPMENT_BUILD) safeAreaCol else colourNull
App.fontGame.draw(it, thisIsDebugStr, 5f, App.scr.height - 24f) App.fontGame.draw(it, thisIsDebugStr, 5f, App.scr.height - 24f)
} }
} }
@@ -192,6 +194,7 @@ object TerrarumPostProcessor : Disposable {
return outFBO return outFBO
} }
private val rng = HQRNG() private val rng = HQRNG()
private val colourNull = Color(0)
private fun Double.format(digits: Int) = "%.${digits}f".format(this) private fun Double.format(digits: Int) = "%.${digits}f".format(this)
private fun Float.format(digits: Int) = "%.${digits}f".format(this) private fun Float.format(digits: Int) = "%.${digits}f".format(this)

View File

@@ -19,7 +19,7 @@ fun main() {
// y: increasing turbidity (1.0 .. 10.0, in steps of 0.333) // y: increasing turbidity (1.0 .. 10.0, in steps of 0.333)
// x: elevations (-75 .. 75 in steps of 1, then albedo of [0.1, 0.3, 0.5, 0.7, 0.9]) // x: elevations (-75 .. 75 in steps of 1, then albedo of [0.1, 0.3, 0.5, 0.7, 0.9])
val texh = Skybox.gradSize * Skybox.turbCnt val texh = Skybox.gradSize * Skybox.turbCnt
val texw = Skybox.elevCnt * Skybox.albedoCnt val texw = Skybox.elevCnt * Skybox.albedoCnt * 2
val TGA_HEADER_SIZE = 18 val TGA_HEADER_SIZE = 18
val bytes = ByteArray(TGA_HEADER_SIZE + texw * texh * 4 + 26) val bytes = ByteArray(TGA_HEADER_SIZE + texw * texh * 4 + 26)
@@ -44,47 +44,50 @@ fun main() {
println("Generating texture atlas ($texw x $texh)...") println("Generating texture atlas ($texw x $texh)...")
// write pixels // write pixels
for (albedo0 in 0 until Skybox.albedoCnt) { for (gammaPair in 0..1) {
val albedo = Skybox.albedos[albedo0] for (albedo0 in 0 until Skybox.albedoCnt) {
println("Albedo=$albedo") val albedo = Skybox.albedos[albedo0]
for (turb0 in 0 until Skybox.turbCnt) { println("Albedo=$albedo")
val turbidity = Skybox.turbiditiesD[turb0] for (turb0 in 0 until Skybox.turbCnt) {
println("....... Turbidity=$turbidity") val turbidity = Skybox.turbiditiesD[turb0]
for (elev0 in 0 until Skybox.elevCnt) { println("....... Turbidity=$turbidity")
val elevationDeg = Skybox.elevationsD[elev0] for (elev0 in 0 until Skybox.elevCnt) {
val elevationRad = Math.toRadians(elevationDeg) val elevationDeg = Skybox.elevationsD[elev0]
val elevationRad = Math.toRadians(elevationDeg)
// println("... Elevation: $elevationDeg") // println("... Elevation: $elevationDeg")
val state = ArHosekSkyModel.arhosek_xyz_skymodelstate_alloc_init(turbidity, albedo, elevationRad.abs()) val state =
ArHosekSkyModel.arhosek_xyz_skymodelstate_alloc_init(turbidity, albedo, elevationRad.abs())
for (yp in 0 until Skybox.gradSize) { for (yp in 0 until Skybox.gradSize) {
val yi = yp - 3 val yi = yp - 10
val xf = -elevationDeg / 90.0 val xf = -elevationDeg / 90.0
var yf = (yi / 58.0).coerceIn(0.0, 1.0).mapCircle().coerceInSmoothly(0.0, 0.95) var yf = (yi / 58.0).coerceIn(0.0, 1.0).mapCircle().coerceInSmoothly(0.0, 0.95)
// experiments visualisation: https://www.desmos.com/calculator/5crifaekwa // experiments visualisation: https://www.desmos.com/calculator/5crifaekwa
// if (elevationDeg < 0) yf *= 1.0 - pow(xf, 0.333) // if (elevationDeg < 0) yf *= 1.0 - pow(xf, 0.333)
// if (elevationDeg < 0) yf *= -2.0 * asin(xf - 1.0) / PI // if (elevationDeg < 0) yf *= -2.0 * asin(xf - 1.0) / PI
if (elevationDeg < 0) yf *= Skybox.superellipsoidDecay(1.0 / 3.0, xf) if (elevationDeg < 0) yf *= Skybox.superellipsoidDecay(1.0 / 3.0, xf)
val theta = yf * HALF_PI val theta = yf * HALF_PI
// vertical angle, where 0 is zenith, ±90 is ground (which is odd) // vertical angle, where 0 is zenith, ±90 is ground (which is odd)
// println("$yp\t$theta") // println("$yp\t$theta")
val xyz = CIEXYZ( val xyz = CIEXYZ(
ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, HALF_PI, 0).toFloat(), ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, (gammaPair * 2f + 1f) * HALF_PI, 0).toFloat(),
ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, HALF_PI, 1).toFloat(), ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, (gammaPair * 2f + 1f) * HALF_PI, 1).toFloat(),
ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, HALF_PI, 2).toFloat() ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, (gammaPair * 2f + 1f) * HALF_PI, 2).toFloat()
) )
val xyz2 = xyz.scaleToFit(elevationDeg) val xyz2 = xyz.scaleToFit(elevationDeg)
val rgb = xyz2.toRGB().toColor() val rgb = xyz2.toRGB().toColor()
val colour = rgb.toIntBits().toLittle() val colour = rgb.toIntBits().toLittle()
val imgOffX = (albedo0 * Skybox.elevCnt + elev0) val imgOffX = albedo0 * Skybox.elevCnt + elev0 + Skybox.elevCnt * Skybox.albedoCnt * gammaPair
val imgOffY = texh - 1 - (Skybox.gradSize * turb0 + yp) val imgOffY = texh - 1 - (Skybox.gradSize * turb0 + yp)
val fileOffset = TGA_HEADER_SIZE + 4 * (imgOffY * texw + imgOffX) val fileOffset = TGA_HEADER_SIZE + 4 * (imgOffY * texw + imgOffX)
for (i in 0..3) { for (i in 0..3) {
bytes[fileOffset + i] = colour[bytesLut[i]] bytes[fileOffset + i] = colour[bytesLut[i]]
}
} }
} }
} }
@@ -93,7 +96,7 @@ fun main() {
println("Atlas generation done!") println("Atlas generation done!")
File("./assets/mods/basegame/weathers/main_skybox.tga").writeBytes(bytes) File("./assets/clut/skybox.tga").writeBytes(bytes)
} }
private val bytesLut = arrayOf(2,1,0,3,2,1,0,3) // For some reason BGRA order is what makes it work private val bytesLut = arrayOf(2,1,0,3,2,1,0,3) // For some reason BGRA order is what makes it work

View File

@@ -13,6 +13,8 @@ import net.torvald.parametricsky.ArHosekSkyModel
import net.torvald.terrarum.App import net.torvald.terrarum.App
import net.torvald.terrarum.App.printdbg import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.abs import net.torvald.terrarum.abs
import net.torvald.terrarum.floorToInt
import net.torvald.terrarum.toInt
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
import kotlin.math.* import kotlin.math.*
@@ -25,7 +27,7 @@ object Skybox : Disposable {
private const val PI = 3.141592653589793 private const val PI = 3.141592653589793
private const val TWO_PI = 6.283185307179586 private const val TWO_PI = 6.283185307179586
const val gradSize = 64 const val gradSize = 78
private lateinit var gradTexBinLowAlbedo: Array<TextureRegion> private lateinit var gradTexBinLowAlbedo: Array<TextureRegion>
private lateinit var gradTexBinHighAlbedo: Array<TextureRegion> private lateinit var gradTexBinHighAlbedo: Array<TextureRegion>
@@ -37,6 +39,7 @@ object Skybox : Disposable {
fun loadlut() { fun loadlut() {
tex = Texture(Gdx.files.internal("assets/clut/skybox.png")) tex = Texture(Gdx.files.internal("assets/clut/skybox.png"))
tex.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear) tex.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)
tex.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat)
texRegions = TextureRegionPack(tex, 2, gradSize - 2, 0, 2, 0, 1) texRegions = TextureRegionPack(tex, 2, gradSize - 2, 0, 2, 0, 1)
texStripRegions = TextureRegionPack(tex, elevCnt, gradSize - 2, 0, 2, 0, 1) texStripRegions = TextureRegionPack(tex, elevCnt, gradSize - 2, 0, 2, 0, 1)
} }
@@ -50,28 +53,67 @@ object Skybox : Disposable {
}*/ }*/
// use external LUT // use external LUT
operator fun get(elevationDeg: Double, turbidity: Double, albedo: Double): TextureRegion { operator fun get(elevationDeg: Double, turbidity: Double, albedo: Double, isAfternoon: Boolean): TextureRegion {
val elev = elevationDeg.coerceIn(-elevMax, elevMax).roundToInt().plus(elevMax).roundToInt() TODO()
val turb = turbidity.coerceIn(1.0, 10.0).minus(1.0).times(turbDivisor).roundToInt()
val alb = albedo.coerceIn(0.1, 0.9).minus(0.1).times(turbDivisor).roundToInt()
//printdbg(this, "elev $elevationDeg->$elev; turb $turbidity->$turb; alb $albedo->$alb")
return texRegions.get(alb * elevCnt + elev, turb)
} }
fun getUV(elevationDeg: Double, turbidity: Double, albedo: Double): Pair<Texture, FloatArray> { data class SkyboxRenderInfo(
val turb = turbidity.coerceIn(1.0, 10.0).minus(1.0).times(turbDivisor).roundToInt() val texture: Texture,
val alb = albedo.coerceIn(0.1, 0.9).minus(0.1).times(turbDivisor).roundToInt() val uvs: FloatArray,
val region = texStripRegions.get(alb, turb) val turbidityPoint: Float,
val albedoPoint: Float,
)
val elev = elevationDeg.coerceIn(-elevMax, elevMax).plus(elevMax).div(elevations.last.toDouble()).div(albedoCnt).times((elevCnt - 1.0) / elevCnt) fun getUV(elevationDeg: Double, turbidity: Double, albedo: Double): SkyboxRenderInfo {
val turb = turbidity.coerceIn(turbiditiesD.first(), turbiditiesD.last()).minus(1.0).times(turbDivisor)
val turbLo = turb.floorToInt()
val turbHi = min(turbCnt - 1, turbLo + 1)
val alb = albedo.coerceIn(albedos.first(), albedos.last()).times(5.0)
val albLo = alb.floorToInt()
val albHi = min(albedoCnt - 1, albLo + 1)
val elev = elevationDeg.coerceIn(-elevMax, elevMax).plus(elevMax).div(elevations.last.toDouble()).div(albedoCnt * 2).times((elevCnt - 1.0) / elevCnt)
val u = region.u + (0.5f / tex.width) + elev.toFloat() // because of the nature of bilinear interpolation, half pixels from the edges must be discarded // A: morn, turbLow, albLow
// B: noon, turbLow, albLow
// C: morn, turbHigh, albLow
// D: noon, turbHigh, albLow
// E: morn, turbLow, albHigh
// F: noon, turbLow, albHigh
// G: morn, turbHigh, albHigh
// H: noon, turbHigh, albHigh
return tex to floatArrayOf( val regionA = texStripRegions.get(albLo + albedoCnt * 0, turbLo)
u, val regionB = texStripRegions.get(albLo + albedoCnt * 1, turbLo)
region.v, val regionC = texStripRegions.get(albLo + albedoCnt * 0, turbHi)
u, val regionD = texStripRegions.get(albLo + albedoCnt * 1, turbHi)
region.v2 val regionE = texStripRegions.get(albHi + albedoCnt * 0, turbLo)
val regionF = texStripRegions.get(albHi + albedoCnt * 1, turbLo)
val regionG = texStripRegions.get(albHi + albedoCnt * 0, turbHi)
val regionH = texStripRegions.get(albHi + albedoCnt * 1, turbHi)
// (0.5f / tex.width): because of the nature of bilinear interpolation, half pixels from the edges must be discarded
val uA = regionA.u + (0.5f / tex.width) + elev.toFloat()
val uB = regionB.u + (0.5f / tex.width) + elev.toFloat()
val uC = regionC.u + (0.5f / tex.width) + elev.toFloat()
val uD = regionD.u + (0.5f / tex.width) + elev.toFloat()
val uE = regionE.u + (0.5f / tex.width) + elev.toFloat()
val uF = regionF.u + (0.5f / tex.width) + elev.toFloat()
val uG = regionG.u + (0.5f / tex.width) + elev.toFloat()
val uH = regionH.u + (0.5f / tex.width) + elev.toFloat()
return SkyboxRenderInfo(
tex,
floatArrayOf(
uA, regionA.v, uA, regionA.v2,
uB, regionB.v, uB, regionB.v2,
uC, regionC.v, uC, regionC.v2,
uD, regionD.v, uD, regionD.v2,
uE, regionE.v, uE, regionE.v2,
uF, regionF.v, uF, regionF.v2,
uG, regionG.v, uG, regionG.v2,
uH, regionH.v, uH, regionH.v2,
),
(turb - turbLo).toFloat(),
(alb - albLo).toFloat(),
) )
} }
@@ -88,15 +130,20 @@ object Skybox : Disposable {
) )
} }
else { else {
val deg1 = (-elevationDeg / elevMax).pow(0.93).times(-elevMax) // maths model: https://www.desmos.com/calculator/cwi7iyzygg
val elevation1 = -deg1
val elevation2 = -deg1 / 28.5 val x = -elevationDeg.toFloat()
val scale = (1f - (1f - 1f / 1.8.pow(elevation1)) * 0.97f).toFloat() // val elevation2 = elevationDeg.toFloat() / 28.5f
val scale2 = (1.0 - (elevation2.pow(E) / E.pow(elevation2))*0.8).toFloat() val p = 3.5f
val q = 7.5f
val s = -0.2f
val f = (1f - (1f - 1f / 1.8f.pow(x)) * 0.97f).toFloat()
// val g = (1.0 - (elevation2.pow(E) / E.pow(elevation2))*0.8).toFloat()
val h = ((x / q).pow(p) + 1f).pow(s)
CIEXYZ( CIEXYZ(
this.X.scaleFun() * scale * scale2, this.X.scaleFun() * f * h,
this.Y.scaleFun() * scale * scale2, this.Y.scaleFun() * f * h,
this.Z.scaleFun() * scale * scale2, this.Z.scaleFun() * f * h,
this.alpha this.alpha
) )
} }
@@ -105,15 +152,13 @@ object Skybox : Disposable {
val elevations = (0..150) val elevations = (0..150)
val elevMax = elevations.last / 2.0 val elevMax = elevations.last / 2.0
val elevationsD = elevations.map { -elevMax + it } // -75, -74, -73, ..., 74, 75 // (specifically using whole number of angles because angle units any finer than 1.0 would make "hack" sunsut happen too fast) val elevationsD = elevations.map { -elevMax + it } // -75, -74, -73, ..., 74, 75 // (specifically using whole number of angles because angle units any finer than 1.0 would make "hack" sunsut happen too fast)
val turbidities = (0..45) // 1, 1.2, 1.4, 1.6, ..., 10.0 val turbidities = (0..25) // 1, 1.2, 1.4, 1.6, ..., 6.0
val turbDivisor = 5.0 val turbDivisor = 5.0
val turbiditiesD = turbidities.map { 1.0 + it / turbDivisor } val turbiditiesD = turbidities.map { 1.0 + it / turbDivisor }
val albedos = arrayOf(0.1, 0.3, 0.5, 0.7, 0.9) val albedos = arrayOf(0.0, 0.2, 0.4, 0.6, 0.8, 1.0)
val elevCnt = elevations.count() val elevCnt = elevations.count()
val turbCnt = turbidities.count() val turbCnt = turbidities.count()
val albedoCnt = albedos.size val albedoCnt = albedos.size
val albedoLow = 0.1
val albedoHight = 0.8 // for theoretical "winter wonderland"?
val gamma = HALF_PI val gamma = HALF_PI
internal fun Double.mapCircle() = sin(HALF_PI * this) internal fun Double.mapCircle() = sin(HALF_PI * this)
@@ -121,8 +166,7 @@ object Skybox : Disposable {
internal fun initiate() { internal fun initiate() {
printdbg(this, "Initialising skybox model") printdbg(this, "Initialising skybox model")
gradTexBinLowAlbedo = getTexturmaps(albedoLow) TODO()
gradTexBinHighAlbedo = getTexturmaps(albedoHight)
App.disposables.add(this) App.disposables.add(this)
@@ -198,7 +242,7 @@ object Skybox : Disposable {
// printdbg(this, "elev $elevationDeg turb $turbidity") // printdbg(this, "elev $elevationDeg turb $turbidity")
for (yp in 0 until gradSize) { for (yp in 0 until gradSize) {
val yi = yp - 3 val yi = yp - 10
val xf = -elevationDeg / 90.0 val xf = -elevationDeg / 90.0
var yf = (yi / 58.0).coerceIn(0.0, 1.0).mapCircle().coerceInSmoothly(0.0, 0.95) var yf = (yi / 58.0).coerceIn(0.0, 1.0).mapCircle().coerceInSmoothly(0.0, 0.95)

View File

@@ -154,7 +154,7 @@ internal object WeatherMixer : RNGConsumer {
} }
var turbidity = 4.0; private set var turbidity = 1.0; private set
private var gH = 1.4f * App.scr.height private var gH = 1.4f * App.scr.height
// private var gH = 0.8f * App.scr.height // private var gH = 0.8f * App.scr.height
@@ -168,9 +168,11 @@ internal object WeatherMixer : RNGConsumer {
drawSkybox(camera, batch, world) drawSkybox(camera, batch, world)
} }
private val parallaxDomainSize = 400f
private val turbidityDomainSize = 533.3333f
private fun drawSkybox(camera: Camera, batch: FlippingSpriteBatch, world: GameWorld) { private fun drawSkybox(camera: Camera, batch: FlippingSpriteBatch, world: GameWorld) {
val parallaxZeroPos = (world.height / 3f) val parallaxZeroPos = (world.height / 3f)
val parallaxDomainSize = 300f
// we will not care for nextSkybox for now // we will not care for nextSkybox for now
val timeNow = (forceTimeAt ?: world.worldTime.TIME_T.toInt()) % WorldTime.DAY_LENGTH val timeNow = (forceTimeAt ?: world.worldTime.TIME_T.toInt()) % WorldTime.DAY_LENGTH
@@ -199,17 +201,20 @@ internal object WeatherMixer : RNGConsumer {
-+ <- 0.0 = -+ <- 0.0 =
*/ */
val parallax = ((parallaxZeroPos - WorldCamera.gdxCamY.div(TILE_SIZEF)) / parallaxDomainSize).times(-1f).coerceIn(-1f, 1f) val parallax = ((parallaxZeroPos - WorldCamera.gdxCamY.div(TILE_SIZEF)) / parallaxDomainSize).times(-1f).coerceIn(-1f, 1f)
val turbidityCoeff = ((parallaxZeroPos - WorldCamera.gdxCamY.div(TILE_SIZEF)) / turbidityDomainSize).times(-1f).coerceIn(-1f, 1f)
parallaxPos = parallax parallaxPos = parallax
// println(parallax) // parallax value works as intended. // println(parallax) // parallax value works as intended.
gdxBlendNormalStraightAlpha() gdxBlendNormalStraightAlpha()
turbidity = (3.5 + turbidityCoeff * 2.5).coerceIn(1.0, 6.0)
val thisTurbidity = forceTurbidity ?: turbidity val thisTurbidity = forceTurbidity ?: turbidity
// TODO trilinear with (deg, turb, alb)
val gradY = -(gH - App.scr.height) * ((parallax + 1f) / 2f) val gradY = -(gH - App.scr.height) * ((parallax + 1f) / 2f)
val (tex, uvs) = Skybox.getUV(solarElev, thisTurbidity, 0.3)
val (tex, uvs, turbX, albX) = Skybox.getUV(solarElev, thisTurbidity, 0.3)
val mornNoonBlend = (1f/4000f * (timeNow - 43200) + 0.5f).coerceIn(0f, 1f) // 0.0 at T41200; 0.5 at T43200; 1.0 at T45200;
starmapTex.texture.bind(1) starmapTex.texture.bind(1)
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0) // so that batch that comes next will bind any tex to it Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0) // so that batch that comes next will bind any tex to it
@@ -220,20 +225,26 @@ internal object WeatherMixer : RNGConsumer {
batch.inUse { batch.inUse {
batch.shader = shaderBlendMax batch.shader = shaderBlendMax
shaderBlendMax.setUniformi("tex1", 1) shaderBlendMax.setUniformi("tex1", 1)
shaderBlendMax.setUniformf("drawOffset", 0f, gradY) shaderBlendMax.setUniformf("drawOffsetSize", 0f, gradY, App.scr.wf, gH)
shaderBlendMax.setUniformf("drawOffsetSize", App.scr.wf, gH) shaderBlendMax.setUniform4fv("uvA", uvs, 0, 4)
shaderBlendMax.setUniform2fv("skyboxUV1", uvs, 0, 2) shaderBlendMax.setUniform4fv("uvB", uvs, 4, 4)
shaderBlendMax.setUniform2fv("skyboxUV2", uvs, 2, 2) shaderBlendMax.setUniform4fv("uvC", uvs, 8, 4)
shaderBlendMax.setUniform4fv("uvD", uvs, 12, 4)
shaderBlendMax.setUniform4fv("uvE", uvs, 16, 4)
shaderBlendMax.setUniform4fv("uvF", uvs, 20, 4)
shaderBlendMax.setUniform4fv("uvG", uvs, 24, 4)
shaderBlendMax.setUniform4fv("uvH", uvs, 28, 4)
shaderBlendMax.setUniformf("texBlend", mornNoonBlend, turbX, albX, 0f)
shaderBlendMax.setUniformf("astrumScroll", astrumOffX + astrumX, astrumOffY + astrumY) shaderBlendMax.setUniformf("astrumScroll", astrumOffX + astrumX, astrumOffY + astrumY)
shaderBlendMax.setUniformf("randomNumber", shaderBlendMax.setUniformf("randomNumber",
// (world.worldTime.TIME_T.plus(31L) xor 1453L + 31L).and(1023).toFloat(), // (world.worldTime.TIME_T.plus(31L) xor 1453L + 31L).and(1023).toFloat(),
// (world.worldTime.TIME_T.plus(37L) xor 862L + 31L).and(1023).toFloat(), // (world.worldTime.TIME_T.plus(37L) xor 862L + 31L).and(1023).toFloat(),
// (world.worldTime.TIME_T.plus(23L) xor 1639L + 29L).and(1023).toFloat(), // (world.worldTime.TIME_T.plus(23L) xor 1639L + 29L).and(1023).toFloat(),
// (world.worldTime.TIME_T.plus(29L) xor 2971L + 41L).and(1023).toFloat(), // (world.worldTime.TIME_T.plus(29L) xor 2971L + 41L).and(1023).toFloat(),
world.worldTime.TIME_T.div(+12.1f).plus(31L), world.worldTime.TIME_T.div(+14.1f).plus(31L),
world.worldTime.TIME_T.div(-11.8f).plus(37L), world.worldTime.TIME_T.div(-13.8f).plus(37L),
world.worldTime.TIME_T.div(+11.9f).plus(23L), world.worldTime.TIME_T.div(+13.9f).plus(23L),
world.worldTime.TIME_T.div(-12.3f).plus(29L), world.worldTime.TIME_T.div(-14.3f).plus(29L),
) )
batch.color = Color.WHITE batch.color = Color.WHITE

View File

@@ -13,10 +13,16 @@ out vec4 fragColor;
const vec2 boolean = vec2(0.0, 1.0); const vec2 boolean = vec2(0.0, 1.0);
uniform vec2 drawOffset; // value of the 'gradY' uniform vec4 drawOffsetSize; // (gradX, gradY, gradW, gradH)
uniform vec2 drawOffsetSize; // value of the 'gradH' uniform vec4 uvA; // (u, v, u2, v2) for morn, turbLow, albLow
uniform vec2 skyboxUV1; // (u, v) for the skybox drawing uniform vec4 uvB; // (u, v, u2, v2) for noon, turbLow, albLow
uniform vec2 skyboxUV2; // (u2, v2) for the skybox drawing uniform vec4 uvC; // (u, v, u2, v2) for morn, turbHigh, albLow
uniform vec4 uvD; // (u, v, u2, v2) for noon, turbHigh, albLow
uniform vec4 uvE; // (u, v, u2, v2) for morn, turbLow, albHigh
uniform vec4 uvF; // (u, v, u2, v2) for noon, turbLow, albHigh
uniform vec4 uvG; // (u, v, u2, v2) for morn, turbHigh, albHigh
uniform vec4 uvH; // (u, v, u2, v2) for noon, turbHigh, albHigh
uniform vec4 texBlend; // (morn/noon, turbidity, albedo, unused)
uniform vec2 tex1Size = vec2(4096.0); uniform vec2 tex1Size = vec2(4096.0);
uniform vec2 astrumScroll = vec2(0.0); uniform vec2 astrumScroll = vec2(0.0);
uniform vec4 randomNumber = vec4(1.0, -2.0, 3.0, -4.0); uniform vec4 randomNumber = vec4(1.0, -2.0, 3.0, -4.0);
@@ -112,15 +118,36 @@ vec4 random(vec2 p) {
// draw call to this function must use UV coord of (0,0,1,1)! // draw call to this function must use UV coord of (0,0,1,1)!
void main(void) { void main(void) {
vec2 skyboxTexCoord = mix(skyboxUV1, skyboxUV2, v_texCoords); vec4 colorTexA = texture(u_texture, mix(uvA.xy, uvA.zw, v_texCoords));
vec2 astrumTexCoord = (v_texCoords * drawOffsetSize + drawOffset + astrumScroll) / tex1Size; vec4 colorTexB = texture(u_texture, mix(uvB.xy, uvB.zw, v_texCoords));
vec4 colorTexC = texture(u_texture, mix(uvC.xy, uvC.zw, v_texCoords));
vec4 colorTexD = texture(u_texture, mix(uvD.xy, uvD.zw, v_texCoords));
vec4 colorTexE = texture(u_texture, mix(uvE.xy, uvE.zw, v_texCoords));
vec4 colorTexF = texture(u_texture, mix(uvF.xy, uvF.zw, v_texCoords));
vec4 colorTexG = texture(u_texture, mix(uvG.xy, uvG.zw, v_texCoords));
vec4 colorTexH = texture(u_texture, mix(uvH.xy, uvH.zw, v_texCoords));
vec2 astrumTexCoord = (v_texCoords * drawOffsetSize.zw + drawOffsetSize.xy + astrumScroll) / tex1Size;
vec4 randomness = snoise4((gl_FragCoord.xy - astrumScroll) * 0.16) * 2.0; // multiply by 2 so that the "density" of the stars would be same as the non-random version vec4 randomness = snoise4((gl_FragCoord.xy - astrumScroll) * 0.16) * 2.0; // multiply by 2 so that the "density" of the stars would be same as the non-random version
vec4 colorTex0 = texture(u_texture, skyboxTexCoord);
vec4 colorTex1 = texture(tex1, astrumTexCoord) * randomness; vec4 colorTex1 = texture(tex1, astrumTexCoord) * randomness;
// notations used: https://en.wikipedia.org/wiki/File:Enclosing_points.svg and https://en.wikipedia.org/wiki/File:3D_interpolation2.svg
vec4 colorTex0 = mix(
mix(
mix(colorTexA, colorTexE, texBlend.z), // c00 = c000..c100
mix(colorTexC, colorTexG, texBlend.z), // c10 = c010..c110
texBlend.y
), // c0 = c00..c10
mix(
mix(colorTexB, colorTexF, texBlend.z), // c01 = c001..c101
mix(colorTexD, colorTexH, texBlend.z), // c11 = c011..c111
texBlend.y
), // c1 = c01..c11
texBlend.x
); // c = c0..c1
fragColor = (max(colorTex0, colorTex1) * boolean.yyyx) + boolean.xxxy; fragColor = (max(colorTex0, colorTex1) * boolean.yyyx) + boolean.xxxy;