mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 20:31:51 +09:00
new lightmap: nice try but didn't work
This commit is contained in:
@@ -70,6 +70,21 @@ class Cvec {
|
||||
set(color)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get RGBA Element using index, of which:
|
||||
* - 0: R
|
||||
* - 1: G
|
||||
* - 2: B
|
||||
* - 3: A
|
||||
*/
|
||||
fun getElem(index: Int) = when(index) {
|
||||
0 -> r
|
||||
1 -> g
|
||||
2 -> b
|
||||
3 -> a
|
||||
else -> throw IndexOutOfBoundsException("Invalid index $index")
|
||||
}
|
||||
|
||||
/** Sets this color to the given color.
|
||||
*
|
||||
* @param color the Cvec
|
||||
|
||||
@@ -29,6 +29,18 @@ internal class UnsafeCvecArray(val width: Int, val height: Int) {
|
||||
fun setB(x: Int, y: Int, value: Float) { array.setFloat(toAddr(x, y) + 8, value) }
|
||||
fun setA(x: Int, y: Int, value: Float) { array.setFloat(toAddr(x, y) + 12, value) }
|
||||
|
||||
/**
|
||||
* @param channel 0 for R, 1 for G, 2 for B, 3 for A
|
||||
*/
|
||||
inline fun channelSet(x: Int, y: Int, channel: Int, value: Float) {
|
||||
array.setFloat(toAddr(x, y) + 4L * channel, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param channel 0 for R, 1 for G, 2 for B, 3 for A
|
||||
*/
|
||||
inline fun channelGet(x: Int, y: Int, channel: Int) = array.getFloat(toAddr(x, y) + 4L * channel)
|
||||
|
||||
fun max(x: Int, y: Int, other: Cvec) {
|
||||
setR(x, y, maxOf(getR(x, y), other.r))
|
||||
setG(x, y, maxOf(getG(x, y), other.g))
|
||||
|
||||
@@ -19,6 +19,14 @@ class BlockProp {
|
||||
|
||||
lateinit var opacity: Cvec
|
||||
|
||||
fun getOpacity(channel: Int) = when (channel) {
|
||||
0 -> shadeColR
|
||||
1 -> shadeColG
|
||||
2 -> shadeColB
|
||||
3 -> shadeColA
|
||||
else -> throw IllegalArgumentException("Invalid channel $channel")
|
||||
}
|
||||
|
||||
var strength: Int = 0
|
||||
var density: Int = 0
|
||||
var viscosity: Int = 0
|
||||
@@ -48,6 +56,16 @@ class BlockProp {
|
||||
inline val luminosity: Cvec
|
||||
get() = BlockPropUtil.getDynamicLumFunc(internalLumCol, dynamicLuminosityFunction)
|
||||
|
||||
fun getLum(channel: Int) = BlockPropUtil.getDynamicLumFuncByChan(
|
||||
when (channel) {
|
||||
0 -> lumColR
|
||||
1 -> lumColG
|
||||
2 -> lumColB
|
||||
3 -> lumColA
|
||||
else -> throw IllegalArgumentException("Invalid channel $channel")
|
||||
}, dynamicLuminosityFunction, channel
|
||||
)
|
||||
|
||||
var drop: Int = 0
|
||||
|
||||
var maxSupport: Int = -1 // couldn't use NULL at all...
|
||||
|
||||
@@ -37,22 +37,31 @@ object BlockPropUtil {
|
||||
|
||||
private fun getTorchFlicker(baseLum: Cvec): Cvec {
|
||||
val funcY = FastMath.interpolateLinear(flickerFuncX / flickerFuncDomain, flickerP0, flickerP1)
|
||||
|
||||
return alterBrightnessUniform(baseLum, funcY)
|
||||
}
|
||||
|
||||
private fun getTorchFlicker(baseLum: Float): Float {
|
||||
return baseLum + FastMath.interpolateLinear(flickerFuncX / flickerFuncDomain, flickerP0, flickerP1)
|
||||
}
|
||||
|
||||
private fun getSlowBreath(baseLum: Cvec): Cvec {
|
||||
val funcY = FastMath.sin(FastMath.PI * breathFuncX / breathCycleDuration) * breathRange
|
||||
|
||||
return alterBrightnessUniform(baseLum, funcY)
|
||||
}
|
||||
|
||||
private fun getSlowBreath(baseLum: Float): Float {
|
||||
return baseLum + FastMath.sin(FastMath.PI * breathFuncX / breathCycleDuration) * breathRange
|
||||
}
|
||||
|
||||
private fun getPulsate(baseLum: Cvec): Cvec {
|
||||
val funcY = FastMath.sin(FastMath.PI * pulsateFuncX / pulsateCycleDuration) * pulsateRange
|
||||
|
||||
return alterBrightnessUniform(baseLum, funcY)
|
||||
}
|
||||
|
||||
private fun getPulsate(baseLum: Float): Float {
|
||||
return baseLum + FastMath.sin(FastMath.PI * pulsateFuncX / pulsateCycleDuration) * pulsateRange
|
||||
}
|
||||
|
||||
/**
|
||||
* Using our own timer so that they flickers for same duration regardless of game's FPS
|
||||
*/
|
||||
@@ -94,6 +103,20 @@ object BlockPropUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chan 0 for R, 1 for G, 2 for B, 3 for A
|
||||
*/
|
||||
fun getDynamicLumFuncByChan(baseLum: Float, type: Int, chan: Int): Float {
|
||||
return when (type) {
|
||||
1 -> getTorchFlicker(baseLum)
|
||||
2 -> (Terrarum.ingame!!.world).globalLight.cpy().mul(LightmapRenderer.DIV_FLOAT).getElem(chan) // current global light
|
||||
3 -> WeatherMixer.getGlobalLightOfTime(Terrarum.ingame!!.world, WorldTime.DAY_LENGTH / 2).cpy().mul(LightmapRenderer.DIV_FLOAT).getElem(chan) // daylight at noon
|
||||
4 -> getSlowBreath(baseLum)
|
||||
5 -> getPulsate(baseLum)
|
||||
else -> baseLum
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Darken or brighten colour by 'brighten' argument
|
||||
*
|
||||
|
||||
@@ -4,6 +4,9 @@ import com.badlogic.gdx.utils.Disposable
|
||||
import net.torvald.UnsafeHelper
|
||||
|
||||
/**
|
||||
* As the fast access to this LUT is critical for the performance because of the way light calculation work,
|
||||
* even the IO can be a bottleneck so I use UnsafePointer.
|
||||
*
|
||||
* Created by Torvald on 2019-12-05.
|
||||
*/
|
||||
internal object LightmapHDRMap : Disposable {
|
||||
|
||||
@@ -20,7 +20,10 @@ import net.torvald.terrarum.gameactors.Luminous
|
||||
import net.torvald.terrarum.gameworld.BlockAddress
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarum.modulebasegame.IngameRenderer
|
||||
import net.torvald.terrarum.modulebasegame.ui.abs
|
||||
import net.torvald.terrarum.realestate.LandUtil
|
||||
import net.torvald.terrarum.worlddrawer.LightmapRenderer.convX
|
||||
import net.torvald.terrarum.worlddrawer.LightmapRenderer.convY
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
@@ -226,6 +229,38 @@ object LightmapRenderer {
|
||||
// when disabled, light will "decay out" instead of "instantly out", which can have a cool effect
|
||||
// but the performance boost is measly 0.1 ms on 6700K
|
||||
lightmap.zerofill()
|
||||
|
||||
|
||||
// pre-seed the lightmap with known value
|
||||
for (x in for_x_start - overscan_open + 1..for_x_end + overscan_open - 1) {
|
||||
for (y in for_y_start - overscan_open + 1..for_y_end + overscan_open - 1) {
|
||||
val tile = world.getTileFromTerrain(x, y)
|
||||
val wall = world.getTileFromWall(x, y)
|
||||
|
||||
val lightlevel = if (!BlockCodex[tile].isSolid && !BlockCodex[wall].isSolid)
|
||||
sunLight.cpy()
|
||||
else
|
||||
colourNull.cpy()
|
||||
// are you a light source?
|
||||
lightlevel.add(BlockCodex[tile].luminosity)
|
||||
|
||||
// TODO: add lanterns
|
||||
|
||||
// subtract attenuation value
|
||||
if (!lightlevel.nonZero()) {
|
||||
lightlevel.sub(
|
||||
BlockCodex[tile].opacity
|
||||
)
|
||||
}
|
||||
|
||||
val lx = x.convX(); val ly = y.convY()
|
||||
|
||||
lightmap.setR(lx, ly, lightlevel.r)
|
||||
lightmap.setG(lx, ly, lightlevel.g)
|
||||
lightmap.setB(lx, ly, lightlevel.b)
|
||||
lightmap.setA(lx, ly, lightlevel.a)
|
||||
}
|
||||
}
|
||||
}
|
||||
// O((5*9)n) == O(n) where n is a size of the map.
|
||||
// Because of inevitable overlaps on the area, it only works with MAX blend
|
||||
@@ -240,7 +275,7 @@ object LightmapRenderer {
|
||||
|
||||
AppLoader.measureDebugTime("Renderer.LightTotal") {
|
||||
// Round 2
|
||||
for (y in for_y_end + overscan_open downTo for_y_start) {
|
||||
/*for (y in for_y_end + overscan_open downTo for_y_start) {
|
||||
// TODO multithread the following for loop duh
|
||||
for (x in for_x_start - overscan_open..for_x_end) {
|
||||
calculateAndAssign(lightmap, x, y)
|
||||
@@ -266,6 +301,69 @@ object LightmapRenderer {
|
||||
for (x in for_x_start - overscan_open..for_x_end) {
|
||||
calculateAndAssign(lightmap, x, y)
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
// per-channel operation for bit more aggressive optimisation
|
||||
repeat(4) { rgbaOffset ->
|
||||
val visitedCells = setOf(-1L) // (x shl 32) or y
|
||||
|
||||
for (x in for_x_start - overscan_open + 1..for_x_end + overscan_open - 1) {
|
||||
for (y in for_y_start - overscan_open + 1..for_y_end + overscan_open - 1) {
|
||||
val lx = x.convX()
|
||||
val ly = y.convY()
|
||||
|
||||
val hash = (x.toLong() shl 32) or y.toLong()
|
||||
if (visitedCells.contains(hash)) continue
|
||||
|
||||
var thisLightValue = lightmap.channelGet(lx, ly, rgbaOffset)
|
||||
|
||||
|
||||
// nearby tiles, namely a b c d e f g h
|
||||
val tilea = world.getTileFromTerrain(x - 1, y - 1)
|
||||
val tileb = world.getTileFromTerrain(x, y - 1)
|
||||
val tilec = world.getTileFromTerrain(x + 1, y - 1)
|
||||
val tiled = world.getTileFromTerrain(x - 1, y)
|
||||
val tilee = world.getTileFromTerrain(x + 1, y)
|
||||
val tilef = world.getTileFromTerrain(x - 1, y + 1)
|
||||
val tileg = world.getTileFromTerrain(x, y + 1)
|
||||
val tileh = world.getTileFromTerrain(x + 1, y + 1)
|
||||
|
||||
// max value from plus-shaped neighbouring tiles
|
||||
thisLightValue = maxOf(thisLightValue, BlockCodex[tileb].getLum(rgbaOffset))
|
||||
thisLightValue = maxOf(thisLightValue, BlockCodex[tiled].getLum(rgbaOffset))
|
||||
thisLightValue = maxOf(thisLightValue, BlockCodex[tilee].getLum(rgbaOffset))
|
||||
thisLightValue = maxOf(thisLightValue, BlockCodex[tileg].getLum(rgbaOffset))
|
||||
// max vaule from x-shaped neighbouring tiles
|
||||
thisLightValue = maxOf(thisLightValue, BlockCodex[tilea].getLum(rgbaOffset) * 0.70710678f)
|
||||
thisLightValue = maxOf(thisLightValue, BlockCodex[tilec].getLum(rgbaOffset) * 0.70710678f)
|
||||
thisLightValue = maxOf(thisLightValue, BlockCodex[tilef].getLum(rgbaOffset) * 0.70710678f)
|
||||
thisLightValue = maxOf(thisLightValue, BlockCodex[tileh].getLum(rgbaOffset) * 0.70710678f)
|
||||
|
||||
lightmap.channelSet(lx, ly, rgbaOffset, thisLightValue)
|
||||
|
||||
// recurse condition
|
||||
if (lightmap.channelGet(lx - 1, ly - 1, rgbaOffset) < thisLightValue)
|
||||
spread(x - 1, y - 1, rgbaOffset, visitedCells, 0)
|
||||
if (lightmap.channelGet(lx, ly - 1, rgbaOffset) < thisLightValue)
|
||||
spread(x, y - 1, rgbaOffset, visitedCells, 0)
|
||||
if (lightmap.channelGet(lx + 1, ly - 1, rgbaOffset) < thisLightValue)
|
||||
spread(x + 1, y - 1, rgbaOffset, visitedCells, 0)
|
||||
|
||||
if (lightmap.channelGet(lx - 1, ly, rgbaOffset) < thisLightValue)
|
||||
spread(x - 1, y, rgbaOffset, visitedCells, 0)
|
||||
if (lightmap.channelGet(lx + 1, ly, rgbaOffset) < thisLightValue)
|
||||
spread(x + 1, y, rgbaOffset, visitedCells, 0)
|
||||
|
||||
if (lightmap.channelGet(lx - 1, ly + 1, rgbaOffset) < thisLightValue)
|
||||
spread(x - 1, y + 1, rgbaOffset, visitedCells, 0)
|
||||
if (lightmap.channelGet(lx, ly + 1, rgbaOffset) < thisLightValue)
|
||||
spread(x, y + 1, rgbaOffset, visitedCells, 0)
|
||||
if (lightmap.channelGet(lx + 1, ly + 1, rgbaOffset) < thisLightValue)
|
||||
spread(x + 1, y + 1, rgbaOffset, visitedCells, 0)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,6 +409,69 @@ object LightmapRenderer {
|
||||
// TODO re-init at every resize
|
||||
private lateinit var updateMessages: List<Array<ThreadedLightmapUpdateMessage>>
|
||||
|
||||
/**
|
||||
* @param x x position in the world
|
||||
* @param y y position in the world
|
||||
*/
|
||||
private fun spread(x: Int, y: Int, rgbaOffset: Int, visitedCells: Set<Long>, recurseCount: Int) {
|
||||
val lx = x.convX()
|
||||
val ly = y.convY()
|
||||
|
||||
val hash = (x.toLong() shl 32) or y.toLong()
|
||||
|
||||
// stop condition
|
||||
if (visitedCells.contains(hash)) return
|
||||
if (x !in for_x_start - overscan_open + 1..for_x_end + overscan_open - 1) return
|
||||
if (y !in for_y_start - overscan_open + 1..for_y_end + overscan_open - 1) return
|
||||
if (recurseCount > 128) return
|
||||
|
||||
var thisLightValue = lightmap.channelGet(lx, ly, rgbaOffset)
|
||||
|
||||
// nearby tiles, namely a b c d e f g h
|
||||
val tilea = world.getTileFromTerrain(x - 1, y - 1)
|
||||
val tileb = world.getTileFromTerrain(x, y - 1)
|
||||
val tilec = world.getTileFromTerrain(x + 1, y - 1)
|
||||
val tiled = world.getTileFromTerrain(x - 1, y)
|
||||
val tilee = world.getTileFromTerrain(x + 1, y)
|
||||
val tilef = world.getTileFromTerrain(x - 1, y + 1)
|
||||
val tileg = world.getTileFromTerrain(x, y + 1)
|
||||
val tileh = world.getTileFromTerrain(x + 1, y + 1)
|
||||
|
||||
// max value from plus-shaped neighbouring tiles
|
||||
thisLightValue = maxOf(thisLightValue, BlockCodex[tileb].getLum(rgbaOffset))
|
||||
thisLightValue = maxOf(thisLightValue, BlockCodex[tiled].getLum(rgbaOffset))
|
||||
thisLightValue = maxOf(thisLightValue, BlockCodex[tilee].getLum(rgbaOffset))
|
||||
thisLightValue = maxOf(thisLightValue, BlockCodex[tileg].getLum(rgbaOffset))
|
||||
// max vaule from x-shaped neighbouring tiles
|
||||
thisLightValue = maxOf(thisLightValue, BlockCodex[tilea].getLum(rgbaOffset) * 0.70710678f)
|
||||
thisLightValue = maxOf(thisLightValue, BlockCodex[tilec].getLum(rgbaOffset) * 0.70710678f)
|
||||
thisLightValue = maxOf(thisLightValue, BlockCodex[tilef].getLum(rgbaOffset) * 0.70710678f)
|
||||
thisLightValue = maxOf(thisLightValue, BlockCodex[tileh].getLum(rgbaOffset) * 0.70710678f)
|
||||
|
||||
lightmap.channelSet(lx, ly, rgbaOffset, thisLightValue)
|
||||
|
||||
// recurse condition
|
||||
if (lightmap.channelGet(lx - 1, ly - 1, rgbaOffset) < thisLightValue)
|
||||
spread(x - 1, y - 1, rgbaOffset, visitedCells, recurseCount + 1)
|
||||
if (lightmap.channelGet(lx, ly - 1, rgbaOffset) < thisLightValue)
|
||||
spread(x, y - 1, rgbaOffset, visitedCells, recurseCount + 1)
|
||||
if (lightmap.channelGet(lx + 1, ly - 1, rgbaOffset) < thisLightValue)
|
||||
spread(x + 1, y - 1, rgbaOffset, visitedCells, recurseCount + 1)
|
||||
|
||||
if (lightmap.channelGet(lx - 1, ly, rgbaOffset) < thisLightValue)
|
||||
spread(x - 1, y, rgbaOffset, visitedCells, recurseCount + 1)
|
||||
if (lightmap.channelGet(lx + 1, ly, rgbaOffset) < thisLightValue)
|
||||
spread(x + 1, y, rgbaOffset, visitedCells, recurseCount + 1)
|
||||
|
||||
if (lightmap.channelGet(lx - 1, ly + 1, rgbaOffset) < thisLightValue)
|
||||
spread(x - 1, y + 1, rgbaOffset, visitedCells, recurseCount + 1)
|
||||
if (lightmap.channelGet(lx, ly + 1, rgbaOffset) < thisLightValue)
|
||||
spread(x, y + 1, rgbaOffset, visitedCells, recurseCount + 1)
|
||||
if (lightmap.channelGet(lx + 1, ly + 1, rgbaOffset) < thisLightValue)
|
||||
spread(x + 1, y + 1, rgbaOffset, visitedCells, recurseCount + 1)
|
||||
|
||||
}
|
||||
|
||||
private fun makeUpdateTaskList() {
|
||||
val lightTaskArr = ArrayList<ThreadedLightmapUpdateMessage>()
|
||||
|
||||
@@ -786,7 +947,10 @@ object LightmapRenderer {
|
||||
hdr(this.a.coerceIn(0f, 1f))
|
||||
)
|
||||
|
||||
private fun Cvec.nonZero() = this.r + this.g + this.b + this.a > epsilon
|
||||
private fun Cvec.nonZero() = this.r.abs() > epsilon &&
|
||||
this.g.abs() > epsilon &&
|
||||
this.b.abs() > epsilon &&
|
||||
this.a.abs() > epsilon
|
||||
|
||||
val histogram: Histogram
|
||||
get() {
|
||||
|
||||
Reference in New Issue
Block a user