mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 20:31:51 +09:00
skybox atlas texture generation
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -107,13 +107,13 @@ import kotlin.test.assertEquals
|
||||
|
||||
object DatasetCIEXYZ {
|
||||
|
||||
val datasetXYZ1 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetXYZ1.bin")
|
||||
val datasetXYZ2 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetXYZ2.bin")
|
||||
val datasetXYZ3 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetXYZ3.bin")
|
||||
val datasetXYZ1 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetXYZ1.bin")
|
||||
val datasetXYZ2 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetXYZ2.bin")
|
||||
val datasetXYZ3 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetXYZ3.bin")
|
||||
|
||||
val datasetXYZRad1 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetXYZRad1.bin")
|
||||
val datasetXYZRad2 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetXYZRad2.bin")
|
||||
val datasetXYZRad3 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetXYZRad3.bin")
|
||||
val datasetXYZRad1 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetXYZRad1.bin")
|
||||
val datasetXYZRad2 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetXYZRad2.bin")
|
||||
val datasetXYZRad3 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetXYZRad3.bin")
|
||||
|
||||
init {
|
||||
assertEquals(1080, datasetXYZ2.size, "Dataset size mismatch: expected 1080, got ${datasetXYZ2.size}")
|
||||
|
||||
@@ -107,13 +107,13 @@ import kotlin.test.assertEquals
|
||||
|
||||
object DatasetRGB {
|
||||
|
||||
val datasetRGB1 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetRGB1.bin")
|
||||
val datasetRGB2 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetRGB2.bin")
|
||||
val datasetRGB3 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetRGB3.bin")
|
||||
val datasetRGB1 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRGB1.bin")
|
||||
val datasetRGB2 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRGB2.bin")
|
||||
val datasetRGB3 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRGB3.bin")
|
||||
|
||||
val datasetRGBRad1 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetRGBRad1.bin")
|
||||
val datasetRGBRad2 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetRGBRad2.bin")
|
||||
val datasetRGBRad3 = DatasetOp.readDatasetFromFile("./assets/clut/hosek/datasetRGBRad3.bin")
|
||||
val datasetRGBRad1 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRGBRad1.bin")
|
||||
val datasetRGBRad2 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRGBRad2.bin")
|
||||
val datasetRGBRad3 = DatasetOp.readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRGBRad3.bin")
|
||||
|
||||
init {
|
||||
assertEquals(1080, datasetRGB2.size, "Dataset size mismatch: expected 1080, got ${datasetRGB2.size}")
|
||||
|
||||
@@ -108,41 +108,41 @@ import kotlin.test.assertEquals
|
||||
|
||||
object DatasetSpectral {
|
||||
|
||||
val dataset320 = readDatasetFromFile("./assets/clut/hosek/dataset320.bin")
|
||||
val dataset360 = readDatasetFromFile("./assets/clut/hosek/dataset360.bin")
|
||||
val dataset400 = readDatasetFromFile("./assets/clut/hosek/dataset400.bin")
|
||||
val dataset440 = readDatasetFromFile("./assets/clut/hosek/dataset440.bin")
|
||||
val dataset480 = readDatasetFromFile("./assets/clut/hosek/dataset480.bin")
|
||||
val dataset520 = readDatasetFromFile("./assets/clut/hosek/dataset520.bin")
|
||||
val dataset560 = readDatasetFromFile("./assets/clut/hosek/dataset560.bin")
|
||||
val dataset600 = readDatasetFromFile("./assets/clut/hosek/dataset600.bin")
|
||||
val dataset640 = readDatasetFromFile("./assets/clut/hosek/dataset640.bin")
|
||||
val dataset680 = readDatasetFromFile("./assets/clut/hosek/dataset680.bin")
|
||||
val dataset720 = readDatasetFromFile("./assets/clut/hosek/dataset720.bin")
|
||||
val dataset320 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset320.bin")
|
||||
val dataset360 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset360.bin")
|
||||
val dataset400 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset400.bin")
|
||||
val dataset440 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset440.bin")
|
||||
val dataset480 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset480.bin")
|
||||
val dataset520 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset520.bin")
|
||||
val dataset560 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset560.bin")
|
||||
val dataset600 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset600.bin")
|
||||
val dataset640 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset640.bin")
|
||||
val dataset680 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset680.bin")
|
||||
val dataset720 = readDatasetFromFile("./work_files/skylight/hosek_model_source/dataset720.bin")
|
||||
|
||||
val datasetRad320 = readDatasetFromFile("./assets/clut/hosek/datasetRad320.bin")
|
||||
val datasetRad360 = readDatasetFromFile("./assets/clut/hosek/datasetRad360.bin")
|
||||
val datasetRad400 = readDatasetFromFile("./assets/clut/hosek/datasetRad400.bin")
|
||||
val datasetRad440 = readDatasetFromFile("./assets/clut/hosek/datasetRad440.bin")
|
||||
val datasetRad480 = readDatasetFromFile("./assets/clut/hosek/datasetRad480.bin")
|
||||
val datasetRad520 = readDatasetFromFile("./assets/clut/hosek/datasetRad520.bin")
|
||||
val datasetRad560 = readDatasetFromFile("./assets/clut/hosek/datasetRad560.bin")
|
||||
val datasetRad600 = readDatasetFromFile("./assets/clut/hosek/datasetRad600.bin")
|
||||
val datasetRad640 = readDatasetFromFile("./assets/clut/hosek/datasetRad640.bin")
|
||||
val datasetRad680 = readDatasetFromFile("./assets/clut/hosek/datasetRad680.bin")
|
||||
val datasetRad720 = readDatasetFromFile("./assets/clut/hosek/datasetRad720.bin")
|
||||
val datasetRad320 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad320.bin")
|
||||
val datasetRad360 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad360.bin")
|
||||
val datasetRad400 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad400.bin")
|
||||
val datasetRad440 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad440.bin")
|
||||
val datasetRad480 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad480.bin")
|
||||
val datasetRad520 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad520.bin")
|
||||
val datasetRad560 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad560.bin")
|
||||
val datasetRad600 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad600.bin")
|
||||
val datasetRad640 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad640.bin")
|
||||
val datasetRad680 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad680.bin")
|
||||
val datasetRad720 = readDatasetFromFile("./work_files/skylight/hosek_model_source/datasetRad720.bin")
|
||||
|
||||
val solarDataset320 = readDatasetFromFile("./assets/clut/hosek/solarDataset320.bin")
|
||||
val solarDataset360 = readDatasetFromFile("./assets/clut/hosek/solarDataset360.bin")
|
||||
val solarDataset400 = readDatasetFromFile("./assets/clut/hosek/solarDataset400.bin")
|
||||
val solarDataset440 = readDatasetFromFile("./assets/clut/hosek/solarDataset440.bin")
|
||||
val solarDataset480 = readDatasetFromFile("./assets/clut/hosek/solarDataset480.bin")
|
||||
val solarDataset520 = readDatasetFromFile("./assets/clut/hosek/solarDataset520.bin")
|
||||
val solarDataset560 = readDatasetFromFile("./assets/clut/hosek/solarDataset560.bin")
|
||||
val solarDataset600 = readDatasetFromFile("./assets/clut/hosek/solarDataset600.bin")
|
||||
val solarDataset640 = readDatasetFromFile("./assets/clut/hosek/solarDataset640.bin")
|
||||
val solarDataset680 = readDatasetFromFile("./assets/clut/hosek/solarDataset680.bin")
|
||||
val solarDataset720 = readDatasetFromFile("./assets/clut/hosek/solarDataset720.bin")
|
||||
val solarDataset320 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset320.bin")
|
||||
val solarDataset360 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset360.bin")
|
||||
val solarDataset400 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset400.bin")
|
||||
val solarDataset440 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset440.bin")
|
||||
val solarDataset480 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset480.bin")
|
||||
val solarDataset520 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset520.bin")
|
||||
val solarDataset560 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset560.bin")
|
||||
val solarDataset600 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset600.bin")
|
||||
val solarDataset640 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset640.bin")
|
||||
val solarDataset680 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset680.bin")
|
||||
val solarDataset720 = readDatasetFromFile("./work_files/skylight/hosek_model_source/solarDataset720.bin")
|
||||
|
||||
init {
|
||||
assertEquals(1080, dataset600.size, "Dataset size mismatch - expected 1080, got ${dataset600.size}")
|
||||
|
||||
@@ -241,7 +241,8 @@ class TitleScreen(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
||||
uiContainer.add(uiRemoCon)
|
||||
|
||||
CommandDict // invoke
|
||||
Skybox // invoke
|
||||
Skybox.loadlut() // invoke
|
||||
// Skybox.initiate() // invoke the lengthy calculation
|
||||
// TODO add console here
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package net.torvald.terrarum.modulebasegame.clut
|
||||
|
||||
import net.torvald.colourutil.CIEXYZ
|
||||
import net.torvald.colourutil.toColor
|
||||
import net.torvald.colourutil.toRGB
|
||||
import net.torvald.parametricsky.ArHosekSkyModel
|
||||
import net.torvald.terrarum.abs
|
||||
import net.torvald.terrarum.modulebasegame.clut.Skybox.coerceInSmoothly
|
||||
import net.torvald.terrarum.modulebasegame.clut.Skybox.mapCircle
|
||||
import net.torvald.terrarum.modulebasegame.clut.Skybox.scaleToFit
|
||||
import net.torvald.terrarum.modulebasegame.worldgenerator.HALF_PI
|
||||
import net.torvald.terrarum.serialise.toLittle
|
||||
import java.io.File
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2023-08-01.
|
||||
*/
|
||||
fun main() {
|
||||
// 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])
|
||||
val texh = Skybox.gradSize * Skybox.turbCnt
|
||||
val texw = 2 * Skybox.elevCnt * 5
|
||||
val TGA_HEADER_SIZE = 18
|
||||
|
||||
val bytes = ByteArray(TGA_HEADER_SIZE + texw * texh * 4 + 26)
|
||||
// write header
|
||||
byteArrayOf(
|
||||
0, // ID field
|
||||
0, // colour map (none)
|
||||
2, // colour type (unmapped RGB)
|
||||
0,0,0,0,0, // colour map spec (empty)
|
||||
0,0, // x origin (0)
|
||||
0,0, // y origin (0)
|
||||
(texw and 255).toByte(),(texw.ushr(8) and 255).toByte(), // width
|
||||
(texh and 255).toByte(),(texh.ushr(8) and 255).toByte(), // height
|
||||
32, // bits-per-pixel (8bpp RGBA)
|
||||
8 // image descriptor
|
||||
).forEachIndexed { i,b -> bytes[i] = b }
|
||||
// write footer
|
||||
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000TRUEVISION-XFILE\u002E\u0000".forEachIndexed { i, c -> bytes[18 + texw * texh * 4 + i] =
|
||||
c.code.toByte()
|
||||
}
|
||||
|
||||
println("Generating texture atlas ($texw x $texh)...")
|
||||
|
||||
// write pixels
|
||||
for (albedo0 in 0 until Skybox.albedoCnt) {
|
||||
val albedo = Skybox.albedos[albedo0]
|
||||
println("Albedo=$albedo")
|
||||
for (turb0 in 0 until Skybox.turbCnt) {
|
||||
val turbidity = Skybox.turbiditiesD[turb0]
|
||||
println("....... Turbidity=$turbidity")
|
||||
for (elev0 in 0 until Skybox.elevCnt) {
|
||||
val elevationDeg = Skybox.elevationsD[elev0]
|
||||
val elevationRad = Math.toRadians(elevationDeg)
|
||||
// println("... Elevation: $elevationDeg")
|
||||
|
||||
val state = ArHosekSkyModel.arhosek_xyz_skymodelstate_alloc_init(turbidity, albedo, elevationRad.abs())
|
||||
|
||||
for (yp in 0 until Skybox.gradSize) {
|
||||
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)
|
||||
|
||||
// experiments visualisation: https://www.desmos.com/calculator/5crifaekwa
|
||||
// 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 *= Skybox.superellipsoidDecay(1.0 / 3.0, xf)
|
||||
val theta = yf * HALF_PI
|
||||
// vertical angle, where 0 is zenith, ±90 is ground (which is odd)
|
||||
|
||||
// println("$yp\t$theta")
|
||||
|
||||
val xyz = CIEXYZ(
|
||||
ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, HALF_PI, 0).toFloat(),
|
||||
ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, HALF_PI, 1).toFloat(),
|
||||
ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, HALF_PI, 2).toFloat()
|
||||
)
|
||||
val xyz2 = xyz.scaleToFit(elevationDeg)
|
||||
val rgb = xyz2.toRGB().toColor()
|
||||
val colour = rgb.toIntBits().toLittle()
|
||||
|
||||
val imgOffX = 2 * (albedo0 * Skybox.elevCnt + elev0)
|
||||
val imgOffY = texh - 1 - (Skybox.gradSize * turb0 + yp)
|
||||
val fileOffset = TGA_HEADER_SIZE + 4 * (imgOffY * texw + imgOffX)
|
||||
for (i in 0..7) {
|
||||
bytes[fileOffset + i] = colour[bytesLut[i]]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println("Atlas generation done!")
|
||||
|
||||
File("./assets/mods/basegame/weathers/main_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
|
||||
@@ -2,6 +2,7 @@ package net.torvald.terrarum.modulebasegame.clut
|
||||
|
||||
import com.badlogic.gdx.graphics.Pixmap
|
||||
import com.badlogic.gdx.graphics.Texture
|
||||
import com.badlogic.gdx.graphics.g2d.TextureRegion
|
||||
import com.badlogic.gdx.utils.Disposable
|
||||
import com.jme3.math.FastMath
|
||||
import net.torvald.colourutil.CIEXYZ
|
||||
@@ -10,8 +11,10 @@ import net.torvald.colourutil.toRGB
|
||||
import net.torvald.parametricsky.ArHosekSkyModel
|
||||
import net.torvald.terrarum.App
|
||||
import net.torvald.terrarum.App.printdbg
|
||||
import net.torvald.terrarum.ModMgr
|
||||
import net.torvald.terrarum.abs
|
||||
import net.torvald.terrarum.modulebasegame.worldgenerator.HALF_PI
|
||||
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
|
||||
import kotlin.math.*
|
||||
|
||||
/**
|
||||
@@ -21,29 +24,39 @@ object Skybox : Disposable {
|
||||
|
||||
const val gradSize = 64
|
||||
|
||||
private val gradTexBinLowAlbedo: Array<Texture>
|
||||
private val gradTexBinHighAlbedo: Array<Texture>
|
||||
private lateinit var gradTexBinLowAlbedo: Array<TextureRegion>
|
||||
private lateinit var gradTexBinHighAlbedo: Array<TextureRegion>
|
||||
|
||||
operator fun get(elevationDeg: Double, turbidity: Double, highAlbedo: Boolean = false): Texture {
|
||||
// if (elevationDeg !in elevationsD) {
|
||||
// throw IllegalArgumentException("Elevation not in ±75° (got $elevationDeg)")
|
||||
// }
|
||||
// if (turbidity !in turbiditiesD) {
|
||||
// throw IllegalArgumentException("Turbidity not in 1..10 (got $turbidity)")
|
||||
// }
|
||||
private lateinit var tex: Texture
|
||||
private lateinit var texRegions: TextureRegionPack
|
||||
|
||||
val elev = elevationDeg.coerceIn(elevationsD).toInt() - elevations.first
|
||||
val turb = ((turbidity.coerceIn(turbiditiesD) - turbiditiesD.start) / (turbidities.step / 10.0)).toInt()
|
||||
fun loadlut() {
|
||||
tex = Texture(ModMgr.getGdxFile("basegame", "weathers/main_skybox.png"))
|
||||
tex.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)
|
||||
texRegions = TextureRegionPack(tex, 2, gradSize - 2, 0, 2, 0, 1)
|
||||
}
|
||||
|
||||
// printdbg(this, "$elevationDeg $turbidity ; $elev $turb")
|
||||
// use internal LUT
|
||||
/*operator fun get(elevationDeg: Double, turbidity: Double, albedo: Double): TextureRegion {
|
||||
val elev = elevationDeg.coerceIn(-75.0, 75.0).times(2.0).roundToInt().plus(150)
|
||||
val turb = turbidity.coerceIn(1.0, 10.0).minus(1.0).times(3.0).roundToInt()
|
||||
val alb = albedo.coerceIn(0.1, 0.9).minus(0.1).times(5.0).roundToInt()
|
||||
return gradTexBinLowAlbedo[elev * turbCnt + turb]
|
||||
}*/
|
||||
|
||||
return (if (highAlbedo) gradTexBinHighAlbedo else gradTexBinLowAlbedo)[elev * turbCnt + turb]
|
||||
// use external LUT
|
||||
operator fun get(elevationDeg: Double, turbidity: Double, albedo: Double): TextureRegion {
|
||||
val elev = elevationDeg.coerceIn(-75.0, 75.0).roundToInt().plus(75)
|
||||
val turb = turbidity.coerceIn(1.0, 10.0).minus(1.0).times(3.0).roundToInt()
|
||||
val alb = albedo.coerceIn(0.1, 0.9).minus(0.1).times(5.0).roundToInt()
|
||||
//printdbg(this, "elev $elevationDeg->$elev; turb $turbidity->$turb; alb $albedo->$alb")
|
||||
return texRegions.get(alb * elevCnt + elev, turb)
|
||||
}
|
||||
|
||||
private fun Float.scaleFun() =
|
||||
(1f - 1f / 2f.pow(this/6f)) * 0.97f
|
||||
|
||||
private fun CIEXYZ.scaleToFit(elevationDeg: Double): CIEXYZ {
|
||||
internal fun CIEXYZ.scaleToFit(elevationDeg: Double): CIEXYZ {
|
||||
return if (elevationDeg >= 0) {
|
||||
CIEXYZ(
|
||||
this.X.scaleFun(),
|
||||
@@ -67,20 +80,21 @@ object Skybox : Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private val elevations = (-75..75) //zw 151
|
||||
private val elevationsD = (elevations.first.toDouble() .. elevations.last.toDouble())
|
||||
private val turbidityStep = 5
|
||||
private val turbidities = (1_0..10_0 step turbidityStep) // (100 / turbidityStep) - 1
|
||||
private val turbiditiesD = (turbidities.first / 10.0..turbidities.last / 10.0)
|
||||
private val elevCnt = elevations.count()
|
||||
private val turbCnt = turbidities.count()
|
||||
private val albedoLow = 0.1
|
||||
private val albedoHight = 0.8 // for theoretical "winter wonderland"?
|
||||
private val gamma = HALF_PI
|
||||
val elevations = (0..150) //
|
||||
val elevationsD = elevations.map { -75.0 + 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..27) // 1, 1.333, 1.666, 2, 2,333, ... , 10.0
|
||||
val turbiditiesD = turbidities.map { 1.0 + it / 3.0 }
|
||||
val albedos = arrayOf(0.1, 0.3, 0.5, 0.7, 0.9)
|
||||
val elevCnt = elevations.count()
|
||||
val turbCnt = turbidities.count()
|
||||
val albedoCnt = albedos.size
|
||||
val albedoLow = 0.1
|
||||
val albedoHight = 0.8 // for theoretical "winter wonderland"?
|
||||
val gamma = HALF_PI
|
||||
|
||||
private fun Double.mapCircle() = sin(HALF_PI * this)
|
||||
internal fun Double.mapCircle() = sin(HALF_PI * this)
|
||||
|
||||
init {
|
||||
internal fun initiate() {
|
||||
printdbg(this, "Initialising skybox model")
|
||||
|
||||
gradTexBinLowAlbedo = getTexturmaps(albedoLow)
|
||||
@@ -97,7 +111,7 @@ object Skybox : Disposable {
|
||||
* @param q polynomial degree. 2+. Larger value means sharper transition around the point p
|
||||
* @param x the 'x' value of the function, as in `y=f(x)`. 0.0..1.0
|
||||
*/
|
||||
private fun polynomialDecay(p: Double, q: Int, x: Double): Double {
|
||||
internal fun polynomialDecay(p: Double, q: Int, x: Double): Double {
|
||||
val sign = if (q % 2 == 1) -1 else 1
|
||||
val a1 = -1.0 / p
|
||||
val a2 = 1.0 / (1.0 - p)
|
||||
@@ -108,7 +122,7 @@ object Skybox : Disposable {
|
||||
sign * a2.pow(q - 1.0) * (x - 1.0).pow(q)
|
||||
}
|
||||
|
||||
private fun polynomialDecay2(p: Double, q: Int, x: Double): Double {
|
||||
internal fun polynomialDecay2(p: Double, q: Int, x: Double): Double {
|
||||
val sign = if (q % 2 == 1) 1 else -1
|
||||
val a1 = -1.0 / p
|
||||
val a2 = 1.0 / (1.0 - p)
|
||||
@@ -119,11 +133,11 @@ object Skybox : Disposable {
|
||||
sign * a2.pow(q - 1.0) * (x - 1.0).pow(q) + 1.0
|
||||
}
|
||||
|
||||
private fun superellipsoidDecay(p: Double, x: Double): Double {
|
||||
internal fun superellipsoidDecay(p: Double, x: Double): Double {
|
||||
return 1.0 - (1.0 - (1.0 - x).pow(1.0 / p)).pow(p)
|
||||
}
|
||||
|
||||
private fun Double.coerceInSmoothly(low: Double, high: Double): Double {
|
||||
internal fun Double.coerceInSmoothly(low: Double, high: Double): Double {
|
||||
val x = this.coerceIn(low, high)
|
||||
val x2 = ((x - low) * (high - low).pow(-1.0))
|
||||
// return FastMath.interpolateLinear(polynomialDecay2(0.5, 2, x2), low, high)
|
||||
@@ -133,25 +147,26 @@ object Skybox : Disposable {
|
||||
/**
|
||||
* To get the idea what the fuck is going on here, please refer to https://www.desmos.com/calculator/snqglcu2wl
|
||||
*/
|
||||
private fun smoothLinear(p: Double, x0: Double): Double {
|
||||
internal fun smoothLinear(p: Double, x0: Double): Double {
|
||||
val x = x0 - 0.5
|
||||
val t = 0.5 * sqrt(1.0 - 2.0 * p)
|
||||
val p1 = sqrt(1.0 - 2.0 * p)
|
||||
val t = 0.5 * p1
|
||||
val y0 = if (x < -t)
|
||||
(1.0 / p) * (x + 0.5).pow(2) - 0.5
|
||||
else if (x > t)
|
||||
-(1.0 / p) * (x - 0.5).pow(2) + 0.5
|
||||
else
|
||||
x * 2.0 / (1.0 + sqrt(1.0 - 2.0 * p))
|
||||
x * 2.0 / (1.0 + p1)
|
||||
|
||||
return y0 + 0.5
|
||||
}
|
||||
|
||||
private fun getTexturmaps(albedo: Double): Array<Texture> {
|
||||
private fun getTexturmaps(albedo: Double): Array<TextureRegion> {
|
||||
return Array(elevCnt * turbCnt) {
|
||||
|
||||
val elevationDeg = (it / turbCnt).plus(elevations.first).toDouble()
|
||||
val elevationDeg = elevationsD[it / turbCnt]
|
||||
val elevationRad = Math.toRadians(elevationDeg)
|
||||
val turbidity = 1.0 + (it % turbCnt) / (10.0 / turbidityStep)
|
||||
val turbidity = turbiditiesD[it % turbCnt]
|
||||
|
||||
val state = ArHosekSkyModel.arhosek_xyz_skymodelstate_alloc_init(turbidity, albedo, elevationRad.abs())
|
||||
val pixmap = Pixmap(1, gradSize, Pixmap.Format.RGBA8888)
|
||||
@@ -161,15 +176,17 @@ object Skybox : Disposable {
|
||||
for (yp in 0 until gradSize) {
|
||||
val yi = yp - 3
|
||||
val xf = -elevationDeg / 90.0
|
||||
var yf = (yi / 58.0).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
|
||||
// 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 *= superellipsoidDecay(1.0 / 3.0, xf)
|
||||
val theta = (yf.mapCircle() * HALF_PI)
|
||||
val theta = yf * HALF_PI
|
||||
// vertical angle, where 0 is zenith, ±90 is ground (which is odd)
|
||||
|
||||
// println("$yp\t$theta")
|
||||
|
||||
val xyz = CIEXYZ(
|
||||
ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, gamma, 0).toFloat(),
|
||||
ArHosekSkyModel.arhosek_tristim_skymodel_radiance(state, theta, gamma, 1).toFloat(),
|
||||
@@ -187,12 +204,13 @@ object Skybox : Disposable {
|
||||
it.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)
|
||||
}
|
||||
pixmap.dispose()
|
||||
texture
|
||||
TextureRegion(texture)
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
gradTexBinLowAlbedo.forEach { it.dispose() }
|
||||
gradTexBinHighAlbedo.forEach { it.dispose() }
|
||||
if (::gradTexBinLowAlbedo.isInitialized) gradTexBinLowAlbedo.forEach { it.texture.dispose() }
|
||||
if (::gradTexBinHighAlbedo.isInitialized) gradTexBinHighAlbedo.forEach { it.texture.dispose() }
|
||||
if (::tex.isInitialized) tex.dispose()
|
||||
}
|
||||
}
|
||||
@@ -152,7 +152,7 @@ internal object WeatherMixer : RNGConsumer {
|
||||
}
|
||||
|
||||
var turbidity = 4.0; private set
|
||||
private var gH = (4f/3f) * App.scr.height
|
||||
private var gH = 1.4f * App.scr.height
|
||||
// private var gH = 0.8f * App.scr.height
|
||||
|
||||
internal var parallaxPos = 0f; private set
|
||||
@@ -195,13 +195,17 @@ internal object WeatherMixer : RNGConsumer {
|
||||
|
||||
gdxBlendNormalStraightAlpha()
|
||||
|
||||
val degThis = if (timeNow < HALF_DAY) solarElev.floorToDouble() else solarElev.ceilToDouble()
|
||||
val degThis = if (timeNow < HALF_DAY)
|
||||
solarElev.floorToDouble()
|
||||
else
|
||||
solarElev.ceilToDouble()
|
||||
val degNext = degThis + if (timeNow < HALF_DAY) 1 else -1 // Skybox.get has internal coerceIn
|
||||
|
||||
val thisTurbidity = forceTurbidity ?: turbidity
|
||||
|
||||
val texture1 = Skybox[degThis, thisTurbidity]
|
||||
val texture2 = Skybox[degNext, thisTurbidity]
|
||||
// TODO trilinear with (deg, turb, alb)
|
||||
val texture1 = Skybox[degThis, thisTurbidity, 0.1]
|
||||
val texture2 = Skybox[degNext, thisTurbidity, 0.1]
|
||||
val lerpScale = (if (timeNow < HALF_DAY) solarElev - degThis else -(solarElev - degThis)).toFloat()
|
||||
|
||||
// println("degThis=$degThis, degNext=$degNext, lerp=$lerpScale")
|
||||
@@ -210,10 +214,10 @@ internal object WeatherMixer : RNGConsumer {
|
||||
batch.inUse {
|
||||
batch.shader = null
|
||||
batch.color = Color.WHITE
|
||||
batch.draw(texture1, 0f, gradY, App.scr.wf, gH)
|
||||
batch.draw(texture1, -App.scr.halfwf, gradY, 2f * App.scr.wf, gH)
|
||||
|
||||
batch.color = Color(1f, 1f, 1f, lerpScale)
|
||||
batch.draw(texture2, 0f, gradY, App.scr.wf, gH)
|
||||
batch.draw(texture2, -App.scr.halfwf, gradY, 2f * App.scr.wf, gH)
|
||||
|
||||
batch.color = Color.WHITE
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user