|
|
|
|
@@ -14,7 +14,6 @@ import net.torvald.terrarum.concurrent.ParallelUtils.sliceEvenly
|
|
|
|
|
import net.torvald.terrarum.gameactors.ActorWBMovable
|
|
|
|
|
import net.torvald.terrarum.gameactors.ActorWithBody
|
|
|
|
|
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.realestate.LandUtil
|
|
|
|
|
@@ -34,6 +33,7 @@ import net.torvald.terrarum.realestate.LandUtil
|
|
|
|
|
*/
|
|
|
|
|
object LightmapRenderer {
|
|
|
|
|
private const val TILE_SIZE = CreateTileAtlas.TILE_SIZE
|
|
|
|
|
private const val SQRT2 = 1.41421356f
|
|
|
|
|
|
|
|
|
|
private var world: GameWorld = GameWorld.makeNullWorld()
|
|
|
|
|
private lateinit var lightCalcShader: ShaderProgram
|
|
|
|
|
@@ -83,7 +83,51 @@ object LightmapRenderer {
|
|
|
|
|
// it utilises alpha channel to determine brightness of "glow" sprites (so that alpha channel works like UV light)
|
|
|
|
|
//private val lightmap: Array<Array<Color>> = Array(LIGHTMAP_HEIGHT) { Array(LIGHTMAP_WIDTH, { Color(0f,0f,0f,0f) }) } // Can't use framebuffer/pixmap -- this is a fvec4 array, whereas they are ivec4.
|
|
|
|
|
private val lightmap: Array<Color> = Array(LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT) { Color(0f,0f,0f,0f) } // Can't use framebuffer/pixmap -- this is a fvec4 array, whereas they are ivec4.
|
|
|
|
|
private val lanternMap = HashMap<BlockAddress, Color>((Terrarum.ingame?.ACTORCONTAINER_INITIAL_SIZE ?: 2) * 4)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sstores both the block light sources and actor light sources.
|
|
|
|
|
*/
|
|
|
|
|
//private val lightSourcesMap = HashMap<BlockAddress, Color>((Terrarum.ingame?.ACTORCONTAINER_INITIAL_SIZE ?: 2) * 4)
|
|
|
|
|
private val lightSourcesMap = Array<Color>(LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT) { Color(0) }
|
|
|
|
|
private fun toLightOffset(y: Int, x: Int): Int {
|
|
|
|
|
val xpos = x - for_x_start + overscan_open
|
|
|
|
|
val ypos = y - for_y_start + overscan_open
|
|
|
|
|
val index = ypos * LIGHTMAP_WIDTH + xpos
|
|
|
|
|
|
|
|
|
|
if (index >= lightSourcesMap.size)
|
|
|
|
|
println("$x, $y | $xpos, $ypos | $for_x_start, $for_y_start | $index")
|
|
|
|
|
|
|
|
|
|
return index
|
|
|
|
|
}
|
|
|
|
|
private fun getLightSourceMap(worldX: Int, worldY: Int) =
|
|
|
|
|
lightSourcesMap[toLightOffset(worldX, worldY)]
|
|
|
|
|
private fun setLightSourcesMap(worldX: Int, worldY: Int, value: Color) {
|
|
|
|
|
lightSourcesMap[toLightOffset(worldX, worldY)] = value
|
|
|
|
|
}
|
|
|
|
|
private fun mixLightSourcesMap(worldX: Int, worldY: Int, value: Color) {
|
|
|
|
|
lightSourcesMap[toLightOffset(worldX, worldY)].maxAndAssign(value)
|
|
|
|
|
}
|
|
|
|
|
private fun clearLightSourcesOffset() {
|
|
|
|
|
for (i in 0 until lightSourcesMap.size) { lightSourcesMap[i] = Color(0) }
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Pair of: Regular shade, the former shade times 1.4142
|
|
|
|
|
*/
|
|
|
|
|
//private val shadesMap = HashMap<BlockAddress, Pair<Color, Color>>((Terrarum.ingame?.ACTORCONTAINER_INITIAL_SIZE ?: 2) * 4)
|
|
|
|
|
private val shadesMap = Array<Pair<Color, Color>>(LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT) { Color(0) to Color(0) }
|
|
|
|
|
private fun getShadesMap(worldX: Int, worldY: Int) =
|
|
|
|
|
shadesMap[toLightOffset(worldX, worldY)]
|
|
|
|
|
private fun setShadesMap(worldX: Int, worldY: Int, value: Color) {
|
|
|
|
|
shadesMap[toLightOffset(worldX, worldY)] = value to (value.cpy().mul(SQRT2))
|
|
|
|
|
}
|
|
|
|
|
private fun mixShadesMap(worldX: Int, worldY: Int, value: Color) {
|
|
|
|
|
val field = shadesMap[toLightOffset(worldX, worldY)]
|
|
|
|
|
val baseval = field.first.maxAndAssign(value)
|
|
|
|
|
field.second.set(baseval); field.second.mul(SQRT2)
|
|
|
|
|
}
|
|
|
|
|
private fun clearShadesMap() {
|
|
|
|
|
for (i in 0 until shadesMap.size) { shadesMap[i] = Color(0) to Color(0) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
init {
|
|
|
|
|
printdbg(this, "Overscan open: $overscan_open; opaque: $overscan_opaque")
|
|
|
|
|
@@ -201,8 +245,15 @@ object LightmapRenderer {
|
|
|
|
|
|
|
|
|
|
//println("$for_x_start..$for_x_end, $for_x\t$for_y_start..$for_y_end, $for_y")
|
|
|
|
|
|
|
|
|
|
AppLoader.measureDebugTime("Renderer.Lanterns") {
|
|
|
|
|
buildLanternmap(actorContainers)
|
|
|
|
|
// set sunlight
|
|
|
|
|
sunLight.set(world.globalLight); sunLight.mul(DIV_FLOAT)
|
|
|
|
|
|
|
|
|
|
AppLoader.measureDebugTime("Renderer.LightPreload") {
|
|
|
|
|
// this is to recycle pre-calculated lights and shades for all 4 rounds.
|
|
|
|
|
// the old code always re-calculates them (calls 'getLightsAndShades()') for every blocks for every round.
|
|
|
|
|
// the light source information can also be used to create no-op mask? I'm sceptical about that, there must
|
|
|
|
|
// exist some edge cases like the other time...
|
|
|
|
|
buildLightSourcesMap(actorContainers)
|
|
|
|
|
} // usually takes 3000 ns
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
@@ -219,9 +270,6 @@ object LightmapRenderer {
|
|
|
|
|
* If you run only 4 sets, orthogonal/diagonal artefacts are bound to occur,
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// set sunlight
|
|
|
|
|
sunLight.set(world.globalLight); sunLight.mul(DIV_FLOAT)
|
|
|
|
|
|
|
|
|
|
// set no-op mask from solidity of the block
|
|
|
|
|
AppLoader.measureDebugTime("Renderer.LightNoOpMask") {
|
|
|
|
|
noopMask.clear()
|
|
|
|
|
@@ -245,6 +293,7 @@ object LightmapRenderer {
|
|
|
|
|
// The skipping is dependent on how you get ambient light,
|
|
|
|
|
// in this case we have 'spillage' due to the fact calculate() samples 3x3 area.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AppLoader.measureDebugTime("Renderer.LightTotal") {
|
|
|
|
|
// Round 2
|
|
|
|
|
for (y in for_y_end + overscan_open downTo for_y_start) {
|
|
|
|
|
@@ -331,8 +380,22 @@ object LightmapRenderer {
|
|
|
|
|
internal data class ThreadedLightmapUpdateMessage(val x: Int, val y: Int)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun buildLanternmap(actorContainers: Array<out List<ActorWithBody>?>) {
|
|
|
|
|
lanternMap.clear()
|
|
|
|
|
private var _block = 0
|
|
|
|
|
private var _blockProp = BlockCodex[0]
|
|
|
|
|
private var _wall = 0
|
|
|
|
|
private var _blockLum = Color(0)
|
|
|
|
|
private var _fluid = GameWorld.FluidInfo(Fluid.NULL, 0f)
|
|
|
|
|
private var _fluidProp = BlockCodex[_fluid.type]
|
|
|
|
|
private var _tileAddr = 0L
|
|
|
|
|
private var _fluidAmountToCol = Color(0)
|
|
|
|
|
|
|
|
|
|
private fun buildLightSourcesMap(actorContainers: Array<out List<ActorWithBody>?>) {
|
|
|
|
|
clearLightSourcesOffset()
|
|
|
|
|
clearShadesMap()
|
|
|
|
|
|
|
|
|
|
//lightSourcesMapOffset.set(for_x_start - overscan_open, for_y_start + overscan_open)
|
|
|
|
|
|
|
|
|
|
// lanterns from actors
|
|
|
|
|
actorContainers.forEach { actorContainer ->
|
|
|
|
|
actorContainer?.forEach {
|
|
|
|
|
if (it is Luminous && it is ActorWBMovable) {
|
|
|
|
|
@@ -349,17 +412,55 @@ object LightmapRenderer {
|
|
|
|
|
|
|
|
|
|
val normalisedColor = it.color//.cpy().mul(DIV_FLOAT)
|
|
|
|
|
|
|
|
|
|
lanternMap[LandUtil.getBlockAddr(world, x, y)] = normalisedColor
|
|
|
|
|
//lanternMap[Point2i(x, y)] = normalisedColor
|
|
|
|
|
//lightSourcesMap[LandUtil.getBlockAddr(world, x, y)] = normalisedColor
|
|
|
|
|
setLightSourcesMap(x, y, normalisedColor)
|
|
|
|
|
|
|
|
|
|
//lightSourcesMap[Point2i(x, y)] = normalisedColor
|
|
|
|
|
// Q&D fix for Roundworld anomaly
|
|
|
|
|
//lanternMap[Point2i(x + world.width, y)] = normalisedColor
|
|
|
|
|
//lanternMap[Point2i(x - world.width, y)] = normalisedColor
|
|
|
|
|
//lightSourcesMap[Point2i(x + world.width, y)] = normalisedColor
|
|
|
|
|
//lightSourcesMap[Point2i(x - world.width, y)] = normalisedColor
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// light sources and shades from a block
|
|
|
|
|
for (y in for_y_start - overscan_open..for_y_end + overscan_open) {
|
|
|
|
|
for (x in for_x_start - overscan_open..for_x_end + overscan_open) {
|
|
|
|
|
_block = world.getTileFromTerrain(x, y) ?: Block.STONE
|
|
|
|
|
_blockProp = BlockCodex[_block]
|
|
|
|
|
_wall = world.getTileFromWall(x, y) ?: Block.STONE
|
|
|
|
|
_blockLum = _blockProp.luminosity
|
|
|
|
|
_fluid = world.getFluid(x, y)
|
|
|
|
|
_fluidProp = BlockCodex[_fluid.type]
|
|
|
|
|
_tileAddr = LandUtil.getBlockAddr(world, x, y)
|
|
|
|
|
_fluidAmountToCol = Color(_fluid.amount.coerceIn(0f, 1f))
|
|
|
|
|
|
|
|
|
|
// light sources from blocks //
|
|
|
|
|
|
|
|
|
|
// mix with the existing value
|
|
|
|
|
mixLightSourcesMap(x, y, _blockLum)
|
|
|
|
|
|
|
|
|
|
// see if sunlight is applicable. If it does, mix with the existing value
|
|
|
|
|
if ((_block == AIR && _wall == AIR) || (_blockLum.nonZero() && _wall == AIR)) {
|
|
|
|
|
mixLightSourcesMap(x, y, sunLight)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// mix the lava light
|
|
|
|
|
if (_fluid.type != Fluid.NULL) {
|
|
|
|
|
mixLightSourcesMap(x, y, _fluidProp.luminosity mul _fluidAmountToCol)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// deal with the shades //
|
|
|
|
|
|
|
|
|
|
// shade from the block
|
|
|
|
|
setShadesMap(x, y, _blockProp.opacity)
|
|
|
|
|
// shade from the fluid
|
|
|
|
|
mixShadesMap(x, y, _fluidProp.opacity mul _fluidAmountToCol)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun buildNoopMask() {
|
|
|
|
|
@@ -394,14 +495,14 @@ object LightmapRenderer {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//private val ambientAccumulator = Color(0f,0f,0f,0f)
|
|
|
|
|
private val lightLevelThis = Color(0)
|
|
|
|
|
private var thisTerrain = 0
|
|
|
|
|
private var thisFluid = GameWorld.FluidInfo(Fluid.NULL, 0f)
|
|
|
|
|
private val fluidAmountToCol = Color(0)
|
|
|
|
|
private var thisWall = 0
|
|
|
|
|
private val thisTileLuminosity = Color(0)
|
|
|
|
|
private val thisTileOpacity = Color(0)
|
|
|
|
|
private val thisTileOpacity2 = Color(0) // thisTileOpacity * sqrt(2)
|
|
|
|
|
//private val lightLevelThis = Color(0)
|
|
|
|
|
//private var thisTerrain = 0
|
|
|
|
|
//private var thisFluid = GameWorld.FluidInfo(Fluid.NULL, 0f)
|
|
|
|
|
//private val fluidAmountToCol = Color(0)
|
|
|
|
|
//private var thisWall = 0
|
|
|
|
|
//private val thisTileLuminosity = Color(0)
|
|
|
|
|
//private val thisTileOpacity = Color(0)
|
|
|
|
|
//private val thisTileOpacity2 = Color(0) // thisTileOpacity * sqrt(2)
|
|
|
|
|
private val sunLight = Color(0)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@@ -415,7 +516,10 @@ object LightmapRenderer {
|
|
|
|
|
* - thisTileOpacity2
|
|
|
|
|
* - sunlight
|
|
|
|
|
*/
|
|
|
|
|
private fun getLightsAndShades(x: Int, y: Int) {
|
|
|
|
|
/*private fun getLightsAndShades(x: Int, y: Int) {
|
|
|
|
|
// TODO lanternmap now also holds light sources (incl. sunlight)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lightLevelThis.set(colourNull)
|
|
|
|
|
thisTerrain = world.getTileFromTerrain(x, y) ?: Block.STONE
|
|
|
|
|
thisFluid = world.getFluid(x, y)
|
|
|
|
|
@@ -444,9 +548,9 @@ object LightmapRenderer {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// blend lantern
|
|
|
|
|
lightLevelThis.maxAndAssign(thisTileLuminosity).maxAndAssign(lanternMap[LandUtil.getBlockAddr(world, x, y)] ?: colourNull)
|
|
|
|
|
lightLevelThis.maxAndAssign(thisTileLuminosity).maxAndAssign(lightSourcesMap[LandUtil.getBlockAddr(world, x, y)] ?: colourNull)
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}*/
|
|
|
|
|
|
|
|
|
|
private val inNoopMaskp = Point2i(0,0)
|
|
|
|
|
|
|
|
|
|
@@ -505,7 +609,9 @@ object LightmapRenderer {
|
|
|
|
|
|
|
|
|
|
// O(9n) == O(n) where n is a size of the map
|
|
|
|
|
|
|
|
|
|
getLightsAndShades(x, y)
|
|
|
|
|
//getLightsAndShades(x, y)
|
|
|
|
|
val lightLevelThis = getLightSourceMap(x, y) // it HAS to be a cpy()...?, otherwise all cells gets the same instance
|
|
|
|
|
val (thisTileOpacity, thisTileOpacity2) = getShadesMap(x, y)
|
|
|
|
|
|
|
|
|
|
// calculate ambient
|
|
|
|
|
/* + * + 0 4 1
|
|
|
|
|
@@ -516,6 +622,7 @@ object LightmapRenderer {
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// will "overwrite" what's there in the lightmap if it's the first pass
|
|
|
|
|
// using "map"s actually makes it slower. Guess I'll keep it dirty...
|
|
|
|
|
// takes about 2 ms on 6700K
|
|
|
|
|
/* + */lightLevelThis.maxAndAssign(darkenColoured(getLightInternal(x - 1, y - 1) ?: colourNull, thisTileOpacity2))
|
|
|
|
|
/* + */lightLevelThis.maxAndAssign(darkenColoured(getLightInternal(x + 1, y - 1) ?: colourNull, thisTileOpacity2))
|
|
|
|
|
@@ -528,7 +635,7 @@ object LightmapRenderer {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//return lightLevelThis.cpy() // it HAS to be a cpy(), otherwise all cells gets the same instance
|
|
|
|
|
setLightOf(lightmap, x, y, lightLevelThis.cpy())
|
|
|
|
|
setLightOf(lightmap, x, y, lightLevelThis)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun getLightForOpaque(x: Int, y: Int): Color? { // ...so that they wouldn't appear too dark
|
|
|
|
|
@@ -887,4 +994,4 @@ object LightmapRenderer {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun Color.toRGBA() = (255 * r).toInt() shl 24 or ((255 * g).toInt() shl 16) or ((255 * b).toInt() shl 8) or (255 * a).toInt()
|
|
|
|
|
|
|
|
|
|
//fun Color(c: Float) = Color(c, c, c, c)
|
|
|
|
|
|