mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-10 10:34:06 +09:00
fixed a bug where wall item can be consumend indefinitely because checking for what's already there was not working
This commit is contained in:
@@ -50,7 +50,7 @@ object BlockBase {
|
|||||||
if (gameItem.inventoryCategory == GameItem.Category.BLOCK &&
|
if (gameItem.inventoryCategory == GameItem.Category.BLOCK &&
|
||||||
gameItem.dynamicID == ingame.world.getTileFromTerrain(mouseTile.x, mouseTile.y) ||
|
gameItem.dynamicID == ingame.world.getTileFromTerrain(mouseTile.x, mouseTile.y) ||
|
||||||
gameItem.inventoryCategory == GameItem.Category.WALL &&
|
gameItem.inventoryCategory == GameItem.Category.WALL &&
|
||||||
gameItem.dynamicID == ingame.world.getTileFromWall(mouseTile.x, mouseTile.y)
|
gameItem.dynamicID == "wall@"+ingame.world.getTileFromWall(mouseTile.x, mouseTile.y)
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|||||||
@@ -378,7 +378,7 @@ class UIItemInventoryItemGrid(
|
|||||||
if (isCompactMode && it.item != null && it.mouseUp && !tooltipSet) {
|
if (isCompactMode && it.item != null && it.mouseUp && !tooltipSet) {
|
||||||
(Terrarum.ingame as? TerrarumIngame)?.setTooltipMessage(
|
(Terrarum.ingame as? TerrarumIngame)?.setTooltipMessage(
|
||||||
if (INVEN_DEBUG_MODE) {
|
if (INVEN_DEBUG_MODE) {
|
||||||
it.item?.name + "/Mat: ${it.item?.material?.identifier}"
|
it.item?.name + " (${it.item?.originalID}${if (it.item?.originalID == it.item?.dynamicID) "" else "/${it.item?.dynamicID}"})"
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
it.item?.name
|
it.item?.name
|
||||||
|
|||||||
@@ -589,7 +589,7 @@ internal object BlocksDrawer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var _tilesBufferAsTex: Texture = Texture(1, 1, Pixmap.Format.RGBA8888)
|
private var _tilesBufferAsTex: Texture = Texture(1, 1, Pixmap.Format.RGBA8888)
|
||||||
private val occlusionIntensity = 0.3f
|
private val occlusionIntensity = 0.35f // too low value and dark-coloured walls won't darken enough
|
||||||
|
|
||||||
private fun renderUsingBuffer(mode: Int, projectionMatrix: Matrix4, drawGlow: Boolean) {
|
private fun renderUsingBuffer(mode: Int, projectionMatrix: Matrix4, drawGlow: Boolean) {
|
||||||
//Gdx.gl.glClearColor(.094f, .094f, .094f, 0f)
|
//Gdx.gl.glClearColor(.094f, .094f, .094f, 0f)
|
||||||
@@ -1,724 +0,0 @@
|
|||||||
/*package net.torvald.terrarum.worlddrawer
|
|
||||||
|
|
||||||
import com.badlogic.gdx.Gdx
|
|
||||||
import Color
|
|
||||||
import com.badlogic.gdx.graphics.Pixmap
|
|
||||||
import com.badlogic.gdx.graphics.Texture
|
|
||||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
|
||||||
import net.torvald.terrarum.*
|
|
||||||
import net.torvald.terrarum.blockproperties.Block
|
|
||||||
import net.torvald.terrarum.blockproperties.BlockCodex
|
|
||||||
import net.torvald.terrarum.gameworld.GameWorld
|
|
||||||
import net.torvald.terrarum.gameworld.PairedMapLayer
|
|
||||||
import net.torvald.terrarum.itemproperties.ItemCodex.ITEM_TILES
|
|
||||||
import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack
|
|
||||||
import java.io.BufferedOutputStream
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.util.zip.GZIPInputStream
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by minjaesong on 2016-01-19.
|
|
||||||
*/
|
|
||||||
object BlocksDrawerOLD {
|
|
||||||
lateinit var world: GameWorld
|
|
||||||
|
|
||||||
|
|
||||||
private val TILE_SIZE = TILE_SIZE
|
|
||||||
private val TILE_SIZEF = TILE_SIZEF
|
|
||||||
|
|
||||||
// TODO modular
|
|
||||||
//val tilesTerrain = SpriteSheet(ModMgr.getPath("basegame", "blocks/terrain.tga.gz"), TILE_SIZE, TILE_SIZE) // 64 MB
|
|
||||||
//val tilesWire = SpriteSheet(ModMgr.getPath("basegame", "blocks/wire.tga.gz"), TILE_SIZE, TILE_SIZE) // 4 MB
|
|
||||||
|
|
||||||
val tilesTerrain: TextureRegionPack
|
|
||||||
val tilesWire: TextureRegionPack
|
|
||||||
val tileItemWall: TextureRegionPack
|
|
||||||
|
|
||||||
//val tileItemWall = Image(TILE_SIZE * 16, TILE_SIZE * GameWorld.TILES_SUPPORTED / 16) // 4 MB
|
|
||||||
|
|
||||||
|
|
||||||
val wallOverlayColour = Color(2f/3f, 2f/3f, 2f/3f, 1f)
|
|
||||||
|
|
||||||
val breakAnimSteps = 10
|
|
||||||
|
|
||||||
val WALL = GameWorld.WALL
|
|
||||||
val TERRAIN = GameWorld.TERRAIN
|
|
||||||
val WIRE = GameWorld.WIRE
|
|
||||||
|
|
||||||
private val NEARBY_TILE_KEY_UP = 0
|
|
||||||
private val NEARBY_TILE_KEY_RIGHT = 1
|
|
||||||
private val NEARBY_TILE_KEY_DOWN = 2
|
|
||||||
private val NEARBY_TILE_KEY_LEFT = 3
|
|
||||||
|
|
||||||
private val NEARBY_TILE_CODE_UP = 1
|
|
||||||
private val NEARBY_TILE_CODE_RIGHT = 2
|
|
||||||
private val NEARBY_TILE_CODE_DOWN = 4
|
|
||||||
private val NEARBY_TILE_CODE_LEFT = 8
|
|
||||||
|
|
||||||
|
|
||||||
private val GZIP_READBUF_SIZE = 8192
|
|
||||||
|
|
||||||
init {
|
|
||||||
// hard-coded as tga.gz
|
|
||||||
val gzFileList = listOf("blocks/terrain.tga.gz", "blocks/wire.tga.gz")
|
|
||||||
val gzTmpFName = listOf("tmp_terrain.tga", "tmp_wire.tga")
|
|
||||||
// unzip GZIP temporarily
|
|
||||||
gzFileList.forEachIndexed { index, filename ->
|
|
||||||
val terrainTexFile = ModMgr.getGdxFile("basegame", filename)
|
|
||||||
val gzi = GZIPInputStream(terrainTexFile.read(GZIP_READBUF_SIZE))
|
|
||||||
val wholeFile = gzi.readBytes()
|
|
||||||
gzi.close()
|
|
||||||
val fos = BufferedOutputStream(FileOutputStream(gzTmpFName[index]))
|
|
||||||
fos.write(wholeFile)
|
|
||||||
fos.flush()
|
|
||||||
fos.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
val terrainPixMap = Pixmap(Gdx.files.internal(gzTmpFName[0]))
|
|
||||||
val wirePixMap = Pixmap(Gdx.files.internal(gzTmpFName[1]))
|
|
||||||
|
|
||||||
// delete temp files
|
|
||||||
gzTmpFName.forEach { File(it).delete() }
|
|
||||||
|
|
||||||
tilesTerrain = TextureRegionPack(Texture(terrainPixMap), TILE_SIZE, TILE_SIZE)
|
|
||||||
tilesTerrain.texture.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
|
|
||||||
tilesWire = TextureRegionPack(Texture(wirePixMap), TILE_SIZE, TILE_SIZE)
|
|
||||||
tilesWire.texture.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
|
|
||||||
|
|
||||||
// also dispose unused temp files
|
|
||||||
//terrainPixMap.dispose() // commented: tileItemWall needs it
|
|
||||||
wirePixMap.dispose()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// create item_wall images
|
|
||||||
// --> make pixmap
|
|
||||||
val tileItemImgPixMap = Pixmap(TILE_SIZE * 16, TILE_SIZE * GameWorld.TILES_SUPPORTED / 16, Pixmap.Format.RGBA8888)
|
|
||||||
tileItemImgPixMap.pixels.rewind()
|
|
||||||
|
|
||||||
for (tileID in ITEM_TILES) {
|
|
||||||
|
|
||||||
val tile = tilesTerrain.get((tileID % 16) * 16, (tileID / 16))
|
|
||||||
|
|
||||||
// slow memory copy :\ I'm afraid I can't random-access bytebuffer...
|
|
||||||
for (y in 0..TILE_SIZE - 1) {
|
|
||||||
for (x in 0..TILE_SIZE - 1) {
|
|
||||||
tileItemImgPixMap.pixels.putInt(
|
|
||||||
terrainPixMap.getPixel(
|
|
||||||
tile.regionX + x,
|
|
||||||
tile.regionY + y
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tileItemImgPixMap.pixels.rewind()
|
|
||||||
// turn pixmap into texture
|
|
||||||
tileItemWall = TextureRegionPack(Texture(tileItemImgPixMap), TILE_SIZE, TILE_SIZE)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
tileItemImgPixMap.dispose()
|
|
||||||
terrainPixMap.dispose() // finally
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connectivity group 01 : artificial tiles
|
|
||||||
* It holds different shading rule to discriminate with group 02, index 0 is single tile.
|
|
||||||
* These are the tiles that only connects to itself, will not connect to colour variants
|
|
||||||
*/
|
|
||||||
private val TILES_CONNECT_SELF = hashSetOf(
|
|
||||||
Block.GLASS_CRUDE,
|
|
||||||
Block.GLASS_CLEAN,
|
|
||||||
Block.ILLUMINATOR_BLACK,
|
|
||||||
Block.ILLUMINATOR_BLUE,
|
|
||||||
Block.ILLUMINATOR_BROWN,
|
|
||||||
Block.ILLUMINATOR_CYAN,
|
|
||||||
Block.ILLUMINATOR_FUCHSIA,
|
|
||||||
Block.ILLUMINATOR_GREEN,
|
|
||||||
Block.ILLUMINATOR_GREEN_DARK,
|
|
||||||
Block.ILLUMINATOR_GREY_DARK,
|
|
||||||
Block.ILLUMINATOR_GREY_LIGHT,
|
|
||||||
Block.ILLUMINATOR_GREY_MED,
|
|
||||||
Block.ILLUMINATOR_ORANGE,
|
|
||||||
Block.ILLUMINATOR_PURPLE,
|
|
||||||
Block.ILLUMINATOR_RED,
|
|
||||||
Block.ILLUMINATOR_TAN,
|
|
||||||
Block.ILLUMINATOR_WHITE,
|
|
||||||
Block.ILLUMINATOR_YELLOW,
|
|
||||||
Block.ILLUMINATOR_BLACK_OFF,
|
|
||||||
Block.ILLUMINATOR_BLUE_OFF,
|
|
||||||
Block.ILLUMINATOR_BROWN_OFF,
|
|
||||||
Block.ILLUMINATOR_CYAN_OFF,
|
|
||||||
Block.ILLUMINATOR_FUCHSIA_OFF,
|
|
||||||
Block.ILLUMINATOR_GREEN_OFF,
|
|
||||||
Block.ILLUMINATOR_GREEN_DARK_OFF,
|
|
||||||
Block.ILLUMINATOR_GREY_DARK_OFF,
|
|
||||||
Block.ILLUMINATOR_GREY_LIGHT_OFF,
|
|
||||||
Block.ILLUMINATOR_GREY_MED_OFF,
|
|
||||||
Block.ILLUMINATOR_ORANGE_OFF,
|
|
||||||
Block.ILLUMINATOR_PURPLE_OFF,
|
|
||||||
Block.ILLUMINATOR_RED_OFF,
|
|
||||||
Block.ILLUMINATOR_TAN_OFF,
|
|
||||||
Block.ILLUMINATOR_WHITE_OFF,
|
|
||||||
Block.ILLUMINATOR_YELLOW,
|
|
||||||
Block.DAYLIGHT_CAPACITOR
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To interact with external modules
|
|
||||||
*/
|
|
||||||
@JvmStatic fun addConnectSelf(blockID: Int): Boolean {
|
|
||||||
return TILES_CONNECT_SELF.add(blockID)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connectivity group 02 : natural tiles
|
|
||||||
* It holds different shading rule to discriminate with group 01, index 0 is middle tile.
|
|
||||||
*/
|
|
||||||
private val TILES_CONNECT_MUTUAL = hashSetOf(
|
|
||||||
Block.STONE,
|
|
||||||
Block.STONE_QUARRIED,
|
|
||||||
Block.STONE_TILE_WHITE,
|
|
||||||
Block.STONE_BRICKS,
|
|
||||||
Block.DIRT,
|
|
||||||
Block.GRASS,
|
|
||||||
Block.GRASSWALL,
|
|
||||||
Block.PLANK_BIRCH,
|
|
||||||
Block.PLANK_BLOODROSE,
|
|
||||||
Block.PLANK_EBONY,
|
|
||||||
Block.PLANK_NORMAL,
|
|
||||||
Block.SAND,
|
|
||||||
Block.SAND_WHITE,
|
|
||||||
Block.SAND_RED,
|
|
||||||
Block.SAND_DESERT,
|
|
||||||
Block.SAND_BLACK,
|
|
||||||
Block.SAND_GREEN,
|
|
||||||
Block.GRAVEL,
|
|
||||||
Block.GRAVEL_GREY,
|
|
||||||
Block.SNOW,
|
|
||||||
Block.ICE_NATURAL,
|
|
||||||
Block.ICE_MAGICAL,
|
|
||||||
Block.ORE_COPPER,
|
|
||||||
Block.ORE_IRON,
|
|
||||||
Block.ORE_GOLD,
|
|
||||||
Block.ORE_SILVER,
|
|
||||||
Block.ORE_ILMENITE,
|
|
||||||
Block.ORE_AURICHALCUM,
|
|
||||||
|
|
||||||
Block.SANDSTONE,
|
|
||||||
Block.SANDSTONE_BLACK,
|
|
||||||
Block.SANDSTONE_DESERT,
|
|
||||||
Block.SANDSTONE_RED,
|
|
||||||
Block.SANDSTONE_WHITE,
|
|
||||||
Block.SANDSTONE_GREEN
|
|
||||||
|
|
||||||
/*Block.WATER,
|
|
||||||
Block.WATER_1,
|
|
||||||
Block.WATER_2,
|
|
||||||
Block.WATER_3,
|
|
||||||
Block.WATER_4,
|
|
||||||
Block.WATER_5,
|
|
||||||
Block.WATER_6,
|
|
||||||
Block.WATER_7,
|
|
||||||
Block.WATER_8,
|
|
||||||
Block.WATER_9,
|
|
||||||
Block.WATER_10,
|
|
||||||
Block.WATER_11,
|
|
||||||
Block.WATER_12,
|
|
||||||
Block.WATER_13,
|
|
||||||
Block.WATER_14,
|
|
||||||
Block.WATER_15,
|
|
||||||
Block.LAVA,
|
|
||||||
Block.LAVA_1,
|
|
||||||
Block.LAVA_2,
|
|
||||||
Block.LAVA_3,
|
|
||||||
Block.LAVA_4,
|
|
||||||
Block.LAVA_5,
|
|
||||||
Block.LAVA_6,
|
|
||||||
Block.LAVA_7,
|
|
||||||
Block.LAVA_8,
|
|
||||||
Block.LAVA_9,
|
|
||||||
Block.LAVA_10,
|
|
||||||
Block.LAVA_11,
|
|
||||||
Block.LAVA_12,
|
|
||||||
Block.LAVA_13,
|
|
||||||
Block.LAVA_14,
|
|
||||||
Block.LAVA_15*/
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To interact with external modules
|
|
||||||
*/
|
|
||||||
@JvmStatic fun addConnectMutual(blockID: Int): Boolean {
|
|
||||||
return TILES_CONNECT_MUTUAL.add(blockID)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Torches, levers, switches, ...
|
|
||||||
*/
|
|
||||||
private val TILES_WALL_STICKER = hashSetOf(
|
|
||||||
Block.TORCH,
|
|
||||||
Block.TORCH_FROST,
|
|
||||||
Block.TORCH_OFF,
|
|
||||||
Block.TORCH_FROST_OFF
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To interact with external modules
|
|
||||||
*/
|
|
||||||
@JvmStatic fun addWallSticker(blockID: Int): Boolean {
|
|
||||||
return TILES_WALL_STICKER.add(blockID)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* platforms, ...
|
|
||||||
*/
|
|
||||||
private val TILES_WALL_STICKER_CONNECT_SELF = hashSetOf(
|
|
||||||
Block.PLATFORM_BIRCH,
|
|
||||||
Block.PLATFORM_BLOODROSE,
|
|
||||||
Block.PLATFORM_EBONY,
|
|
||||||
Block.PLATFORM_STONE,
|
|
||||||
Block.PLATFORM_WOODEN
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To interact with external modules
|
|
||||||
*/
|
|
||||||
@JvmStatic fun addWallStickerConnectSelf(blockID: Int): Boolean {
|
|
||||||
return TILES_WALL_STICKER_CONNECT_SELF.add(blockID)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tiles that half-transparent and has hue
|
|
||||||
* will blend colour using colour multiplication
|
|
||||||
* i.e. red hues get lost if you dive into the water
|
|
||||||
*/
|
|
||||||
private val TILES_BLEND_MUL = hashSetOf(-1
|
|
||||||
/*Block.WATER,
|
|
||||||
Block.WATER_1,
|
|
||||||
Block.WATER_2,
|
|
||||||
Block.WATER_3,
|
|
||||||
Block.WATER_4,
|
|
||||||
Block.WATER_5,
|
|
||||||
Block.WATER_6,
|
|
||||||
Block.WATER_7,
|
|
||||||
Block.WATER_8,
|
|
||||||
Block.WATER_9,
|
|
||||||
Block.WATER_10,
|
|
||||||
Block.WATER_11,
|
|
||||||
Block.WATER_12,
|
|
||||||
Block.WATER_13,
|
|
||||||
Block.WATER_14,
|
|
||||||
Block.WATER_15,
|
|
||||||
Block.LAVA,
|
|
||||||
Block.LAVA_1,
|
|
||||||
Block.LAVA_2,
|
|
||||||
Block.LAVA_3,
|
|
||||||
Block.LAVA_4,
|
|
||||||
Block.LAVA_5,
|
|
||||||
Block.LAVA_6,
|
|
||||||
Block.LAVA_7,
|
|
||||||
Block.LAVA_8,
|
|
||||||
Block.LAVA_9,
|
|
||||||
Block.LAVA_10,
|
|
||||||
Block.LAVA_11,
|
|
||||||
Block.LAVA_12,
|
|
||||||
Block.LAVA_13,
|
|
||||||
Block.LAVA_14,
|
|
||||||
Block.LAVA_15*/
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To interact with external modules
|
|
||||||
*/
|
|
||||||
@JvmStatic fun addBlendMul(blockID: Int): Boolean {
|
|
||||||
return TILES_BLEND_MUL.add(blockID)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////
|
|
||||||
// NO draw lightmap using colour filter, actors must also be hidden behind the darkness
|
|
||||||
///////////////////////////////////////////
|
|
||||||
|
|
||||||
fun renderWall(batch: SpriteBatch) {
|
|
||||||
/**
|
|
||||||
* render to camera
|
|
||||||
*/
|
|
||||||
blendNormal()
|
|
||||||
|
|
||||||
drawTiles(batch, WALL, false, wallOverlayColour)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun renderTerrain(batch: SpriteBatch) {
|
|
||||||
/**
|
|
||||||
* render to camera
|
|
||||||
*/
|
|
||||||
blendNormal()
|
|
||||||
|
|
||||||
drawTiles(batch, TERRAIN, false, Color.WHITE) // regular tiles
|
|
||||||
}
|
|
||||||
|
|
||||||
fun renderFront(batch: SpriteBatch, drawWires: Boolean) {
|
|
||||||
/**
|
|
||||||
* render to camera
|
|
||||||
*/
|
|
||||||
blendMul()
|
|
||||||
|
|
||||||
drawTiles(batch, TERRAIN, true, Color.WHITE) // blendmul tiles
|
|
||||||
|
|
||||||
if (drawWires) {
|
|
||||||
drawTiles(batch, WIRE, false, Color.WHITE)
|
|
||||||
}
|
|
||||||
|
|
||||||
blendNormal()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val tileDrawLightThreshold = 2f / LightmapRenderer.MUL
|
|
||||||
|
|
||||||
private fun canIHazRender(mode: Int, x: Int, y: Int) =
|
|
||||||
(world.getTileFrom(mode, x, y) != 0) // not an air tile
|
|
||||||
&&
|
|
||||||
// for WALLs; else: ret true
|
|
||||||
if (mode == WALL) { // DRAW WHEN it is visible and 'is a lip'
|
|
||||||
( BlockCodex[world.getTileFromTerrain(x, y) ?: 0].isClear ||
|
|
||||||
!
|
|
||||||
((!BlockCodex[world.getTileFromTerrain(x, y - 1) ?: 0].isClear && !BlockCodex[world.getTileFromTerrain(x, y + 1) ?: 0].isClear)
|
|
||||||
&&
|
|
||||||
(!BlockCodex[world.getTileFromTerrain(x - 1, y) ?: 0].isClear && !BlockCodex[world.getTileFromTerrain(x + 1, y + 1) ?: 0].isClear)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
true
|
|
||||||
|
|
||||||
// end
|
|
||||||
|
|
||||||
private fun hasLightNearby(x: Int, y: Int) = ( // check if light level of nearby or this tile is illuminated
|
|
||||||
LightmapRenderer.getHighestRGB(x, y) ?: 0f >= tileDrawLightThreshold ||
|
|
||||||
LightmapRenderer.getHighestRGB(x - 1, y) ?: 0f >= tileDrawLightThreshold ||
|
|
||||||
LightmapRenderer.getHighestRGB(x + 1, y) ?: 0f >= tileDrawLightThreshold ||
|
|
||||||
LightmapRenderer.getHighestRGB(x, y - 1) ?: 0f >= tileDrawLightThreshold ||
|
|
||||||
LightmapRenderer.getHighestRGB(x, y + 1) ?: 0f >= tileDrawLightThreshold ||
|
|
||||||
LightmapRenderer.getHighestRGB(x - 1, y - 1) ?: 0f >= tileDrawLightThreshold ||
|
|
||||||
LightmapRenderer.getHighestRGB(x + 1, y + 1) ?: 0f >= tileDrawLightThreshold ||
|
|
||||||
LightmapRenderer.getHighestRGB(x + 1, y - 1) ?: 0f >= tileDrawLightThreshold ||
|
|
||||||
LightmapRenderer.getHighestRGB(x - 1, y + 1) ?: 0f >= tileDrawLightThreshold
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun drawTiles(batch: SpriteBatch, mode: Int, drawModeTilesBlendMul: Boolean, color: Color) {
|
|
||||||
val for_y_start = WorldCamera.y / TILE_SIZE
|
|
||||||
val for_y_end = clampHTile(for_y_start + (WorldCamera.height / TILE_SIZE) + 2)
|
|
||||||
|
|
||||||
val for_x_start = WorldCamera.x / TILE_SIZE - 1
|
|
||||||
val for_x_end = for_x_start + (WorldCamera.width / TILE_SIZE) + 3
|
|
||||||
|
|
||||||
val originalBatchColour = batch.color.cpy()
|
|
||||||
batch.color = color
|
|
||||||
|
|
||||||
// loop
|
|
||||||
for (y in for_y_start..for_y_end) {
|
|
||||||
var zeroTileCounter = 0
|
|
||||||
|
|
||||||
for (x in for_x_start..for_x_end) {
|
|
||||||
|
|
||||||
val thisTile: Int?
|
|
||||||
if (mode % 3 == WALL)
|
|
||||||
thisTile = world.getTileFromWall(x, y)
|
|
||||||
else if (mode % 3 == TERRAIN)
|
|
||||||
thisTile = world.getTileFromTerrain(x, y)
|
|
||||||
else if (mode % 3 == WIRE)
|
|
||||||
thisTile = world.getTileFromWire(x, y)
|
|
||||||
else
|
|
||||||
throw IllegalArgumentException()
|
|
||||||
|
|
||||||
val noDamageLayer = mode % 3 == WIRE
|
|
||||||
|
|
||||||
// draw a tile, but only when illuminated
|
|
||||||
try {
|
|
||||||
if (canIHazRender(mode, x, y)) {
|
|
||||||
|
|
||||||
if (!hasLightNearby(x, y)) {
|
|
||||||
// draw black patch
|
|
||||||
zeroTileCounter += 1 // unused for now
|
|
||||||
|
|
||||||
// temporary solution; FIXME bad scanlines bug
|
|
||||||
batch.color = Color.BLACK
|
|
||||||
batch.fillRect(x * TILE_SIZEF, y * TILE_SIZEF, TILE_SIZEF, TILE_SIZEF)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// commented out; FIXME bad scanlines bug
|
|
||||||
if (zeroTileCounter > 0) {
|
|
||||||
/*batch.color = Color.BLACK
|
|
||||||
batch.fillRect(x * TILE_SIZEF, y * TILE_SIZEF, -zeroTileCounter * TILE_SIZEF, TILE_SIZEF)
|
|
||||||
batch.color = color
|
|
||||||
zeroTileCounter = 0*/
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val nearbyTilesInfo: Int
|
|
||||||
if (isPlatform(thisTile)) {
|
|
||||||
nearbyTilesInfo = getNearbyTilesInfoPlatform(x, y)
|
|
||||||
}
|
|
||||||
else if (isWallSticker(thisTile)) {
|
|
||||||
nearbyTilesInfo = getNearbyTilesInfoWallSticker(x, y)
|
|
||||||
}
|
|
||||||
else if (isConnectMutual(thisTile)) {
|
|
||||||
nearbyTilesInfo = getNearbyTilesInfoConMutual(x, y, mode)
|
|
||||||
}
|
|
||||||
else if (isConnectSelf(thisTile)) {
|
|
||||||
nearbyTilesInfo = getNearbyTilesInfoConSelf(x, y, mode, thisTile)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
nearbyTilesInfo = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val thisTileX = if (!noDamageLayer)
|
|
||||||
PairedMapLayer.RANGE * ((thisTile ?: 0) % PairedMapLayer.RANGE) + nearbyTilesInfo
|
|
||||||
else
|
|
||||||
nearbyTilesInfo
|
|
||||||
|
|
||||||
val thisTileY = (thisTile ?: 0) / PairedMapLayer.RANGE
|
|
||||||
|
|
||||||
|
|
||||||
// draw a tile
|
|
||||||
if (drawModeTilesBlendMul) {
|
|
||||||
if (BlocksDrawer.isBlendMul(thisTile)) {
|
|
||||||
batch.color = color
|
|
||||||
drawTile(batch, mode, x, y, thisTileX, thisTileY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// do NOT add "if (!isBlendMul(thisTile))"!
|
|
||||||
// or else they will not look like they should be when backed with wall
|
|
||||||
batch.color = color
|
|
||||||
drawTile(batch, mode, x, y, thisTileX, thisTileY)
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw a breakage
|
|
||||||
if (mode == TERRAIN || mode == WALL) {
|
|
||||||
val breakage = if (mode == TERRAIN) world.getTerrainDamage(x, y) else world.getWallDamage(x, y)
|
|
||||||
val maxHealth = BlockCodex[world.getTileFromTerrain(x, y)].strength
|
|
||||||
val stage = (breakage / maxHealth).times(breakAnimSteps).roundToInt()
|
|
||||||
// actual drawing
|
|
||||||
if (stage > 0) {
|
|
||||||
batch.color = color
|
|
||||||
drawTile(batch, mode, x, y, 5 + stage, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // end if (is illuminated)
|
|
||||||
} // end if (not an air)
|
|
||||||
} catch (e: NullPointerException) {
|
|
||||||
// do nothing. WARNING: This exception handling may hide erratic behaviour completely.
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// hit the end of the current scanline
|
|
||||||
// FIXME bad scanlines bug
|
|
||||||
/*if (x == for_x_end) {
|
|
||||||
val x = x + 1 // because current tile is also counted
|
|
||||||
batch.color = Color.BLACK
|
|
||||||
batch.fillRect(x * TILE_SIZEF, y * TILE_SIZEF, -zeroTileCounter * TILE_SIZEF, TILE_SIZEF)
|
|
||||||
batch.color = color
|
|
||||||
zeroTileCounter = 0
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
batch.color = originalBatchColour
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
|
|
||||||
* @param x
|
|
||||||
* *
|
|
||||||
* @param y
|
|
||||||
* *
|
|
||||||
* @return binary [0-15] 1: up, 2: right, 4: down, 8: left
|
|
||||||
*/
|
|
||||||
fun getNearbyTilesInfoConSelf(x: Int, y: Int, mode: Int, mark: Int?): Int {
|
|
||||||
val nearbyTiles = IntArray(4)
|
|
||||||
nearbyTiles[NEARBY_TILE_KEY_LEFT] = world.getTileFrom(mode, x - 1, y) ?: Block.NULL
|
|
||||||
nearbyTiles[NEARBY_TILE_KEY_RIGHT] = world.getTileFrom(mode, x + 1, y) ?: Block.NULL
|
|
||||||
nearbyTiles[NEARBY_TILE_KEY_UP] = world.getTileFrom(mode, x , y - 1) ?: 4906
|
|
||||||
nearbyTiles[NEARBY_TILE_KEY_DOWN] = world.getTileFrom(mode, x , y + 1) ?: Block.NULL
|
|
||||||
|
|
||||||
// try for
|
|
||||||
var ret = 0
|
|
||||||
for (i in 0..3) {
|
|
||||||
if (nearbyTiles[i] == mark) {
|
|
||||||
ret += 1 shl i // add 1, 2, 4, 8 for i = 0, 1, 2, 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getNearbyTilesInfoConMutual(x: Int, y: Int, mode: Int): Int {
|
|
||||||
val nearbyTiles = IntArray(4)
|
|
||||||
nearbyTiles[NEARBY_TILE_KEY_LEFT] = world.getTileFrom(mode, x - 1, y) ?: Block.NULL
|
|
||||||
nearbyTiles[NEARBY_TILE_KEY_RIGHT] = world.getTileFrom(mode, x + 1, y) ?: Block.NULL
|
|
||||||
nearbyTiles[NEARBY_TILE_KEY_UP] = world.getTileFrom(mode, x , y - 1) ?: 4906
|
|
||||||
nearbyTiles[NEARBY_TILE_KEY_DOWN] = world.getTileFrom(mode, x , y + 1) ?: Block.NULL
|
|
||||||
|
|
||||||
// try for
|
|
||||||
var ret = 0
|
|
||||||
for (i in 0..3) {
|
|
||||||
try {
|
|
||||||
if (!BlockCodex[nearbyTiles[i]].isSolid) {
|
|
||||||
//&& !BlockCodex[nearbyTiles[i]].isFluid) {
|
|
||||||
ret += (1 shl i) // add 1, 2, 4, 8 for i = 0, 1, 2, 3
|
|
||||||
}
|
|
||||||
} catch (e: ArrayIndexOutOfBoundsException) {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getNearbyTilesInfoWallSticker(x: Int, y: Int): Int {
|
|
||||||
val nearbyTiles = IntArray(4)
|
|
||||||
val NEARBY_TILE_KEY_BACK = NEARBY_TILE_KEY_UP
|
|
||||||
nearbyTiles[NEARBY_TILE_KEY_LEFT] = world.getTileFrom(TERRAIN, x - 1, y) ?: Block.NULL
|
|
||||||
nearbyTiles[NEARBY_TILE_KEY_RIGHT] = world.getTileFrom(TERRAIN, x + 1, y) ?: Block.NULL
|
|
||||||
nearbyTiles[NEARBY_TILE_KEY_DOWN] = world.getTileFrom(TERRAIN, x , y + 1) ?: Block.NULL
|
|
||||||
nearbyTiles[NEARBY_TILE_KEY_BACK] = world.getTileFrom(WALL, x , y) ?: Block.NULL
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_DOWN]].isSolid)
|
|
||||||
// has tile on the bottom
|
|
||||||
return 3
|
|
||||||
else if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_RIGHT]].isSolid
|
|
||||||
&& BlockCodex[nearbyTiles[NEARBY_TILE_KEY_LEFT]].isSolid)
|
|
||||||
// has tile on both sides
|
|
||||||
return 0
|
|
||||||
else if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_RIGHT]].isSolid)
|
|
||||||
// has tile on the right
|
|
||||||
return 2
|
|
||||||
else if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_LEFT]].isSolid)
|
|
||||||
// has tile on the left
|
|
||||||
return 1
|
|
||||||
else if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_BACK]].isSolid)
|
|
||||||
// has tile on the back
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
return 3
|
|
||||||
} catch (e: ArrayIndexOutOfBoundsException) {
|
|
||||||
return if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_DOWN]].isSolid)
|
|
||||||
// has tile on the bottom
|
|
||||||
3 else 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getNearbyTilesInfoPlatform(x: Int, y: Int): Int {
|
|
||||||
val nearbyTiles = IntArray(4)
|
|
||||||
nearbyTiles[NEARBY_TILE_KEY_LEFT] = world.getTileFrom(TERRAIN, x - 1, y) ?: Block.NULL
|
|
||||||
nearbyTiles[NEARBY_TILE_KEY_RIGHT] = world.getTileFrom(TERRAIN, x + 1, y) ?: Block.NULL
|
|
||||||
|
|
||||||
if ((BlockCodex[nearbyTiles[NEARBY_TILE_KEY_LEFT]].isSolid &&
|
|
||||||
BlockCodex[nearbyTiles[NEARBY_TILE_KEY_RIGHT]].isSolid) ||
|
|
||||||
isPlatform(nearbyTiles[NEARBY_TILE_KEY_LEFT]) &&
|
|
||||||
isPlatform(nearbyTiles[NEARBY_TILE_KEY_RIGHT])) // LR solid || LR platform
|
|
||||||
return 0
|
|
||||||
else if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_LEFT]].isSolid &&
|
|
||||||
!isPlatform(nearbyTiles[NEARBY_TILE_KEY_LEFT]) &&
|
|
||||||
!BlockCodex[nearbyTiles[NEARBY_TILE_KEY_RIGHT]].isSolid &&
|
|
||||||
!isPlatform(nearbyTiles[NEARBY_TILE_KEY_RIGHT])) // L solid and not platform && R not solid and not platform
|
|
||||||
return 4
|
|
||||||
else if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_RIGHT]].isSolid &&
|
|
||||||
!isPlatform(nearbyTiles[NEARBY_TILE_KEY_RIGHT]) &&
|
|
||||||
!BlockCodex[nearbyTiles[NEARBY_TILE_KEY_LEFT]].isSolid &&
|
|
||||||
!isPlatform(nearbyTiles[NEARBY_TILE_KEY_LEFT])) // R solid and not platform && L not solid and nto platform
|
|
||||||
return 6
|
|
||||||
else if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_LEFT]].isSolid &&
|
|
||||||
!isPlatform(nearbyTiles[NEARBY_TILE_KEY_LEFT])) // L solid && L not platform
|
|
||||||
return 3
|
|
||||||
else if (BlockCodex[nearbyTiles[NEARBY_TILE_KEY_RIGHT]].isSolid &&
|
|
||||||
!isPlatform(nearbyTiles[NEARBY_TILE_KEY_RIGHT])) // R solid && R not platform
|
|
||||||
return 5
|
|
||||||
else if ((BlockCodex[nearbyTiles[NEARBY_TILE_KEY_LEFT]].isSolid ||
|
|
||||||
isPlatform(nearbyTiles[NEARBY_TILE_KEY_LEFT])) &&
|
|
||||||
!BlockCodex[nearbyTiles[NEARBY_TILE_KEY_RIGHT]].isSolid &&
|
|
||||||
!isPlatform(nearbyTiles[NEARBY_TILE_KEY_RIGHT])) // L solid or platform && R not solid and not platform
|
|
||||||
return 1
|
|
||||||
else if ((BlockCodex[nearbyTiles[NEARBY_TILE_KEY_RIGHT]].isSolid ||
|
|
||||||
isPlatform(nearbyTiles[NEARBY_TILE_KEY_RIGHT])) &&
|
|
||||||
!BlockCodex[nearbyTiles[NEARBY_TILE_KEY_LEFT]].isSolid &&
|
|
||||||
!isPlatform(nearbyTiles[NEARBY_TILE_KEY_LEFT])) // R solid or platform && L not solid and not platform
|
|
||||||
return 2
|
|
||||||
else
|
|
||||||
return 7
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun drawTile(batch: SpriteBatch, mode: Int, tilewisePosX: Int, tilewisePosY: Int, sheetX: Int, sheetY: Int) {
|
|
||||||
if (mode == TERRAIN || mode == WALL)
|
|
||||||
batch.draw(
|
|
||||||
tilesTerrain.get(sheetX, sheetY),
|
|
||||||
tilewisePosX * TILE_SIZEF,
|
|
||||||
tilewisePosY * TILE_SIZEF
|
|
||||||
)
|
|
||||||
else if (mode == WIRE)
|
|
||||||
batch.draw(
|
|
||||||
tilesWire.get(sheetX, sheetY),
|
|
||||||
tilewisePosX * TILE_SIZEF,
|
|
||||||
tilewisePosY * TILE_SIZEF
|
|
||||||
)
|
|
||||||
else
|
|
||||||
throw IllegalArgumentException()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clampH(x: Int): Int {
|
|
||||||
if (x < 0) {
|
|
||||||
return 0
|
|
||||||
} else if (x > world.height * TILE_SIZE) {
|
|
||||||
return world.height * TILE_SIZE
|
|
||||||
} else {
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clampWTile(x: Int): Int {
|
|
||||||
if (x < 0) {
|
|
||||||
return 0
|
|
||||||
} else if (x > world.width) {
|
|
||||||
return world.width
|
|
||||||
} else {
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clampHTile(x: Int): Int {
|
|
||||||
if (x < 0) {
|
|
||||||
return 0
|
|
||||||
} else if (x > world.height) {
|
|
||||||
return world.height
|
|
||||||
} else {
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getRenderStartX(): Int = WorldCamera.x / TILE_SIZE
|
|
||||||
fun getRenderStartY(): Int = WorldCamera.y / TILE_SIZE
|
|
||||||
|
|
||||||
fun getRenderEndX(): Int = clampWTile(getRenderStartX() + (WorldCamera.width / TILE_SIZE) + 2)
|
|
||||||
fun getRenderEndY(): Int = clampHTile(getRenderStartY() + (WorldCamera.height / TILE_SIZE) + 2)
|
|
||||||
|
|
||||||
fun isConnectSelf(b: Int?): Boolean = TILES_CONNECT_SELF.contains(b)
|
|
||||||
fun isConnectMutual(b: Int?): Boolean = TILES_CONNECT_MUTUAL.contains(b)
|
|
||||||
fun isWallSticker(b: Int?): Boolean = TILES_WALL_STICKER.contains(b)
|
|
||||||
fun isPlatform(b: Int?): Boolean = TILES_WALL_STICKER_CONNECT_SELF.contains(b)
|
|
||||||
fun isBlendMul(b: Int?): Boolean = TILES_BLEND_MUL.contains(b)
|
|
||||||
|
|
||||||
fun tileInCamera(x: Int, y: Int) =
|
|
||||||
x >= WorldCamera.x.div(TILE_SIZE) && y >= WorldCamera.y.div(TILE_SIZE) &&
|
|
||||||
x <= WorldCamera.x.plus(WorldCamera.width).div(TILE_SIZE) && y <= WorldCamera.y.plus(WorldCamera.width).div(TILE_SIZE)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
@@ -1,10 +1,961 @@
|
|||||||
package net.torvald.terrarum.worlddrawer
|
package net.torvald.terrarum.worlddrawer
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.badlogic.gdx.graphics.Pixmap
|
||||||
|
import com.badlogic.gdx.graphics.Texture
|
||||||
|
import net.torvald.gdx.graphics.Cvec
|
||||||
|
import net.torvald.gdx.graphics.UnsafeCvecArray
|
||||||
|
import net.torvald.terrarum.*
|
||||||
|
import net.torvald.terrarum.AppLoader.printdbg
|
||||||
|
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
|
||||||
|
import net.torvald.terrarum.blockproperties.Block
|
||||||
|
import net.torvald.terrarum.blockproperties.BlockCodex
|
||||||
|
import net.torvald.terrarum.blockproperties.Fluid
|
||||||
|
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.modulebasegame.ui.abs
|
||||||
|
import net.torvald.terrarum.realestate.LandUtil
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Warning: you are not going to store float value to the lightmap -- see RGB_HDR_LUT (beziér)
|
* Sub-portion of IngameRenderer. You are not supposed to directly deal with this.
|
||||||
*
|
*
|
||||||
* Created by minjaesong on 2016-01-25.
|
* Created by minjaesong on 2016-01-25.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//typealias RGB10 = Int
|
||||||
|
|
||||||
|
// NOTE: no Float16 on this thing: 67 kB of memory footage is totally acceptable
|
||||||
|
|
||||||
|
/** This object should not be called by yourself; must be only being used and manipulated by your
|
||||||
|
* own ingame renderer
|
||||||
|
*/
|
||||||
|
object LightmapRenderer {
|
||||||
|
|
||||||
|
/** World change is managed by IngameRenderer.setWorld() */
|
||||||
|
private var world: GameWorld = GameWorld.makeNullWorld()
|
||||||
|
|
||||||
|
//private lateinit var lightCalcShader: ShaderProgram
|
||||||
|
//private val SHADER_LIGHTING = AppLoader.getConfigBoolean("gpulightcalc")
|
||||||
|
|
||||||
|
/** do not call this yourself! Let your game renderer handle this! */
|
||||||
|
internal fun internalSetWorld(world: GameWorld) {
|
||||||
|
try {
|
||||||
|
if (this.world != world) {
|
||||||
|
printdbg(this, "World change detected -- old world: ${this.world.hashCode()}, new world: ${world.hashCode()}")
|
||||||
|
|
||||||
|
lightmap.zerofill()
|
||||||
|
_mapLightLevelThis.zerofill()
|
||||||
|
_mapThisTileOpacity.zerofill()
|
||||||
|
_mapThisTileOpacity2.zerofill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: UninitializedPropertyAccessException) {
|
||||||
|
// new init, do nothing
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.world = world
|
||||||
|
|
||||||
|
// fireRecalculateEvent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const val overscan_open: Int = 40
|
||||||
|
const val overscan_opaque: Int = 10
|
||||||
|
|
||||||
|
private var LIGHTMAP_WIDTH: Int = (Terrarum.ingame?.ZOOM_MINIMUM ?: 1f).inv().times(AppLoader.screenSize.screenW).div(TILE_SIZE).ceilInt() + overscan_open * 2 + 3
|
||||||
|
private var LIGHTMAP_HEIGHT: Int = (Terrarum.ingame?.ZOOM_MINIMUM ?: 1f).inv().times(AppLoader.screenSize.screenH).div(TILE_SIZE).ceilInt() + overscan_open * 2 + 3
|
||||||
|
|
||||||
|
//private val noopMask = HashSet<Point2i>((LIGHTMAP_WIDTH + LIGHTMAP_HEIGHT) * 2)
|
||||||
|
|
||||||
|
private val lanternMap = HashMap<BlockAddress, Cvec>((Terrarum.ingame?.ACTORCONTAINER_INITIAL_SIZE ?: 2) * 4)
|
||||||
|
/**
|
||||||
|
* Float value, 1.0 for 1023
|
||||||
|
*
|
||||||
|
* Note: using UnsafeCvecArray does not actually show great performance improvement
|
||||||
|
*/
|
||||||
|
// it utilises alpha channel to determine brightness of "glow" sprites (so that alpha channel works like UV light)
|
||||||
|
private var lightmap = UnsafeCvecArray(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT)
|
||||||
|
private var _mapLightLevelThis = UnsafeCvecArray(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT)
|
||||||
|
private var _mapThisTileOpacity = UnsafeCvecArray(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT)
|
||||||
|
private var _mapThisTileOpacity2 = UnsafeCvecArray(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT)
|
||||||
|
|
||||||
|
init {
|
||||||
|
LightmapHDRMap.invoke()
|
||||||
|
printdbg(this, "Overscan open: $overscan_open; opaque: $overscan_opaque")
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val AIR = Block.AIR
|
||||||
|
|
||||||
|
const val DRAW_TILE_SIZE: Float = TILE_SIZE / IngameRenderer.lightmapDownsample
|
||||||
|
|
||||||
|
internal var for_x_start = 0
|
||||||
|
internal var for_y_start = 0
|
||||||
|
internal var for_x_end = 0
|
||||||
|
internal var for_y_end = 0
|
||||||
|
internal var for_draw_x_start = 0
|
||||||
|
internal var for_draw_y_start = 0
|
||||||
|
internal var for_draw_x_end = 0
|
||||||
|
internal var for_draw_y_end = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param x world coord
|
||||||
|
* @param y world coord
|
||||||
|
*/
|
||||||
|
private fun inBounds(x: Int, y: Int) =
|
||||||
|
(y - for_y_start + overscan_open in 0 until LIGHTMAP_HEIGHT &&
|
||||||
|
x - for_x_start + overscan_open in 0 until LIGHTMAP_WIDTH)
|
||||||
|
/** World coord to array coord */
|
||||||
|
private inline fun Int.convX() = this - for_x_start + overscan_open
|
||||||
|
/** World coord to array coord */
|
||||||
|
private inline fun Int.convY() = this - for_y_start + overscan_open
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conventional level (multiplied by four)
|
||||||
|
*
|
||||||
|
* @param x world tile coord
|
||||||
|
* @param y world tile coord
|
||||||
|
*/
|
||||||
|
internal fun getLight(x: Int, y: Int): Cvec? {
|
||||||
|
return if (!inBounds(x, y)) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val x = x.convX()
|
||||||
|
val y = y.convY()
|
||||||
|
|
||||||
|
Cvec(
|
||||||
|
lightmap.getR(x, y),
|
||||||
|
lightmap.getG(x, y),
|
||||||
|
lightmap.getB(x, y),
|
||||||
|
lightmap.getA(x, y)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun fireRecalculateEvent(vararg actorContainers: List<ActorWithBody>?) {
|
||||||
|
try {
|
||||||
|
world.getTileFromTerrain(0, 0) // test inquiry
|
||||||
|
}
|
||||||
|
catch (e: UninitializedPropertyAccessException) {
|
||||||
|
return // quit prematurely
|
||||||
|
}
|
||||||
|
catch (e: NullPointerException) {
|
||||||
|
System.err.println("[LightmapRendererNew.fireRecalculateEvent] Attempted to refer destroyed unsafe array " +
|
||||||
|
"(${world.layerTerrain.ptr})")
|
||||||
|
e.printStackTrace()
|
||||||
|
return // something's wrong but we'll ignore it like a trustful AK
|
||||||
|
}
|
||||||
|
|
||||||
|
if (world.worldIndex == -1) return
|
||||||
|
|
||||||
|
|
||||||
|
for_x_start = WorldCamera.zoomedX / TILE_SIZE // fix for premature lightmap rendering
|
||||||
|
for_y_start = WorldCamera.zoomedY / TILE_SIZE // on topmost/leftmost side
|
||||||
|
for_draw_x_start = WorldCamera.x / TILE_SIZE
|
||||||
|
for_draw_y_start = WorldCamera.y / TILE_SIZE
|
||||||
|
|
||||||
|
if (WorldCamera.x < 0) for_draw_x_start -= 1 // edge case fix that light shift 1 tile to the left when WorldCamera.x < 0
|
||||||
|
//if (WorldCamera.x in -(TILE_SIZE - 1)..-1) for_draw_x_start -= 1 // another edge-case fix; we don't need this anymore?
|
||||||
|
|
||||||
|
for_x_end = for_x_start + WorldCamera.zoomedWidth / TILE_SIZE + 3
|
||||||
|
for_y_end = for_y_start + WorldCamera.zoomedHeight / TILE_SIZE + 3 // same fix as above
|
||||||
|
for_draw_x_end = for_draw_x_start + WorldCamera.width / TILE_SIZE + 3
|
||||||
|
for_draw_y_end = for_draw_y_start + WorldCamera.height / TILE_SIZE + 3
|
||||||
|
|
||||||
|
//println("$for_x_start..$for_x_end, $for_x\t$for_y_start..$for_y_end, $for_y")
|
||||||
|
|
||||||
|
AppLoader.measureDebugTime("Renderer.Lanterns") {
|
||||||
|
buildLanternmap(actorContainers)
|
||||||
|
} // usually takes 3000 ns
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Updating order:
|
||||||
|
* ,--------. ,--+-----. ,-----+--. ,--------. -
|
||||||
|
* |↘ | | | 3| |3 | | | ↙| ↕︎ overscan_open / overscan_opaque
|
||||||
|
* | ,-----+ | | 2 | | 2 | | +-----. | - depending on the noop_mask
|
||||||
|
* | |1 | | |1 | | 1| | | 1| |
|
||||||
|
* | | 2 | | `-----+ +-----' | | 2 | |
|
||||||
|
* | | 3| |↗ | | ↖| |3 | |
|
||||||
|
* `--+-----' `--------' `--------' `-----+--'
|
||||||
|
* round: 1 2 3 4
|
||||||
|
* for all lightmap[y][x], run in this order: 2-3-4-1
|
||||||
|
* If you run only 4 sets, orthogonal/diagonal artefacts are bound to occur,
|
||||||
|
*/
|
||||||
|
|
||||||
|
// set sunlight
|
||||||
|
sunLight.set(world.globalLight)
|
||||||
|
|
||||||
|
// set no-op mask from solidity of the block
|
||||||
|
/*AppLoader.measureDebugTime("Renderer.LightNoOpMask") {
|
||||||
|
noopMask.clear()
|
||||||
|
buildNoopMask()
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// wipe out lightmap
|
||||||
|
AppLoader.measureDebugTime("Renderer.LightPrecalc") {
|
||||||
|
// 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()
|
||||||
|
_mapLightLevelThis.zerofill()
|
||||||
|
//lightsourceMap.clear()
|
||||||
|
|
||||||
|
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) {
|
||||||
|
precalculate(x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// YE OLDE LIGHT UPDATER
|
||||||
|
// O((5*9)n where n is a size of the map.
|
||||||
|
// Because of inevitable overlaps on the area, it only works with MAX blend
|
||||||
|
/*fun or1() {
|
||||||
|
// Round 1
|
||||||
|
for (y in for_y_start - overscan_open..for_y_end) {
|
||||||
|
for (x in for_x_start - overscan_open..for_x_end) {
|
||||||
|
calculateAndAssign(lightmap, x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun or2() {
|
||||||
|
// Round 2
|
||||||
|
for (y in for_y_end + overscan_open downTo for_y_start) {
|
||||||
|
for (x in for_x_start - overscan_open..for_x_end) {
|
||||||
|
calculateAndAssign(lightmap, x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun or3() {
|
||||||
|
// Round 3
|
||||||
|
for (y in for_y_end + overscan_open downTo for_y_start) {
|
||||||
|
for (x in for_x_end + overscan_open downTo for_x_start) {
|
||||||
|
calculateAndAssign(lightmap, x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun or4() {
|
||||||
|
// Round 4
|
||||||
|
for (y in for_y_start - overscan_open..for_y_end) {
|
||||||
|
for (x in for_x_end + overscan_open downTo for_x_start) {
|
||||||
|
calculateAndAssign(lightmap, x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// 'NEWLIGHT2' LIGHT SWIPER
|
||||||
|
// O((8*2)n) where n is a size of the map.
|
||||||
|
fun r1() {
|
||||||
|
// TODO test non-parallel
|
||||||
|
swipeDiag = false
|
||||||
|
for (line in 1 until LIGHTMAP_HEIGHT - 1) {
|
||||||
|
swipeLight(
|
||||||
|
1, line,
|
||||||
|
LIGHTMAP_WIDTH - 2, line,
|
||||||
|
1, 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun r2() {
|
||||||
|
// TODO test non-parallel
|
||||||
|
swipeDiag = false
|
||||||
|
for (line in 1 until LIGHTMAP_WIDTH - 1) {
|
||||||
|
swipeLight(
|
||||||
|
line, 1,
|
||||||
|
line, LIGHTMAP_HEIGHT - 2,
|
||||||
|
0, 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun r3() {
|
||||||
|
// TODO test non-parallel
|
||||||
|
swipeDiag = true
|
||||||
|
/* construct indices such that:
|
||||||
|
56789ABC
|
||||||
|
4 1 w-2
|
||||||
|
3 \---\---+
|
||||||
|
2 \\···\··|
|
||||||
|
1 \\\···\·|
|
||||||
|
0 \\\\···\|
|
||||||
|
h-2 \\\\\---\
|
||||||
|
|
||||||
|
0 (1, h-2) -> (1, h-2)
|
||||||
|
1 (1, h-2-1) -> (2, h-2)
|
||||||
|
2 (1, h-2-2) -> (3, h-2)
|
||||||
|
3 (1, h-2-3) -> (4, h-2)
|
||||||
|
4 (1, 1) -> (5, h-2)
|
||||||
|
|
||||||
|
5 (2, 1) -> (6, h-2)
|
||||||
|
6 (3, 1) -> (7, h-2)
|
||||||
|
7 (4, 1) -> (8, h-2)
|
||||||
|
8 (5, 1) -> (w-2, h-2)
|
||||||
|
|
||||||
|
9 (6, 1) -> (w-2, h-2-1)
|
||||||
|
10 (7, 1) -> (w-2, h-2-2)
|
||||||
|
11 (8, 1) -> (w-2, h-2-3)
|
||||||
|
12 (w-2, 1) -> (w-2, 1)
|
||||||
|
|
||||||
|
number of indices: internal_width + internal_height - 1
|
||||||
|
*/
|
||||||
|
for (i in 0 until LIGHTMAP_WIDTH + LIGHTMAP_HEIGHT - 5) {
|
||||||
|
swipeLight(
|
||||||
|
maxOf(1, i - LIGHTMAP_HEIGHT + 4), maxOf(1, LIGHTMAP_HEIGHT - 2 - i),
|
||||||
|
minOf(LIGHTMAP_WIDTH - 2, i + 1), minOf(LIGHTMAP_HEIGHT - 2, (LIGHTMAP_WIDTH + LIGHTMAP_HEIGHT - 5) - i),
|
||||||
|
1, 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun r4() {
|
||||||
|
// TODO test non-parallel
|
||||||
|
swipeDiag = true
|
||||||
|
/*
|
||||||
|
1 w-2
|
||||||
|
/////---/
|
||||||
|
////···/|
|
||||||
|
///···/·|
|
||||||
|
//···/··|
|
||||||
|
h-2 /---/---+
|
||||||
|
d:(1,-1)
|
||||||
|
|
||||||
|
0 (1, 1) -> (1, 1)
|
||||||
|
1 (1, 2) -> (2, 1)
|
||||||
|
2 (1, 3) -> (3, 1)
|
||||||
|
3 (1, 4) -> (4, 1)
|
||||||
|
4 (1, h-2) -> (5, 1)
|
||||||
|
5 (2, h-2) -> (6, 1)
|
||||||
|
6 (3, h-2) -> (7, 1)
|
||||||
|
7 (4, h-2) -> (8, 1)
|
||||||
|
8 (5, h-2) -> (w-2, 1)
|
||||||
|
9 (6, h-2) -> (w-2, 2)
|
||||||
|
10 (7, h-2) -> (w-2, 3)
|
||||||
|
11 (8, h-2) -> (w-2, 4)
|
||||||
|
12 (w-2, h-2) -> (w-2, h-2)
|
||||||
|
*/
|
||||||
|
for (i in 0 until LIGHTMAP_WIDTH + LIGHTMAP_HEIGHT - 5) {
|
||||||
|
swipeLight(
|
||||||
|
maxOf(1, i - LIGHTMAP_HEIGHT + 4), minOf(LIGHTMAP_HEIGHT - 2, i + 1),
|
||||||
|
minOf(LIGHTMAP_WIDTH - 2, i + 1), maxOf(1, (LIGHTMAP_HEIGHT - 2) - (LIGHTMAP_WIDTH + LIGHTMAP_HEIGHT - 6) + i),
|
||||||
|
1, -1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// each usually takes 8..12 ms total when not threaded
|
||||||
|
// - with direct memory access of world array and pre-calculating things in the start of the frame,
|
||||||
|
// I was able to pull out 3.5..5.5 ms! With abhorrently many occurrences of segfaults I had to track down...
|
||||||
|
// - with 'NEWLIGHT2', I was able to pull ~2 ms!
|
||||||
|
//
|
||||||
|
// multithreading - forget about it; overhead is way too big and for some reason i was not able to
|
||||||
|
// resolve the 'noisy shit' artefact
|
||||||
|
AppLoader.measureDebugTime("Renderer.LightRuns") {
|
||||||
|
|
||||||
|
// To save you from pains:
|
||||||
|
// - Per-channel light updating is actually slower
|
||||||
|
// BELOW NOTES DOES NOT APPLY TO 'NEWLIGHT2' LIGHT SWIPER
|
||||||
|
// - It seems 5-pass lighting is needed to resonably eliminate the dark spot (of which I have zero idea
|
||||||
|
// why dark spots appear in the first place)
|
||||||
|
// - Multithreading? I have absolutely no idea.
|
||||||
|
// - If you naively slice the screen (job area) to multithread, the seam will appear.
|
||||||
|
|
||||||
|
r1();r2();r3();r4()
|
||||||
|
r1();r2();r3();r4() // two looks better than one
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildLanternmap(actorContainers: Array<out List<ActorWithBody>?>) {
|
||||||
|
lanternMap.clear()
|
||||||
|
actorContainers.forEach { actorContainer ->
|
||||||
|
actorContainer?.forEach {
|
||||||
|
if (it is Luminous) {
|
||||||
|
// put lanterns to the area the luminantBox is occupying
|
||||||
|
for (lightBox in it.lightBoxList) {
|
||||||
|
val lightBoxX = it.hitbox.startX + lightBox.startX
|
||||||
|
val lightBoxY = it.hitbox.startY + lightBox.startY
|
||||||
|
val lightBoxW = lightBox.width
|
||||||
|
val lightBoxH = lightBox.height
|
||||||
|
for (y in lightBoxY.div(TILE_SIZE).floorInt()
|
||||||
|
..lightBoxY.plus(lightBoxH).div(TILE_SIZE).floorInt()) {
|
||||||
|
for (x in lightBoxX.div(TILE_SIZE).floorInt()
|
||||||
|
..lightBoxX.plus(lightBoxW).div(TILE_SIZE).floorInt()) {
|
||||||
|
|
||||||
|
val normalisedCvec = it.color//.cpy().mul(DIV_FLOAT)
|
||||||
|
|
||||||
|
lanternMap[LandUtil.getBlockAddr(world, x, y)] = normalisedCvec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*private fun buildNoopMask() {
|
||||||
|
fun isShaded(x: Int, y: Int) = try {
|
||||||
|
BlockCodex[world.getTileFromTerrain(x, y)].isSolid
|
||||||
|
}
|
||||||
|
catch (e: NullPointerException) {
|
||||||
|
System.err.println("[LightmapRendererNew.buildNoopMask] Invalid block id ${world.getTileFromTerrain(x, y)} from coord ($x, $y)")
|
||||||
|
e.printStackTrace()
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
update ordering: clockwise snake
|
||||||
|
|
||||||
|
for_x_start
|
||||||
|
|
|
||||||
|
02468>..............|--for_y_start
|
||||||
|
: :
|
||||||
|
: :
|
||||||
|
: :
|
||||||
|
V V
|
||||||
|
13579>............../--for_y_end
|
||||||
|
|
|
||||||
|
for_x_end
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (x in for_x_start..for_x_end) {
|
||||||
|
if (isShaded(x, for_y_start)) noopMask.add(Point2i(x, for_y_start))
|
||||||
|
if (isShaded(x, for_y_end)) noopMask.add(Point2i(x, for_y_end))
|
||||||
|
}
|
||||||
|
for (y in for_y_start + 1..for_y_end - 1) {
|
||||||
|
if (isShaded(for_x_start, y)) noopMask.add(Point2i(for_x_start, y))
|
||||||
|
if (isShaded(for_x_end, y)) noopMask.add(Point2i(for_x_end, y))
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// local variables that are made static
|
||||||
|
private val sunLight = Cvec(0)
|
||||||
|
private var _thisTerrain = 0
|
||||||
|
private var _thisFluid = GameWorld.FluidInfo(Fluid.NULL, 0f)
|
||||||
|
private var _thisWall = 0
|
||||||
|
private val _ambientAccumulator = Cvec(0)
|
||||||
|
private val _thisTileOpacity = Cvec(0)
|
||||||
|
private val _thisTileOpacity2 = Cvec(0) // thisTileOpacity * sqrt(2)
|
||||||
|
private val _fluidAmountToCol = Cvec(0)
|
||||||
|
private val _thisTileLuminosity = Cvec(0)
|
||||||
|
|
||||||
|
fun precalculate(rawx: Int, rawy: Int) {
|
||||||
|
val lx = rawx.convX(); val ly = rawy.convY()
|
||||||
|
val (worldX, worldY) = world.coerceXY(rawx, rawy)
|
||||||
|
|
||||||
|
//printdbg(this, "precalculate ($rawx, $rawy) -> ($lx, $ly) | ($LIGHTMAP_WIDTH, $LIGHTMAP_HEIGHT)")
|
||||||
|
|
||||||
|
if (lx !in 0..LIGHTMAP_WIDTH || ly !in 0..LIGHTMAP_HEIGHT) {
|
||||||
|
println("[LightmapRendererNew.precalculate] Out of range: ($lx, $ly) for size ($LIGHTMAP_WIDTH, $LIGHTMAP_HEIGHT)")
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_thisTerrain = world.getTileFromTerrainRaw(worldX, worldY)
|
||||||
|
_thisFluid = world.getFluid(worldX, worldY)
|
||||||
|
_thisWall = world.getTileFromWallRaw(worldX, worldY)
|
||||||
|
|
||||||
|
|
||||||
|
// regarding the issue #26
|
||||||
|
// uncomment this and/or run JVM with -ea if you're facing diabolically indescribable bugs
|
||||||
|
/*try {
|
||||||
|
val fuck = BlockCodex[_thisTerrain].getLumCol(worldX, worldY)
|
||||||
|
}
|
||||||
|
catch (e: NullPointerException) {
|
||||||
|
System.err.println("## NPE -- x: $worldX, y: $worldY, value: $_thisTerrain")
|
||||||
|
e.printStackTrace()
|
||||||
|
// create shitty minidump
|
||||||
|
System.err.println("MINIMINIDUMP START")
|
||||||
|
for (xx in worldX - 16 until worldX + 16) {
|
||||||
|
val raw = world.getTileFromTerrain(xx, worldY)
|
||||||
|
val lsb = raw.and(0xff).toString(16).padStart(2, '0')
|
||||||
|
val msb = raw.ushr(8).and(0xff).toString(16).padStart(2, '0')
|
||||||
|
System.err.print(lsb)
|
||||||
|
System.err.print(msb)
|
||||||
|
System.err.print(" ")
|
||||||
|
}
|
||||||
|
System.err.println("\nMINIMINIDUMP END")
|
||||||
|
|
||||||
|
exitProcess(1)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
if (_thisFluid.type != Fluid.NULL) {
|
||||||
|
_fluidAmountToCol.set(_thisFluid.amount, _thisFluid.amount, _thisFluid.amount, _thisFluid.amount)
|
||||||
|
|
||||||
|
_thisTileLuminosity.set(BlockCodex[world.tileNumberToNameMap[_thisTerrain]].getLumCol(worldX, worldY))
|
||||||
|
_thisTileLuminosity.maxAndAssign(BlockCodex[_thisFluid.type].getLumCol(worldX, worldY).mul(_fluidAmountToCol)) // already been div by four
|
||||||
|
_mapThisTileOpacity.setVec(lx, ly, BlockCodex[world.tileNumberToNameMap[_thisTerrain]].opacity)
|
||||||
|
_mapThisTileOpacity.max(lx, ly, BlockCodex[_thisFluid.type].opacity.mul(_fluidAmountToCol))// already been div by four
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_thisTileLuminosity.set(BlockCodex[world.tileNumberToNameMap[_thisTerrain]].getLumCol(worldX, worldY))
|
||||||
|
_mapThisTileOpacity.setVec(lx, ly, BlockCodex[world.tileNumberToNameMap[_thisTerrain]].opacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
_mapThisTileOpacity2.setR(lx, ly, _mapThisTileOpacity.getR(lx, ly) * 1.41421356f)
|
||||||
|
_mapThisTileOpacity2.setG(lx, ly, _mapThisTileOpacity.getG(lx, ly) * 1.41421356f)
|
||||||
|
_mapThisTileOpacity2.setB(lx, ly, _mapThisTileOpacity.getB(lx, ly) * 1.41421356f)
|
||||||
|
_mapThisTileOpacity2.setA(lx, ly, _mapThisTileOpacity.getA(lx, ly) * 1.41421356f)
|
||||||
|
|
||||||
|
|
||||||
|
// open air || luminous tile backed by sunlight
|
||||||
|
if ((world.tileNumberToNameMap[_thisTerrain] == AIR && world.tileNumberToNameMap[_thisWall] == AIR) ||
|
||||||
|
(_thisTileLuminosity.nonZero() && world.tileNumberToNameMap[_thisWall] == AIR)) {
|
||||||
|
_mapLightLevelThis.setVec(lx, ly, sunLight)
|
||||||
|
}
|
||||||
|
|
||||||
|
// blend lantern
|
||||||
|
_mapLightLevelThis.max(lx, ly, _thisTileLuminosity.maxAndAssign(
|
||||||
|
lanternMap[LandUtil.getBlockAddr(world, worldX, worldY)] ?: colourNull
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*private val inNoopMaskp = Point2i(0,0)
|
||||||
|
|
||||||
|
private fun inNoopMask(x: Int, y: Int): Boolean {
|
||||||
|
if (x in for_x_start..for_x_end) {
|
||||||
|
// if it's in the top flange
|
||||||
|
inNoopMaskp.set(x, for_y_start)
|
||||||
|
if (y < for_y_start - overscan_opaque && noopMask.contains(inNoopMaskp)) return true
|
||||||
|
// if it's in the bottom flange
|
||||||
|
inNoopMaskp.y = for_y_end
|
||||||
|
return (y > for_y_end + overscan_opaque && noopMask.contains(inNoopMaskp))
|
||||||
|
}
|
||||||
|
else if (y in for_y_start..for_y_end) {
|
||||||
|
// if it's in the left flange
|
||||||
|
inNoopMaskp.set(for_x_start, y)
|
||||||
|
if (x < for_x_start - overscan_opaque && noopMask.contains(inNoopMaskp)) return true
|
||||||
|
// if it's in the right flange
|
||||||
|
inNoopMaskp.set(for_x_end, y)
|
||||||
|
return (x > for_x_end + overscan_opaque && noopMask.contains(inNoopMaskp))
|
||||||
|
}
|
||||||
|
// top-left corner
|
||||||
|
else if (x < for_x_start && y < for_y_start) {
|
||||||
|
inNoopMaskp.set(for_x_start, for_y_start)
|
||||||
|
return (x < for_x_start - overscan_opaque && y < for_y_start - overscan_opaque && noopMask.contains(inNoopMaskp))
|
||||||
|
}
|
||||||
|
// top-right corner
|
||||||
|
else if (x > for_x_end && y < for_y_start) {
|
||||||
|
inNoopMaskp.set(for_x_end, for_y_start)
|
||||||
|
return (x > for_x_end + overscan_opaque && y < for_y_start - overscan_opaque && noopMask.contains(inNoopMaskp))
|
||||||
|
}
|
||||||
|
// bottom-left corner
|
||||||
|
else if (x < for_x_start && y > for_y_end) {
|
||||||
|
inNoopMaskp.set(for_x_start, for_y_end)
|
||||||
|
return (x < for_x_start - overscan_opaque && y > for_y_end + overscan_opaque && noopMask.contains(inNoopMaskp))
|
||||||
|
}
|
||||||
|
// bottom-right corner
|
||||||
|
else if (x > for_x_end && y > for_y_end) {
|
||||||
|
inNoopMaskp.set(for_x_end, for_y_end)
|
||||||
|
return (x > for_x_end + overscan_opaque && y > for_y_end + overscan_opaque && noopMask.contains(inNoopMaskp))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
|
||||||
|
// if your IDE error out that you need return statement, AND it's "fixed" by removing 'else' before 'return false',
|
||||||
|
// you're doing it wrong, the IF and return statements must be inclusive.
|
||||||
|
}*/
|
||||||
|
|
||||||
|
private var swipeX = -1
|
||||||
|
private var swipeY = -1
|
||||||
|
private var swipeDiag = false
|
||||||
|
private fun _swipeTask(x: Int, y: Int, x2: Int, y2: Int) {
|
||||||
|
if (x2 < 0 || y2 < 0 || x2 >= LIGHTMAP_WIDTH || y2 >= LIGHTMAP_HEIGHT) return
|
||||||
|
|
||||||
|
_ambientAccumulator.r = _mapLightLevelThis.getR(x, y)
|
||||||
|
_ambientAccumulator.g = _mapLightLevelThis.getG(x, y)
|
||||||
|
_ambientAccumulator.b = _mapLightLevelThis.getB(x, y)
|
||||||
|
_ambientAccumulator.a = _mapLightLevelThis.getA(x, y)
|
||||||
|
|
||||||
|
if (!swipeDiag) {
|
||||||
|
_thisTileOpacity.r = _mapThisTileOpacity.getR(x, y)
|
||||||
|
_thisTileOpacity.g = _mapThisTileOpacity.getG(x, y)
|
||||||
|
_thisTileOpacity.b = _mapThisTileOpacity.getB(x, y)
|
||||||
|
_thisTileOpacity.a = _mapThisTileOpacity.getA(x, y)
|
||||||
|
_ambientAccumulator.maxAndAssign(darkenColoured(x2, y2, _thisTileOpacity))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_thisTileOpacity2.r = _mapThisTileOpacity2.getR(x, y)
|
||||||
|
_thisTileOpacity2.g = _mapThisTileOpacity2.getG(x, y)
|
||||||
|
_thisTileOpacity2.b = _mapThisTileOpacity2.getB(x, y)
|
||||||
|
_thisTileOpacity2.a = _mapThisTileOpacity2.getA(x, y)
|
||||||
|
_ambientAccumulator.maxAndAssign(darkenColoured(x2, y2, _thisTileOpacity2))
|
||||||
|
}
|
||||||
|
|
||||||
|
_mapLightLevelThis.setVec(x, y, _ambientAccumulator)
|
||||||
|
lightmap.setVec(x, y, _ambientAccumulator)
|
||||||
|
}
|
||||||
|
private fun swipeLight(sx: Int, sy: Int, ex: Int, ey: Int, dx: Int, dy: Int) {
|
||||||
|
swipeX = sx; swipeY = sy
|
||||||
|
while (swipeX*dx <= ex*dx && swipeY*dy <= ey*dy) {
|
||||||
|
// conduct the task #1
|
||||||
|
// spread towards the end
|
||||||
|
_swipeTask(swipeX, swipeY, swipeX-dx, swipeY-dy)
|
||||||
|
|
||||||
|
swipeX += dx
|
||||||
|
swipeY += dy
|
||||||
|
}
|
||||||
|
|
||||||
|
swipeX = ex; swipeY = ey
|
||||||
|
while (swipeX*dx >= sx*dx && swipeY*dy >= sy*dy) {
|
||||||
|
// conduct the task #2
|
||||||
|
// spread towards the start
|
||||||
|
_swipeTask(swipeX, swipeY, swipeX+dx, swipeY+dy)
|
||||||
|
|
||||||
|
swipeX -= dx
|
||||||
|
swipeY -= dy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Another YE OLDE light simulator
|
||||||
|
* Calculates the light simulation, using main lightmap as one of the input.
|
||||||
|
*/
|
||||||
|
/*private fun calculateAndAssign(lightmap: UnsafeCvecArray, worldX: Int, worldY: Int) {
|
||||||
|
|
||||||
|
//if (inNoopMask(worldX, worldY)) return
|
||||||
|
|
||||||
|
// O(9n) == O(n) where n is a size of the map
|
||||||
|
|
||||||
|
//getLightsAndShades(worldX, worldY)
|
||||||
|
|
||||||
|
val x = worldX.convX()
|
||||||
|
val y = worldY.convY()
|
||||||
|
|
||||||
|
// calculate ambient
|
||||||
|
/* + * + 0 4 1
|
||||||
|
* * @ * 6 @ 7
|
||||||
|
* + * + 2 5 3
|
||||||
|
* sample ambient for eight points and apply attenuation for those
|
||||||
|
* maxblend eight values and use it
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
// TODO getLightsAndShades is replaced with precalculate; change following codes accordingly!
|
||||||
|
_ambientAccumulator.r = _mapLightLevelThis.getR(x, y)
|
||||||
|
_ambientAccumulator.g = _mapLightLevelThis.getG(x, y)
|
||||||
|
_ambientAccumulator.b = _mapLightLevelThis.getB(x, y)
|
||||||
|
_ambientAccumulator.a = _mapLightLevelThis.getA(x, y)
|
||||||
|
|
||||||
|
_thisTileOpacity.r = _mapThisTileOpacity.getR(x, y)
|
||||||
|
_thisTileOpacity.g = _mapThisTileOpacity.getG(x, y)
|
||||||
|
_thisTileOpacity.b = _mapThisTileOpacity.getB(x, y)
|
||||||
|
_thisTileOpacity.a = _mapThisTileOpacity.getA(x, y)
|
||||||
|
|
||||||
|
_thisTileOpacity2.r = _mapThisTileOpacity2.getR(x, y)
|
||||||
|
_thisTileOpacity2.g = _mapThisTileOpacity2.getG(x, y)
|
||||||
|
_thisTileOpacity2.b = _mapThisTileOpacity2.getB(x, y)
|
||||||
|
_thisTileOpacity2.a = _mapThisTileOpacity2.getA(x, y)
|
||||||
|
|
||||||
|
// will "overwrite" what's there in the lightmap if it's the first pass
|
||||||
|
// takes about 2 ms on 6700K
|
||||||
|
/* + */_ambientAccumulator.maxAndAssign(darkenColoured(x - 1, y - 1, _thisTileOpacity2))
|
||||||
|
/* + */_ambientAccumulator.maxAndAssign(darkenColoured(x + 1, y - 1, _thisTileOpacity2))
|
||||||
|
/* + */_ambientAccumulator.maxAndAssign(darkenColoured(x - 1, y + 1, _thisTileOpacity2))
|
||||||
|
/* + */_ambientAccumulator.maxAndAssign(darkenColoured(x + 1, y + 1, _thisTileOpacity2))
|
||||||
|
/* * */_ambientAccumulator.maxAndAssign(darkenColoured(x, y - 1, _thisTileOpacity))
|
||||||
|
/* * */_ambientAccumulator.maxAndAssign(darkenColoured(x, y + 1, _thisTileOpacity))
|
||||||
|
/* * */_ambientAccumulator.maxAndAssign(darkenColoured(x - 1, y, _thisTileOpacity))
|
||||||
|
/* * */_ambientAccumulator.maxAndAssign(darkenColoured(x + 1, y, _thisTileOpacity))
|
||||||
|
|
||||||
|
lightmap.setVec(x, y, _ambientAccumulator)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
private fun isSolid(x: Int, y: Int): Float? { // ...so that they wouldn't appear too dark
|
||||||
|
if (!inBounds(x, y)) return null
|
||||||
|
|
||||||
|
// brighten if solid
|
||||||
|
return if (BlockCodex[world.getTileFromTerrain(x, y)].isSolid) 1.2f else 1f
|
||||||
|
}
|
||||||
|
|
||||||
|
var lightBuffer: Pixmap = Pixmap(1, 1, Pixmap.Format.RGBA8888)
|
||||||
|
|
||||||
|
private val colourNull = Cvec(0)
|
||||||
|
private val gdxColorNull = Color(0)
|
||||||
|
const val epsilon = 1f/1024f
|
||||||
|
|
||||||
|
private var _lightBufferAsTex: Texture = Texture(1, 1, Pixmap.Format.RGBA8888)
|
||||||
|
|
||||||
|
internal fun draw(): Texture {
|
||||||
|
|
||||||
|
// when shader is not used: 0.5 ms on 6700K
|
||||||
|
AppLoader.measureDebugTime("Renderer.LightToScreen") {
|
||||||
|
|
||||||
|
val this_x_start = for_draw_x_start
|
||||||
|
val this_y_start = for_draw_y_start
|
||||||
|
val this_x_end = for_draw_x_end
|
||||||
|
val this_y_end = for_draw_y_end
|
||||||
|
|
||||||
|
// wipe out beforehand. You DO need this
|
||||||
|
lightBuffer.blending = Pixmap.Blending.None // gonna overwrite (remove this line causes the world to go bit darker)
|
||||||
|
lightBuffer.setColor(0)
|
||||||
|
lightBuffer.fill()
|
||||||
|
|
||||||
|
|
||||||
|
// write to colour buffer
|
||||||
|
for (y in this_y_start..this_y_end) {
|
||||||
|
//println("y: $y, this_y_start: $this_y_start")
|
||||||
|
//if (y == this_y_start && this_y_start == 0) {
|
||||||
|
// throw Error("Fuck hits again...")
|
||||||
|
//}
|
||||||
|
|
||||||
|
for (x in this_x_start..this_x_end) {
|
||||||
|
|
||||||
|
val solidMultMagic = isSolid(x, y)
|
||||||
|
|
||||||
|
val arrayX = x.convX()
|
||||||
|
val arrayY = y.convY()
|
||||||
|
|
||||||
|
val red = lightmap.getR(arrayX, arrayY)
|
||||||
|
val grn = lightmap.getG(arrayX, arrayY)
|
||||||
|
val blu = lightmap.getB(arrayX, arrayY)
|
||||||
|
val uvl = lightmap.getA(arrayX, arrayY)
|
||||||
|
val redw = (red.sqrt() - 1f) * (7f / 24f)
|
||||||
|
val grnw = (grn.sqrt() - 1f)
|
||||||
|
val bluw = (blu.sqrt() - 1f) * (7f / 72f)
|
||||||
|
val bluwv = (blu.sqrt() - 1f) * (1f / 50f)
|
||||||
|
val uvlwr = (uvl.sqrt() - 1f) * (1f / 13f)
|
||||||
|
val uvlwg = (uvl.sqrt() - 1f) * (1f / 10f)
|
||||||
|
val uvlwb = (uvl.sqrt() - 1f) * (1f / 8f)
|
||||||
|
|
||||||
|
val color = if (solidMultMagic == null)
|
||||||
|
lightBuffer.drawPixel(
|
||||||
|
x - this_x_start,
|
||||||
|
lightBuffer.height - 1 - y + this_y_start, // flip Y
|
||||||
|
0
|
||||||
|
)
|
||||||
|
else
|
||||||
|
lightBuffer.drawPixel(
|
||||||
|
x - this_x_start,
|
||||||
|
lightBuffer.height - 1 - y + this_y_start, // flip Y
|
||||||
|
(maxOf(red,grnw,bluw,uvlwr) * solidMultMagic).hdnorm().times(255f).roundToInt().shl(24) or
|
||||||
|
(maxOf(redw,grn,bluw,uvlwg) * solidMultMagic).hdnorm().times(255f).roundToInt().shl(16) or
|
||||||
|
(maxOf(redw,grnw,blu,uvlwb) * solidMultMagic).hdnorm().times(255f).roundToInt().shl(8) or
|
||||||
|
(maxOf(bluwv,uvl) * solidMultMagic).hdnorm().times(255f).roundToInt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// draw to the batch
|
||||||
|
_lightBufferAsTex.dispose()
|
||||||
|
_lightBufferAsTex = Texture(lightBuffer)
|
||||||
|
_lightBufferAsTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
|
||||||
|
|
||||||
|
/*Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0) // so that batch that comes next will bind any tex to it
|
||||||
|
// we might not need shader here...
|
||||||
|
//batch.draw(lightBufferAsTex, 0f, 0f, lightBufferAsTex.width.toFloat(), lightBufferAsTex.height.toFloat())
|
||||||
|
batch.draw(_lightBufferAsTex, 0f, 0f, _lightBufferAsTex.width * DRAW_TILE_SIZE, _lightBufferAsTex.height * DRAW_TILE_SIZE)
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
return _lightBufferAsTex
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dispose() {
|
||||||
|
LightmapHDRMap.dispose()
|
||||||
|
_lightBufferAsTex.dispose()
|
||||||
|
lightBuffer.dispose()
|
||||||
|
|
||||||
|
lightmap.destroy()
|
||||||
|
_mapLightLevelThis.destroy()
|
||||||
|
_mapThisTileOpacity.destroy()
|
||||||
|
_mapThisTileOpacity2.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val lightScalingMagic = 2f
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtract each channel's RGB value.
|
||||||
|
*
|
||||||
|
* @param x array coord
|
||||||
|
* @param y array coord
|
||||||
|
* @param darken (0-255) per channel
|
||||||
|
* @return darkened data (0-255) per channel
|
||||||
|
*/
|
||||||
|
fun darkenColoured(x: Int, y: Int, darken: Cvec): Cvec {
|
||||||
|
// use equation with magic number 8.0
|
||||||
|
// this function, when done recursively (A_x = darken(A_x-1, C)), draws exponential curve. (R^2 = 1)
|
||||||
|
|
||||||
|
if (x !in 0 until LIGHTMAP_WIDTH || y !in 0 until LIGHTMAP_HEIGHT) return colourNull
|
||||||
|
|
||||||
|
return Cvec(
|
||||||
|
lightmap.getR(x, y) * (1f - darken.r * lightScalingMagic),
|
||||||
|
lightmap.getG(x, y) * (1f - darken.g * lightScalingMagic),
|
||||||
|
lightmap.getB(x, y) * (1f - darken.b * lightScalingMagic),
|
||||||
|
lightmap.getA(x, y) * (1f - darken.a * lightScalingMagic)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** infix is removed to clarify the association direction */
|
||||||
|
private fun Cvec.maxAndAssign(other: Cvec): Cvec {
|
||||||
|
// TODO investigate: if I use assignment instead of set(), it blackens like the vector branch. --Torvald, 2019-06-07
|
||||||
|
// that was because you forgot 'this.r/g/b/a = ' part, bitch. --Torvald, 2019-06-07
|
||||||
|
this.r = if (this.r > other.r) this.r else other.r
|
||||||
|
this.g = if (this.g > other.g) this.g else other.g
|
||||||
|
this.b = if (this.b > other.b) this.b else other.b
|
||||||
|
this.a = if (this.a > other.a) this.a else other.a
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Float.inv() = 1f / this
|
||||||
|
fun Int.even(): Boolean = this and 1 == 0
|
||||||
|
fun Int.odd(): Boolean = this and 1 == 1
|
||||||
|
|
||||||
|
// TODO: float LUT lookup using linear interpolation
|
||||||
|
|
||||||
|
// input: 0..1 for int 0..1023
|
||||||
|
fun hdr(intensity: Float): Float {
|
||||||
|
val intervalStart = (intensity / 4f * LightmapHDRMap.size).floorInt()
|
||||||
|
val intervalEnd = (intensity / 4f * LightmapHDRMap.size).floorInt() + 1
|
||||||
|
|
||||||
|
if (intervalStart == intervalEnd) return LightmapHDRMap[intervalStart]
|
||||||
|
|
||||||
|
val intervalPos = (intensity / 4f * LightmapHDRMap.size) - (intensity / 4f * LightmapHDRMap.size).toInt()
|
||||||
|
|
||||||
|
val ret = interpolateLinear(
|
||||||
|
intervalPos,
|
||||||
|
LightmapHDRMap[intervalStart],
|
||||||
|
LightmapHDRMap[intervalEnd]
|
||||||
|
)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _init = false
|
||||||
|
|
||||||
|
fun resize(screenW: Int, screenH: Int) {
|
||||||
|
// make sure the BlocksDrawer is resized first!
|
||||||
|
|
||||||
|
// copied from BlocksDrawer, duh!
|
||||||
|
// FIXME 'lightBuffer' is not zoomable in this way
|
||||||
|
val tilesInHorizontal = (AppLoader.screenSize.screenWf / TILE_SIZE).ceilInt() + 1
|
||||||
|
val tilesInVertical = (AppLoader.screenSize.screenHf / TILE_SIZE).ceilInt() + 1
|
||||||
|
|
||||||
|
LIGHTMAP_WIDTH = (Terrarum.ingame?.ZOOM_MINIMUM ?: 1f).inv().times(AppLoader.screenSize.screenW).div(TILE_SIZE).ceilInt() + overscan_open * 2 + 3
|
||||||
|
LIGHTMAP_HEIGHT = (Terrarum.ingame?.ZOOM_MINIMUM ?: 1f).inv().times(AppLoader.screenSize.screenH).div(TILE_SIZE).ceilInt() + overscan_open * 2 + 3
|
||||||
|
|
||||||
|
if (_init) {
|
||||||
|
lightBuffer.dispose()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_init = true
|
||||||
|
}
|
||||||
|
lightBuffer = Pixmap(tilesInHorizontal, tilesInVertical, Pixmap.Format.RGBA8888)
|
||||||
|
|
||||||
|
lightmap.destroy()
|
||||||
|
_mapLightLevelThis.destroy()
|
||||||
|
_mapThisTileOpacity.destroy()
|
||||||
|
_mapThisTileOpacity2.destroy()
|
||||||
|
lightmap = UnsafeCvecArray(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT)
|
||||||
|
_mapLightLevelThis = UnsafeCvecArray(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT)
|
||||||
|
_mapThisTileOpacity = UnsafeCvecArray(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT)
|
||||||
|
_mapThisTileOpacity2 = UnsafeCvecArray(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT)
|
||||||
|
|
||||||
|
printdbg(this, "Resize event")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** To eliminated visible edge on the gradient when 255/1023 is exceeded */
|
||||||
|
fun Color.normaliseToHDR() = Color(
|
||||||
|
hdr(this.r.coerceIn(0f, 1f)),
|
||||||
|
hdr(this.g.coerceIn(0f, 1f)),
|
||||||
|
hdr(this.b.coerceIn(0f, 1f)),
|
||||||
|
hdr(this.a.coerceIn(0f, 1f))
|
||||||
|
)
|
||||||
|
|
||||||
|
inline fun Float.hdnorm() = hdr(this.coerceIn(0f, 1f))
|
||||||
|
|
||||||
|
private fun Cvec.nonZero() = this.r.abs() > epsilon ||
|
||||||
|
this.g.abs() > epsilon ||
|
||||||
|
this.b.abs() > epsilon ||
|
||||||
|
this.a.abs() > epsilon
|
||||||
|
|
||||||
|
val histogram: Histogram
|
||||||
|
get() {
|
||||||
|
val reds = IntArray(256) // reds[intensity] ← counts
|
||||||
|
val greens = IntArray(256) // do.
|
||||||
|
val blues = IntArray(256) // do.
|
||||||
|
val uvs = IntArray(256)
|
||||||
|
val render_width = for_x_end - for_x_start
|
||||||
|
val render_height = for_y_end - for_y_start
|
||||||
|
// excluiding overscans; only reckon echo lights
|
||||||
|
for (y in overscan_open..render_height + overscan_open + 1) {
|
||||||
|
for (x in overscan_open..render_width + overscan_open + 1) {
|
||||||
|
try {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
catch (e: ArrayIndexOutOfBoundsException) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Histogram(reds, greens, blues, uvs)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Histogram(val reds: IntArray, val greens: IntArray, val blues: IntArray, val uvs: IntArray) {
|
||||||
|
|
||||||
|
val RED = 0
|
||||||
|
val GREEN = 1
|
||||||
|
val BLUE = 2
|
||||||
|
val UV = 3
|
||||||
|
|
||||||
|
val screen_tiles: Int = (for_x_end - for_x_start + 2) * (for_y_end - for_y_start + 2)
|
||||||
|
|
||||||
|
val brightest: Int
|
||||||
|
get() {
|
||||||
|
for (i in 255 downTo 1) {
|
||||||
|
if (reds[i] > 0 || greens[i] > 0 || blues[i] > 0)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val brightest8Bit: Int
|
||||||
|
get() { val b = brightest
|
||||||
|
return if (brightest > 255) 255 else b
|
||||||
|
}
|
||||||
|
|
||||||
|
val dimmest: Int
|
||||||
|
get() {
|
||||||
|
for (i in 0..255) {
|
||||||
|
if (reds[i] > 0 || greens[i] > 0 || blues[i] > 0)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return 255
|
||||||
|
}
|
||||||
|
|
||||||
|
val range: Int = 255
|
||||||
|
|
||||||
|
fun get(index: Int): IntArray {
|
||||||
|
return when (index) {
|
||||||
|
RED -> reds
|
||||||
|
GREEN -> greens
|
||||||
|
BLUE -> blues
|
||||||
|
UV -> uvs
|
||||||
|
else -> throw IllegalArgumentException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interpolateLinear(scale: Float, startValue: Float, endValue: Float): Float {
|
||||||
|
if (startValue == endValue) {
|
||||||
|
return startValue
|
||||||
|
}
|
||||||
|
if (scale <= 0f) {
|
||||||
|
return startValue
|
||||||
|
}
|
||||||
|
if (scale >= 1f) {
|
||||||
|
return endValue
|
||||||
|
}
|
||||||
|
return (1f - scale) * startValue + scale * endValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Cvec.toRGBA() = (255 * r).toInt() shl 24 or ((255 * g).toInt() shl 16) or ((255 * b).toInt() shl 8) or (255 * a).toInt()
|
||||||
|
fun Color.toRGBA() = (255 * r).toInt() shl 24 or ((255 * g).toInt() shl 16) or ((255 * b).toInt() shl 8) or (255 * a).toInt()
|
||||||
|
|
||||||
|
|||||||
@@ -1,961 +0,0 @@
|
|||||||
package net.torvald.terrarum.worlddrawer
|
|
||||||
|
|
||||||
import com.badlogic.gdx.graphics.Color
|
|
||||||
import com.badlogic.gdx.graphics.Pixmap
|
|
||||||
import com.badlogic.gdx.graphics.Texture
|
|
||||||
import net.torvald.gdx.graphics.Cvec
|
|
||||||
import net.torvald.gdx.graphics.UnsafeCvecArray
|
|
||||||
import net.torvald.terrarum.*
|
|
||||||
import net.torvald.terrarum.AppLoader.printdbg
|
|
||||||
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
|
|
||||||
import net.torvald.terrarum.blockproperties.Block
|
|
||||||
import net.torvald.terrarum.blockproperties.BlockCodex
|
|
||||||
import net.torvald.terrarum.blockproperties.Fluid
|
|
||||||
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.modulebasegame.ui.abs
|
|
||||||
import net.torvald.terrarum.realestate.LandUtil
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
import kotlin.system.exitProcess
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sub-portion of IngameRenderer. You are not supposed to directly deal with this.
|
|
||||||
*
|
|
||||||
* Created by minjaesong on 2016-01-25.
|
|
||||||
*/
|
|
||||||
|
|
||||||
//typealias RGB10 = Int
|
|
||||||
|
|
||||||
// NOTE: no Float16 on this thing: 67 kB of memory footage is totally acceptable
|
|
||||||
|
|
||||||
/** This object should not be called by yourself; must be only being used and manipulated by your
|
|
||||||
* own ingame renderer
|
|
||||||
*/
|
|
||||||
object LightmapRenderer {
|
|
||||||
|
|
||||||
/** World change is managed by IngameRenderer.setWorld() */
|
|
||||||
private var world: GameWorld = GameWorld.makeNullWorld()
|
|
||||||
|
|
||||||
//private lateinit var lightCalcShader: ShaderProgram
|
|
||||||
//private val SHADER_LIGHTING = AppLoader.getConfigBoolean("gpulightcalc")
|
|
||||||
|
|
||||||
/** do not call this yourself! Let your game renderer handle this! */
|
|
||||||
internal fun internalSetWorld(world: GameWorld) {
|
|
||||||
try {
|
|
||||||
if (this.world != world) {
|
|
||||||
printdbg(this, "World change detected -- old world: ${this.world.hashCode()}, new world: ${world.hashCode()}")
|
|
||||||
|
|
||||||
lightmap.zerofill()
|
|
||||||
_mapLightLevelThis.zerofill()
|
|
||||||
_mapThisTileOpacity.zerofill()
|
|
||||||
_mapThisTileOpacity2.zerofill()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e: UninitializedPropertyAccessException) {
|
|
||||||
// new init, do nothing
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
this.world = world
|
|
||||||
|
|
||||||
// fireRecalculateEvent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const val overscan_open: Int = 40
|
|
||||||
const val overscan_opaque: Int = 10
|
|
||||||
|
|
||||||
private var LIGHTMAP_WIDTH: Int = (Terrarum.ingame?.ZOOM_MINIMUM ?: 1f).inv().times(AppLoader.screenSize.screenW).div(TILE_SIZE).ceilInt() + overscan_open * 2 + 3
|
|
||||||
private var LIGHTMAP_HEIGHT: Int = (Terrarum.ingame?.ZOOM_MINIMUM ?: 1f).inv().times(AppLoader.screenSize.screenH).div(TILE_SIZE).ceilInt() + overscan_open * 2 + 3
|
|
||||||
|
|
||||||
//private val noopMask = HashSet<Point2i>((LIGHTMAP_WIDTH + LIGHTMAP_HEIGHT) * 2)
|
|
||||||
|
|
||||||
private val lanternMap = HashMap<BlockAddress, Cvec>((Terrarum.ingame?.ACTORCONTAINER_INITIAL_SIZE ?: 2) * 4)
|
|
||||||
/**
|
|
||||||
* Float value, 1.0 for 1023
|
|
||||||
*
|
|
||||||
* Note: using UnsafeCvecArray does not actually show great performance improvement
|
|
||||||
*/
|
|
||||||
// it utilises alpha channel to determine brightness of "glow" sprites (so that alpha channel works like UV light)
|
|
||||||
private var lightmap = UnsafeCvecArray(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT)
|
|
||||||
private var _mapLightLevelThis = UnsafeCvecArray(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT)
|
|
||||||
private var _mapThisTileOpacity = UnsafeCvecArray(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT)
|
|
||||||
private var _mapThisTileOpacity2 = UnsafeCvecArray(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT)
|
|
||||||
|
|
||||||
init {
|
|
||||||
LightmapHDRMap.invoke()
|
|
||||||
printdbg(this, "Overscan open: $overscan_open; opaque: $overscan_opaque")
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val AIR = Block.AIR
|
|
||||||
|
|
||||||
const val DRAW_TILE_SIZE: Float = TILE_SIZE / IngameRenderer.lightmapDownsample
|
|
||||||
|
|
||||||
internal var for_x_start = 0
|
|
||||||
internal var for_y_start = 0
|
|
||||||
internal var for_x_end = 0
|
|
||||||
internal var for_y_end = 0
|
|
||||||
internal var for_draw_x_start = 0
|
|
||||||
internal var for_draw_y_start = 0
|
|
||||||
internal var for_draw_x_end = 0
|
|
||||||
internal var for_draw_y_end = 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param x world coord
|
|
||||||
* @param y world coord
|
|
||||||
*/
|
|
||||||
private fun inBounds(x: Int, y: Int) =
|
|
||||||
(y - for_y_start + overscan_open in 0 until LIGHTMAP_HEIGHT &&
|
|
||||||
x - for_x_start + overscan_open in 0 until LIGHTMAP_WIDTH)
|
|
||||||
/** World coord to array coord */
|
|
||||||
private inline fun Int.convX() = this - for_x_start + overscan_open
|
|
||||||
/** World coord to array coord */
|
|
||||||
private inline fun Int.convY() = this - for_y_start + overscan_open
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Conventional level (multiplied by four)
|
|
||||||
*
|
|
||||||
* @param x world tile coord
|
|
||||||
* @param y world tile coord
|
|
||||||
*/
|
|
||||||
internal fun getLight(x: Int, y: Int): Cvec? {
|
|
||||||
return if (!inBounds(x, y)) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val x = x.convX()
|
|
||||||
val y = y.convY()
|
|
||||||
|
|
||||||
Cvec(
|
|
||||||
lightmap.getR(x, y),
|
|
||||||
lightmap.getG(x, y),
|
|
||||||
lightmap.getB(x, y),
|
|
||||||
lightmap.getA(x, y)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun fireRecalculateEvent(vararg actorContainers: List<ActorWithBody>?) {
|
|
||||||
try {
|
|
||||||
world.getTileFromTerrain(0, 0) // test inquiry
|
|
||||||
}
|
|
||||||
catch (e: UninitializedPropertyAccessException) {
|
|
||||||
return // quit prematurely
|
|
||||||
}
|
|
||||||
catch (e: NullPointerException) {
|
|
||||||
System.err.println("[LightmapRendererNew.fireRecalculateEvent] Attempted to refer destroyed unsafe array " +
|
|
||||||
"(${world.layerTerrain.ptr})")
|
|
||||||
e.printStackTrace()
|
|
||||||
return // something's wrong but we'll ignore it like a trustful AK
|
|
||||||
}
|
|
||||||
|
|
||||||
if (world.worldIndex == -1) return
|
|
||||||
|
|
||||||
|
|
||||||
for_x_start = WorldCamera.zoomedX / TILE_SIZE // fix for premature lightmap rendering
|
|
||||||
for_y_start = WorldCamera.zoomedY / TILE_SIZE // on topmost/leftmost side
|
|
||||||
for_draw_x_start = WorldCamera.x / TILE_SIZE
|
|
||||||
for_draw_y_start = WorldCamera.y / TILE_SIZE
|
|
||||||
|
|
||||||
if (WorldCamera.x < 0) for_draw_x_start -= 1 // edge case fix that light shift 1 tile to the left when WorldCamera.x < 0
|
|
||||||
//if (WorldCamera.x in -(TILE_SIZE - 1)..-1) for_draw_x_start -= 1 // another edge-case fix; we don't need this anymore?
|
|
||||||
|
|
||||||
for_x_end = for_x_start + WorldCamera.zoomedWidth / TILE_SIZE + 3
|
|
||||||
for_y_end = for_y_start + WorldCamera.zoomedHeight / TILE_SIZE + 3 // same fix as above
|
|
||||||
for_draw_x_end = for_draw_x_start + WorldCamera.width / TILE_SIZE + 3
|
|
||||||
for_draw_y_end = for_draw_y_start + WorldCamera.height / TILE_SIZE + 3
|
|
||||||
|
|
||||||
//println("$for_x_start..$for_x_end, $for_x\t$for_y_start..$for_y_end, $for_y")
|
|
||||||
|
|
||||||
AppLoader.measureDebugTime("Renderer.Lanterns") {
|
|
||||||
buildLanternmap(actorContainers)
|
|
||||||
} // usually takes 3000 ns
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Updating order:
|
|
||||||
* ,--------. ,--+-----. ,-----+--. ,--------. -
|
|
||||||
* |↘ | | | 3| |3 | | | ↙| ↕︎ overscan_open / overscan_opaque
|
|
||||||
* | ,-----+ | | 2 | | 2 | | +-----. | - depending on the noop_mask
|
|
||||||
* | |1 | | |1 | | 1| | | 1| |
|
|
||||||
* | | 2 | | `-----+ +-----' | | 2 | |
|
|
||||||
* | | 3| |↗ | | ↖| |3 | |
|
|
||||||
* `--+-----' `--------' `--------' `-----+--'
|
|
||||||
* round: 1 2 3 4
|
|
||||||
* for all lightmap[y][x], run in this order: 2-3-4-1
|
|
||||||
* If you run only 4 sets, orthogonal/diagonal artefacts are bound to occur,
|
|
||||||
*/
|
|
||||||
|
|
||||||
// set sunlight
|
|
||||||
sunLight.set(world.globalLight)
|
|
||||||
|
|
||||||
// set no-op mask from solidity of the block
|
|
||||||
/*AppLoader.measureDebugTime("Renderer.LightNoOpMask") {
|
|
||||||
noopMask.clear()
|
|
||||||
buildNoopMask()
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// wipe out lightmap
|
|
||||||
AppLoader.measureDebugTime("Renderer.LightPrecalc") {
|
|
||||||
// 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()
|
|
||||||
_mapLightLevelThis.zerofill()
|
|
||||||
//lightsourceMap.clear()
|
|
||||||
|
|
||||||
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) {
|
|
||||||
precalculate(x, y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// YE OLDE LIGHT UPDATER
|
|
||||||
// O((5*9)n where n is a size of the map.
|
|
||||||
// Because of inevitable overlaps on the area, it only works with MAX blend
|
|
||||||
/*fun or1() {
|
|
||||||
// Round 1
|
|
||||||
for (y in for_y_start - overscan_open..for_y_end) {
|
|
||||||
for (x in for_x_start - overscan_open..for_x_end) {
|
|
||||||
calculateAndAssign(lightmap, x, y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun or2() {
|
|
||||||
// Round 2
|
|
||||||
for (y in for_y_end + overscan_open downTo for_y_start) {
|
|
||||||
for (x in for_x_start - overscan_open..for_x_end) {
|
|
||||||
calculateAndAssign(lightmap, x, y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun or3() {
|
|
||||||
// Round 3
|
|
||||||
for (y in for_y_end + overscan_open downTo for_y_start) {
|
|
||||||
for (x in for_x_end + overscan_open downTo for_x_start) {
|
|
||||||
calculateAndAssign(lightmap, x, y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun or4() {
|
|
||||||
// Round 4
|
|
||||||
for (y in for_y_start - overscan_open..for_y_end) {
|
|
||||||
for (x in for_x_end + overscan_open downTo for_x_start) {
|
|
||||||
calculateAndAssign(lightmap, x, y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// 'NEWLIGHT2' LIGHT SWIPER
|
|
||||||
// O((8*2)n) where n is a size of the map.
|
|
||||||
fun r1() {
|
|
||||||
// TODO test non-parallel
|
|
||||||
swipeDiag = false
|
|
||||||
for (line in 1 until LIGHTMAP_HEIGHT - 1) {
|
|
||||||
swipeLight(
|
|
||||||
1, line,
|
|
||||||
LIGHTMAP_WIDTH - 2, line,
|
|
||||||
1, 0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun r2() {
|
|
||||||
// TODO test non-parallel
|
|
||||||
swipeDiag = false
|
|
||||||
for (line in 1 until LIGHTMAP_WIDTH - 1) {
|
|
||||||
swipeLight(
|
|
||||||
line, 1,
|
|
||||||
line, LIGHTMAP_HEIGHT - 2,
|
|
||||||
0, 1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun r3() {
|
|
||||||
// TODO test non-parallel
|
|
||||||
swipeDiag = true
|
|
||||||
/* construct indices such that:
|
|
||||||
56789ABC
|
|
||||||
4 1 w-2
|
|
||||||
3 \---\---+
|
|
||||||
2 \\···\··|
|
|
||||||
1 \\\···\·|
|
|
||||||
0 \\\\···\|
|
|
||||||
h-2 \\\\\---\
|
|
||||||
|
|
||||||
0 (1, h-2) -> (1, h-2)
|
|
||||||
1 (1, h-2-1) -> (2, h-2)
|
|
||||||
2 (1, h-2-2) -> (3, h-2)
|
|
||||||
3 (1, h-2-3) -> (4, h-2)
|
|
||||||
4 (1, 1) -> (5, h-2)
|
|
||||||
|
|
||||||
5 (2, 1) -> (6, h-2)
|
|
||||||
6 (3, 1) -> (7, h-2)
|
|
||||||
7 (4, 1) -> (8, h-2)
|
|
||||||
8 (5, 1) -> (w-2, h-2)
|
|
||||||
|
|
||||||
9 (6, 1) -> (w-2, h-2-1)
|
|
||||||
10 (7, 1) -> (w-2, h-2-2)
|
|
||||||
11 (8, 1) -> (w-2, h-2-3)
|
|
||||||
12 (w-2, 1) -> (w-2, 1)
|
|
||||||
|
|
||||||
number of indices: internal_width + internal_height - 1
|
|
||||||
*/
|
|
||||||
for (i in 0 until LIGHTMAP_WIDTH + LIGHTMAP_HEIGHT - 5) {
|
|
||||||
swipeLight(
|
|
||||||
maxOf(1, i - LIGHTMAP_HEIGHT + 4), maxOf(1, LIGHTMAP_HEIGHT - 2 - i),
|
|
||||||
minOf(LIGHTMAP_WIDTH - 2, i + 1), minOf(LIGHTMAP_HEIGHT - 2, (LIGHTMAP_WIDTH + LIGHTMAP_HEIGHT - 5) - i),
|
|
||||||
1, 1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun r4() {
|
|
||||||
// TODO test non-parallel
|
|
||||||
swipeDiag = true
|
|
||||||
/*
|
|
||||||
1 w-2
|
|
||||||
/////---/
|
|
||||||
////···/|
|
|
||||||
///···/·|
|
|
||||||
//···/··|
|
|
||||||
h-2 /---/---+
|
|
||||||
d:(1,-1)
|
|
||||||
|
|
||||||
0 (1, 1) -> (1, 1)
|
|
||||||
1 (1, 2) -> (2, 1)
|
|
||||||
2 (1, 3) -> (3, 1)
|
|
||||||
3 (1, 4) -> (4, 1)
|
|
||||||
4 (1, h-2) -> (5, 1)
|
|
||||||
5 (2, h-2) -> (6, 1)
|
|
||||||
6 (3, h-2) -> (7, 1)
|
|
||||||
7 (4, h-2) -> (8, 1)
|
|
||||||
8 (5, h-2) -> (w-2, 1)
|
|
||||||
9 (6, h-2) -> (w-2, 2)
|
|
||||||
10 (7, h-2) -> (w-2, 3)
|
|
||||||
11 (8, h-2) -> (w-2, 4)
|
|
||||||
12 (w-2, h-2) -> (w-2, h-2)
|
|
||||||
*/
|
|
||||||
for (i in 0 until LIGHTMAP_WIDTH + LIGHTMAP_HEIGHT - 5) {
|
|
||||||
swipeLight(
|
|
||||||
maxOf(1, i - LIGHTMAP_HEIGHT + 4), minOf(LIGHTMAP_HEIGHT - 2, i + 1),
|
|
||||||
minOf(LIGHTMAP_WIDTH - 2, i + 1), maxOf(1, (LIGHTMAP_HEIGHT - 2) - (LIGHTMAP_WIDTH + LIGHTMAP_HEIGHT - 6) + i),
|
|
||||||
1, -1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// each usually takes 8..12 ms total when not threaded
|
|
||||||
// - with direct memory access of world array and pre-calculating things in the start of the frame,
|
|
||||||
// I was able to pull out 3.5..5.5 ms! With abhorrently many occurrences of segfaults I had to track down...
|
|
||||||
// - with 'NEWLIGHT2', I was able to pull ~2 ms!
|
|
||||||
//
|
|
||||||
// multithreading - forget about it; overhead is way too big and for some reason i was not able to
|
|
||||||
// resolve the 'noisy shit' artefact
|
|
||||||
AppLoader.measureDebugTime("Renderer.LightRuns") {
|
|
||||||
|
|
||||||
// To save you from pains:
|
|
||||||
// - Per-channel light updating is actually slower
|
|
||||||
// BELOW NOTES DOES NOT APPLY TO 'NEWLIGHT2' LIGHT SWIPER
|
|
||||||
// - It seems 5-pass lighting is needed to resonably eliminate the dark spot (of which I have zero idea
|
|
||||||
// why dark spots appear in the first place)
|
|
||||||
// - Multithreading? I have absolutely no idea.
|
|
||||||
// - If you naively slice the screen (job area) to multithread, the seam will appear.
|
|
||||||
|
|
||||||
r1();r2();r3();r4()
|
|
||||||
r1();r2();r3();r4() // two looks better than one
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildLanternmap(actorContainers: Array<out List<ActorWithBody>?>) {
|
|
||||||
lanternMap.clear()
|
|
||||||
actorContainers.forEach { actorContainer ->
|
|
||||||
actorContainer?.forEach {
|
|
||||||
if (it is Luminous) {
|
|
||||||
// put lanterns to the area the luminantBox is occupying
|
|
||||||
for (lightBox in it.lightBoxList) {
|
|
||||||
val lightBoxX = it.hitbox.startX + lightBox.startX
|
|
||||||
val lightBoxY = it.hitbox.startY + lightBox.startY
|
|
||||||
val lightBoxW = lightBox.width
|
|
||||||
val lightBoxH = lightBox.height
|
|
||||||
for (y in lightBoxY.div(TILE_SIZE).floorInt()
|
|
||||||
..lightBoxY.plus(lightBoxH).div(TILE_SIZE).floorInt()) {
|
|
||||||
for (x in lightBoxX.div(TILE_SIZE).floorInt()
|
|
||||||
..lightBoxX.plus(lightBoxW).div(TILE_SIZE).floorInt()) {
|
|
||||||
|
|
||||||
val normalisedCvec = it.color//.cpy().mul(DIV_FLOAT)
|
|
||||||
|
|
||||||
lanternMap[LandUtil.getBlockAddr(world, x, y)] = normalisedCvec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*private fun buildNoopMask() {
|
|
||||||
fun isShaded(x: Int, y: Int) = try {
|
|
||||||
BlockCodex[world.getTileFromTerrain(x, y)].isSolid
|
|
||||||
}
|
|
||||||
catch (e: NullPointerException) {
|
|
||||||
System.err.println("[LightmapRendererNew.buildNoopMask] Invalid block id ${world.getTileFromTerrain(x, y)} from coord ($x, $y)")
|
|
||||||
e.printStackTrace()
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
update ordering: clockwise snake
|
|
||||||
|
|
||||||
for_x_start
|
|
||||||
|
|
|
||||||
02468>..............|--for_y_start
|
|
||||||
: :
|
|
||||||
: :
|
|
||||||
: :
|
|
||||||
V V
|
|
||||||
13579>............../--for_y_end
|
|
||||||
|
|
|
||||||
for_x_end
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
for (x in for_x_start..for_x_end) {
|
|
||||||
if (isShaded(x, for_y_start)) noopMask.add(Point2i(x, for_y_start))
|
|
||||||
if (isShaded(x, for_y_end)) noopMask.add(Point2i(x, for_y_end))
|
|
||||||
}
|
|
||||||
for (y in for_y_start + 1..for_y_end - 1) {
|
|
||||||
if (isShaded(for_x_start, y)) noopMask.add(Point2i(for_x_start, y))
|
|
||||||
if (isShaded(for_x_end, y)) noopMask.add(Point2i(for_x_end, y))
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// local variables that are made static
|
|
||||||
private val sunLight = Cvec(0)
|
|
||||||
private var _thisTerrain = 0
|
|
||||||
private var _thisFluid = GameWorld.FluidInfo(Fluid.NULL, 0f)
|
|
||||||
private var _thisWall = 0
|
|
||||||
private val _ambientAccumulator = Cvec(0)
|
|
||||||
private val _thisTileOpacity = Cvec(0)
|
|
||||||
private val _thisTileOpacity2 = Cvec(0) // thisTileOpacity * sqrt(2)
|
|
||||||
private val _fluidAmountToCol = Cvec(0)
|
|
||||||
private val _thisTileLuminosity = Cvec(0)
|
|
||||||
|
|
||||||
fun precalculate(rawx: Int, rawy: Int) {
|
|
||||||
val lx = rawx.convX(); val ly = rawy.convY()
|
|
||||||
val (worldX, worldY) = world.coerceXY(rawx, rawy)
|
|
||||||
|
|
||||||
//printdbg(this, "precalculate ($rawx, $rawy) -> ($lx, $ly) | ($LIGHTMAP_WIDTH, $LIGHTMAP_HEIGHT)")
|
|
||||||
|
|
||||||
if (lx !in 0..LIGHTMAP_WIDTH || ly !in 0..LIGHTMAP_HEIGHT) {
|
|
||||||
println("[LightmapRendererNew.precalculate] Out of range: ($lx, $ly) for size ($LIGHTMAP_WIDTH, $LIGHTMAP_HEIGHT)")
|
|
||||||
exitProcess(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_thisTerrain = world.getTileFromTerrainRaw(worldX, worldY)
|
|
||||||
_thisFluid = world.getFluid(worldX, worldY)
|
|
||||||
_thisWall = world.getTileFromWallRaw(worldX, worldY)
|
|
||||||
|
|
||||||
|
|
||||||
// regarding the issue #26
|
|
||||||
// uncomment this and/or run JVM with -ea if you're facing diabolically indescribable bugs
|
|
||||||
/*try {
|
|
||||||
val fuck = BlockCodex[_thisTerrain].getLumCol(worldX, worldY)
|
|
||||||
}
|
|
||||||
catch (e: NullPointerException) {
|
|
||||||
System.err.println("## NPE -- x: $worldX, y: $worldY, value: $_thisTerrain")
|
|
||||||
e.printStackTrace()
|
|
||||||
// create shitty minidump
|
|
||||||
System.err.println("MINIMINIDUMP START")
|
|
||||||
for (xx in worldX - 16 until worldX + 16) {
|
|
||||||
val raw = world.getTileFromTerrain(xx, worldY)
|
|
||||||
val lsb = raw.and(0xff).toString(16).padStart(2, '0')
|
|
||||||
val msb = raw.ushr(8).and(0xff).toString(16).padStart(2, '0')
|
|
||||||
System.err.print(lsb)
|
|
||||||
System.err.print(msb)
|
|
||||||
System.err.print(" ")
|
|
||||||
}
|
|
||||||
System.err.println("\nMINIMINIDUMP END")
|
|
||||||
|
|
||||||
exitProcess(1)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
if (_thisFluid.type != Fluid.NULL) {
|
|
||||||
_fluidAmountToCol.set(_thisFluid.amount, _thisFluid.amount, _thisFluid.amount, _thisFluid.amount)
|
|
||||||
|
|
||||||
_thisTileLuminosity.set(BlockCodex[world.tileNumberToNameMap[_thisTerrain]].getLumCol(worldX, worldY))
|
|
||||||
_thisTileLuminosity.maxAndAssign(BlockCodex[_thisFluid.type].getLumCol(worldX, worldY).mul(_fluidAmountToCol)) // already been div by four
|
|
||||||
_mapThisTileOpacity.setVec(lx, ly, BlockCodex[world.tileNumberToNameMap[_thisTerrain]].opacity)
|
|
||||||
_mapThisTileOpacity.max(lx, ly, BlockCodex[_thisFluid.type].opacity.mul(_fluidAmountToCol))// already been div by four
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
_thisTileLuminosity.set(BlockCodex[world.tileNumberToNameMap[_thisTerrain]].getLumCol(worldX, worldY))
|
|
||||||
_mapThisTileOpacity.setVec(lx, ly, BlockCodex[world.tileNumberToNameMap[_thisTerrain]].opacity)
|
|
||||||
}
|
|
||||||
|
|
||||||
_mapThisTileOpacity2.setR(lx, ly, _mapThisTileOpacity.getR(lx, ly) * 1.41421356f)
|
|
||||||
_mapThisTileOpacity2.setG(lx, ly, _mapThisTileOpacity.getG(lx, ly) * 1.41421356f)
|
|
||||||
_mapThisTileOpacity2.setB(lx, ly, _mapThisTileOpacity.getB(lx, ly) * 1.41421356f)
|
|
||||||
_mapThisTileOpacity2.setA(lx, ly, _mapThisTileOpacity.getA(lx, ly) * 1.41421356f)
|
|
||||||
|
|
||||||
|
|
||||||
// open air || luminous tile backed by sunlight
|
|
||||||
if ((world.tileNumberToNameMap[_thisTerrain] == AIR && world.tileNumberToNameMap[_thisWall] == AIR) ||
|
|
||||||
(_thisTileLuminosity.nonZero() && world.tileNumberToNameMap[_thisWall] == AIR)) {
|
|
||||||
_mapLightLevelThis.setVec(lx, ly, sunLight)
|
|
||||||
}
|
|
||||||
|
|
||||||
// blend lantern
|
|
||||||
_mapLightLevelThis.max(lx, ly, _thisTileLuminosity.maxAndAssign(
|
|
||||||
lanternMap[LandUtil.getBlockAddr(world, worldX, worldY)] ?: colourNull
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/*private val inNoopMaskp = Point2i(0,0)
|
|
||||||
|
|
||||||
private fun inNoopMask(x: Int, y: Int): Boolean {
|
|
||||||
if (x in for_x_start..for_x_end) {
|
|
||||||
// if it's in the top flange
|
|
||||||
inNoopMaskp.set(x, for_y_start)
|
|
||||||
if (y < for_y_start - overscan_opaque && noopMask.contains(inNoopMaskp)) return true
|
|
||||||
// if it's in the bottom flange
|
|
||||||
inNoopMaskp.y = for_y_end
|
|
||||||
return (y > for_y_end + overscan_opaque && noopMask.contains(inNoopMaskp))
|
|
||||||
}
|
|
||||||
else if (y in for_y_start..for_y_end) {
|
|
||||||
// if it's in the left flange
|
|
||||||
inNoopMaskp.set(for_x_start, y)
|
|
||||||
if (x < for_x_start - overscan_opaque && noopMask.contains(inNoopMaskp)) return true
|
|
||||||
// if it's in the right flange
|
|
||||||
inNoopMaskp.set(for_x_end, y)
|
|
||||||
return (x > for_x_end + overscan_opaque && noopMask.contains(inNoopMaskp))
|
|
||||||
}
|
|
||||||
// top-left corner
|
|
||||||
else if (x < for_x_start && y < for_y_start) {
|
|
||||||
inNoopMaskp.set(for_x_start, for_y_start)
|
|
||||||
return (x < for_x_start - overscan_opaque && y < for_y_start - overscan_opaque && noopMask.contains(inNoopMaskp))
|
|
||||||
}
|
|
||||||
// top-right corner
|
|
||||||
else if (x > for_x_end && y < for_y_start) {
|
|
||||||
inNoopMaskp.set(for_x_end, for_y_start)
|
|
||||||
return (x > for_x_end + overscan_opaque && y < for_y_start - overscan_opaque && noopMask.contains(inNoopMaskp))
|
|
||||||
}
|
|
||||||
// bottom-left corner
|
|
||||||
else if (x < for_x_start && y > for_y_end) {
|
|
||||||
inNoopMaskp.set(for_x_start, for_y_end)
|
|
||||||
return (x < for_x_start - overscan_opaque && y > for_y_end + overscan_opaque && noopMask.contains(inNoopMaskp))
|
|
||||||
}
|
|
||||||
// bottom-right corner
|
|
||||||
else if (x > for_x_end && y > for_y_end) {
|
|
||||||
inNoopMaskp.set(for_x_end, for_y_end)
|
|
||||||
return (x > for_x_end + overscan_opaque && y > for_y_end + overscan_opaque && noopMask.contains(inNoopMaskp))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
|
|
||||||
// if your IDE error out that you need return statement, AND it's "fixed" by removing 'else' before 'return false',
|
|
||||||
// you're doing it wrong, the IF and return statements must be inclusive.
|
|
||||||
}*/
|
|
||||||
|
|
||||||
private var swipeX = -1
|
|
||||||
private var swipeY = -1
|
|
||||||
private var swipeDiag = false
|
|
||||||
private fun _swipeTask(x: Int, y: Int, x2: Int, y2: Int) {
|
|
||||||
if (x2 < 0 || y2 < 0 || x2 >= LIGHTMAP_WIDTH || y2 >= LIGHTMAP_HEIGHT) return
|
|
||||||
|
|
||||||
_ambientAccumulator.r = _mapLightLevelThis.getR(x, y)
|
|
||||||
_ambientAccumulator.g = _mapLightLevelThis.getG(x, y)
|
|
||||||
_ambientAccumulator.b = _mapLightLevelThis.getB(x, y)
|
|
||||||
_ambientAccumulator.a = _mapLightLevelThis.getA(x, y)
|
|
||||||
|
|
||||||
if (!swipeDiag) {
|
|
||||||
_thisTileOpacity.r = _mapThisTileOpacity.getR(x, y)
|
|
||||||
_thisTileOpacity.g = _mapThisTileOpacity.getG(x, y)
|
|
||||||
_thisTileOpacity.b = _mapThisTileOpacity.getB(x, y)
|
|
||||||
_thisTileOpacity.a = _mapThisTileOpacity.getA(x, y)
|
|
||||||
_ambientAccumulator.maxAndAssign(darkenColoured(x2, y2, _thisTileOpacity))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
_thisTileOpacity2.r = _mapThisTileOpacity2.getR(x, y)
|
|
||||||
_thisTileOpacity2.g = _mapThisTileOpacity2.getG(x, y)
|
|
||||||
_thisTileOpacity2.b = _mapThisTileOpacity2.getB(x, y)
|
|
||||||
_thisTileOpacity2.a = _mapThisTileOpacity2.getA(x, y)
|
|
||||||
_ambientAccumulator.maxAndAssign(darkenColoured(x2, y2, _thisTileOpacity2))
|
|
||||||
}
|
|
||||||
|
|
||||||
_mapLightLevelThis.setVec(x, y, _ambientAccumulator)
|
|
||||||
lightmap.setVec(x, y, _ambientAccumulator)
|
|
||||||
}
|
|
||||||
private fun swipeLight(sx: Int, sy: Int, ex: Int, ey: Int, dx: Int, dy: Int) {
|
|
||||||
swipeX = sx; swipeY = sy
|
|
||||||
while (swipeX*dx <= ex*dx && swipeY*dy <= ey*dy) {
|
|
||||||
// conduct the task #1
|
|
||||||
// spread towards the end
|
|
||||||
_swipeTask(swipeX, swipeY, swipeX-dx, swipeY-dy)
|
|
||||||
|
|
||||||
swipeX += dx
|
|
||||||
swipeY += dy
|
|
||||||
}
|
|
||||||
|
|
||||||
swipeX = ex; swipeY = ey
|
|
||||||
while (swipeX*dx >= sx*dx && swipeY*dy >= sy*dy) {
|
|
||||||
// conduct the task #2
|
|
||||||
// spread towards the start
|
|
||||||
_swipeTask(swipeX, swipeY, swipeX+dx, swipeY+dy)
|
|
||||||
|
|
||||||
swipeX -= dx
|
|
||||||
swipeY -= dy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Another YE OLDE light simulator
|
|
||||||
* Calculates the light simulation, using main lightmap as one of the input.
|
|
||||||
*/
|
|
||||||
/*private fun calculateAndAssign(lightmap: UnsafeCvecArray, worldX: Int, worldY: Int) {
|
|
||||||
|
|
||||||
//if (inNoopMask(worldX, worldY)) return
|
|
||||||
|
|
||||||
// O(9n) == O(n) where n is a size of the map
|
|
||||||
|
|
||||||
//getLightsAndShades(worldX, worldY)
|
|
||||||
|
|
||||||
val x = worldX.convX()
|
|
||||||
val y = worldY.convY()
|
|
||||||
|
|
||||||
// calculate ambient
|
|
||||||
/* + * + 0 4 1
|
|
||||||
* * @ * 6 @ 7
|
|
||||||
* + * + 2 5 3
|
|
||||||
* sample ambient for eight points and apply attenuation for those
|
|
||||||
* maxblend eight values and use it
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
// TODO getLightsAndShades is replaced with precalculate; change following codes accordingly!
|
|
||||||
_ambientAccumulator.r = _mapLightLevelThis.getR(x, y)
|
|
||||||
_ambientAccumulator.g = _mapLightLevelThis.getG(x, y)
|
|
||||||
_ambientAccumulator.b = _mapLightLevelThis.getB(x, y)
|
|
||||||
_ambientAccumulator.a = _mapLightLevelThis.getA(x, y)
|
|
||||||
|
|
||||||
_thisTileOpacity.r = _mapThisTileOpacity.getR(x, y)
|
|
||||||
_thisTileOpacity.g = _mapThisTileOpacity.getG(x, y)
|
|
||||||
_thisTileOpacity.b = _mapThisTileOpacity.getB(x, y)
|
|
||||||
_thisTileOpacity.a = _mapThisTileOpacity.getA(x, y)
|
|
||||||
|
|
||||||
_thisTileOpacity2.r = _mapThisTileOpacity2.getR(x, y)
|
|
||||||
_thisTileOpacity2.g = _mapThisTileOpacity2.getG(x, y)
|
|
||||||
_thisTileOpacity2.b = _mapThisTileOpacity2.getB(x, y)
|
|
||||||
_thisTileOpacity2.a = _mapThisTileOpacity2.getA(x, y)
|
|
||||||
|
|
||||||
// will "overwrite" what's there in the lightmap if it's the first pass
|
|
||||||
// takes about 2 ms on 6700K
|
|
||||||
/* + */_ambientAccumulator.maxAndAssign(darkenColoured(x - 1, y - 1, _thisTileOpacity2))
|
|
||||||
/* + */_ambientAccumulator.maxAndAssign(darkenColoured(x + 1, y - 1, _thisTileOpacity2))
|
|
||||||
/* + */_ambientAccumulator.maxAndAssign(darkenColoured(x - 1, y + 1, _thisTileOpacity2))
|
|
||||||
/* + */_ambientAccumulator.maxAndAssign(darkenColoured(x + 1, y + 1, _thisTileOpacity2))
|
|
||||||
/* * */_ambientAccumulator.maxAndAssign(darkenColoured(x, y - 1, _thisTileOpacity))
|
|
||||||
/* * */_ambientAccumulator.maxAndAssign(darkenColoured(x, y + 1, _thisTileOpacity))
|
|
||||||
/* * */_ambientAccumulator.maxAndAssign(darkenColoured(x - 1, y, _thisTileOpacity))
|
|
||||||
/* * */_ambientAccumulator.maxAndAssign(darkenColoured(x + 1, y, _thisTileOpacity))
|
|
||||||
|
|
||||||
lightmap.setVec(x, y, _ambientAccumulator)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
private fun isSolid(x: Int, y: Int): Float? { // ...so that they wouldn't appear too dark
|
|
||||||
if (!inBounds(x, y)) return null
|
|
||||||
|
|
||||||
// brighten if solid
|
|
||||||
return if (BlockCodex[world.getTileFromTerrain(x, y)].isSolid) 1.2f else 1f
|
|
||||||
}
|
|
||||||
|
|
||||||
var lightBuffer: Pixmap = Pixmap(1, 1, Pixmap.Format.RGBA8888)
|
|
||||||
|
|
||||||
private val colourNull = Cvec(0)
|
|
||||||
private val gdxColorNull = Color(0)
|
|
||||||
const val epsilon = 1f/1024f
|
|
||||||
|
|
||||||
private var _lightBufferAsTex: Texture = Texture(1, 1, Pixmap.Format.RGBA8888)
|
|
||||||
|
|
||||||
internal fun draw(): Texture {
|
|
||||||
|
|
||||||
// when shader is not used: 0.5 ms on 6700K
|
|
||||||
AppLoader.measureDebugTime("Renderer.LightToScreen") {
|
|
||||||
|
|
||||||
val this_x_start = for_draw_x_start
|
|
||||||
val this_y_start = for_draw_y_start
|
|
||||||
val this_x_end = for_draw_x_end
|
|
||||||
val this_y_end = for_draw_y_end
|
|
||||||
|
|
||||||
// wipe out beforehand. You DO need this
|
|
||||||
lightBuffer.blending = Pixmap.Blending.None // gonna overwrite (remove this line causes the world to go bit darker)
|
|
||||||
lightBuffer.setColor(0)
|
|
||||||
lightBuffer.fill()
|
|
||||||
|
|
||||||
|
|
||||||
// write to colour buffer
|
|
||||||
for (y in this_y_start..this_y_end) {
|
|
||||||
//println("y: $y, this_y_start: $this_y_start")
|
|
||||||
//if (y == this_y_start && this_y_start == 0) {
|
|
||||||
// throw Error("Fuck hits again...")
|
|
||||||
//}
|
|
||||||
|
|
||||||
for (x in this_x_start..this_x_end) {
|
|
||||||
|
|
||||||
val solidMultMagic = isSolid(x, y)
|
|
||||||
|
|
||||||
val arrayX = x.convX()
|
|
||||||
val arrayY = y.convY()
|
|
||||||
|
|
||||||
val red = lightmap.getR(arrayX, arrayY)
|
|
||||||
val grn = lightmap.getG(arrayX, arrayY)
|
|
||||||
val blu = lightmap.getB(arrayX, arrayY)
|
|
||||||
val uvl = lightmap.getA(arrayX, arrayY)
|
|
||||||
val redw = (red.sqrt() - 1f) * (7f / 24f)
|
|
||||||
val grnw = (grn.sqrt() - 1f)
|
|
||||||
val bluw = (blu.sqrt() - 1f) * (7f / 72f)
|
|
||||||
val bluwv = (blu.sqrt() - 1f) * (1f / 50f)
|
|
||||||
val uvlwr = (uvl.sqrt() - 1f) * (1f / 13f)
|
|
||||||
val uvlwg = (uvl.sqrt() - 1f) * (1f / 10f)
|
|
||||||
val uvlwb = (uvl.sqrt() - 1f) * (1f / 8f)
|
|
||||||
|
|
||||||
val color = if (solidMultMagic == null)
|
|
||||||
lightBuffer.drawPixel(
|
|
||||||
x - this_x_start,
|
|
||||||
lightBuffer.height - 1 - y + this_y_start, // flip Y
|
|
||||||
0
|
|
||||||
)
|
|
||||||
else
|
|
||||||
lightBuffer.drawPixel(
|
|
||||||
x - this_x_start,
|
|
||||||
lightBuffer.height - 1 - y + this_y_start, // flip Y
|
|
||||||
(maxOf(red,grnw,bluw,uvlwr) * solidMultMagic).hdnorm().times(255f).roundToInt().shl(24) or
|
|
||||||
(maxOf(redw,grn,bluw,uvlwg) * solidMultMagic).hdnorm().times(255f).roundToInt().shl(16) or
|
|
||||||
(maxOf(redw,grnw,blu,uvlwb) * solidMultMagic).hdnorm().times(255f).roundToInt().shl(8) or
|
|
||||||
(maxOf(bluwv,uvl) * solidMultMagic).hdnorm().times(255f).roundToInt()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// draw to the batch
|
|
||||||
_lightBufferAsTex.dispose()
|
|
||||||
_lightBufferAsTex = Texture(lightBuffer)
|
|
||||||
_lightBufferAsTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
|
|
||||||
|
|
||||||
/*Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0) // so that batch that comes next will bind any tex to it
|
|
||||||
// we might not need shader here...
|
|
||||||
//batch.draw(lightBufferAsTex, 0f, 0f, lightBufferAsTex.width.toFloat(), lightBufferAsTex.height.toFloat())
|
|
||||||
batch.draw(_lightBufferAsTex, 0f, 0f, _lightBufferAsTex.width * DRAW_TILE_SIZE, _lightBufferAsTex.height * DRAW_TILE_SIZE)
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
return _lightBufferAsTex
|
|
||||||
}
|
|
||||||
|
|
||||||
fun dispose() {
|
|
||||||
LightmapHDRMap.dispose()
|
|
||||||
_lightBufferAsTex.dispose()
|
|
||||||
lightBuffer.dispose()
|
|
||||||
|
|
||||||
lightmap.destroy()
|
|
||||||
_mapLightLevelThis.destroy()
|
|
||||||
_mapThisTileOpacity.destroy()
|
|
||||||
_mapThisTileOpacity2.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val lightScalingMagic = 2f
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subtract each channel's RGB value.
|
|
||||||
*
|
|
||||||
* @param x array coord
|
|
||||||
* @param y array coord
|
|
||||||
* @param darken (0-255) per channel
|
|
||||||
* @return darkened data (0-255) per channel
|
|
||||||
*/
|
|
||||||
fun darkenColoured(x: Int, y: Int, darken: Cvec): Cvec {
|
|
||||||
// use equation with magic number 8.0
|
|
||||||
// this function, when done recursively (A_x = darken(A_x-1, C)), draws exponential curve. (R^2 = 1)
|
|
||||||
|
|
||||||
if (x !in 0 until LIGHTMAP_WIDTH || y !in 0 until LIGHTMAP_HEIGHT) return colourNull
|
|
||||||
|
|
||||||
return Cvec(
|
|
||||||
lightmap.getR(x, y) * (1f - darken.r * lightScalingMagic),
|
|
||||||
lightmap.getG(x, y) * (1f - darken.g * lightScalingMagic),
|
|
||||||
lightmap.getB(x, y) * (1f - darken.b * lightScalingMagic),
|
|
||||||
lightmap.getA(x, y) * (1f - darken.a * lightScalingMagic)
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/** infix is removed to clarify the association direction */
|
|
||||||
private fun Cvec.maxAndAssign(other: Cvec): Cvec {
|
|
||||||
// TODO investigate: if I use assignment instead of set(), it blackens like the vector branch. --Torvald, 2019-06-07
|
|
||||||
// that was because you forgot 'this.r/g/b/a = ' part, bitch. --Torvald, 2019-06-07
|
|
||||||
this.r = if (this.r > other.r) this.r else other.r
|
|
||||||
this.g = if (this.g > other.g) this.g else other.g
|
|
||||||
this.b = if (this.b > other.b) this.b else other.b
|
|
||||||
this.a = if (this.a > other.a) this.a else other.a
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Float.inv() = 1f / this
|
|
||||||
fun Int.even(): Boolean = this and 1 == 0
|
|
||||||
fun Int.odd(): Boolean = this and 1 == 1
|
|
||||||
|
|
||||||
// TODO: float LUT lookup using linear interpolation
|
|
||||||
|
|
||||||
// input: 0..1 for int 0..1023
|
|
||||||
fun hdr(intensity: Float): Float {
|
|
||||||
val intervalStart = (intensity / 4f * LightmapHDRMap.size).floorInt()
|
|
||||||
val intervalEnd = (intensity / 4f * LightmapHDRMap.size).floorInt() + 1
|
|
||||||
|
|
||||||
if (intervalStart == intervalEnd) return LightmapHDRMap[intervalStart]
|
|
||||||
|
|
||||||
val intervalPos = (intensity / 4f * LightmapHDRMap.size) - (intensity / 4f * LightmapHDRMap.size).toInt()
|
|
||||||
|
|
||||||
val ret = interpolateLinear(
|
|
||||||
intervalPos,
|
|
||||||
LightmapHDRMap[intervalStart],
|
|
||||||
LightmapHDRMap[intervalEnd]
|
|
||||||
)
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
private var _init = false
|
|
||||||
|
|
||||||
fun resize(screenW: Int, screenH: Int) {
|
|
||||||
// make sure the BlocksDrawer is resized first!
|
|
||||||
|
|
||||||
// copied from BlocksDrawer, duh!
|
|
||||||
// FIXME 'lightBuffer' is not zoomable in this way
|
|
||||||
val tilesInHorizontal = (AppLoader.screenSize.screenWf / TILE_SIZE).ceilInt() + 1
|
|
||||||
val tilesInVertical = (AppLoader.screenSize.screenHf / TILE_SIZE).ceilInt() + 1
|
|
||||||
|
|
||||||
LIGHTMAP_WIDTH = (Terrarum.ingame?.ZOOM_MINIMUM ?: 1f).inv().times(AppLoader.screenSize.screenW).div(TILE_SIZE).ceilInt() + overscan_open * 2 + 3
|
|
||||||
LIGHTMAP_HEIGHT = (Terrarum.ingame?.ZOOM_MINIMUM ?: 1f).inv().times(AppLoader.screenSize.screenH).div(TILE_SIZE).ceilInt() + overscan_open * 2 + 3
|
|
||||||
|
|
||||||
if (_init) {
|
|
||||||
lightBuffer.dispose()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
_init = true
|
|
||||||
}
|
|
||||||
lightBuffer = Pixmap(tilesInHorizontal, tilesInVertical, Pixmap.Format.RGBA8888)
|
|
||||||
|
|
||||||
lightmap.destroy()
|
|
||||||
_mapLightLevelThis.destroy()
|
|
||||||
_mapThisTileOpacity.destroy()
|
|
||||||
_mapThisTileOpacity2.destroy()
|
|
||||||
lightmap = UnsafeCvecArray(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT)
|
|
||||||
_mapLightLevelThis = UnsafeCvecArray(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT)
|
|
||||||
_mapThisTileOpacity = UnsafeCvecArray(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT)
|
|
||||||
_mapThisTileOpacity2 = UnsafeCvecArray(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT)
|
|
||||||
|
|
||||||
printdbg(this, "Resize event")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** To eliminated visible edge on the gradient when 255/1023 is exceeded */
|
|
||||||
fun Color.normaliseToHDR() = Color(
|
|
||||||
hdr(this.r.coerceIn(0f, 1f)),
|
|
||||||
hdr(this.g.coerceIn(0f, 1f)),
|
|
||||||
hdr(this.b.coerceIn(0f, 1f)),
|
|
||||||
hdr(this.a.coerceIn(0f, 1f))
|
|
||||||
)
|
|
||||||
|
|
||||||
inline fun Float.hdnorm() = hdr(this.coerceIn(0f, 1f))
|
|
||||||
|
|
||||||
private fun Cvec.nonZero() = this.r.abs() > epsilon ||
|
|
||||||
this.g.abs() > epsilon ||
|
|
||||||
this.b.abs() > epsilon ||
|
|
||||||
this.a.abs() > epsilon
|
|
||||||
|
|
||||||
val histogram: Histogram
|
|
||||||
get() {
|
|
||||||
val reds = IntArray(256) // reds[intensity] ← counts
|
|
||||||
val greens = IntArray(256) // do.
|
|
||||||
val blues = IntArray(256) // do.
|
|
||||||
val uvs = IntArray(256)
|
|
||||||
val render_width = for_x_end - for_x_start
|
|
||||||
val render_height = for_y_end - for_y_start
|
|
||||||
// excluiding overscans; only reckon echo lights
|
|
||||||
for (y in overscan_open..render_height + overscan_open + 1) {
|
|
||||||
for (x in overscan_open..render_width + overscan_open + 1) {
|
|
||||||
try {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
catch (e: ArrayIndexOutOfBoundsException) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Histogram(reds, greens, blues, uvs)
|
|
||||||
}
|
|
||||||
|
|
||||||
class Histogram(val reds: IntArray, val greens: IntArray, val blues: IntArray, val uvs: IntArray) {
|
|
||||||
|
|
||||||
val RED = 0
|
|
||||||
val GREEN = 1
|
|
||||||
val BLUE = 2
|
|
||||||
val UV = 3
|
|
||||||
|
|
||||||
val screen_tiles: Int = (for_x_end - for_x_start + 2) * (for_y_end - for_y_start + 2)
|
|
||||||
|
|
||||||
val brightest: Int
|
|
||||||
get() {
|
|
||||||
for (i in 255 downTo 1) {
|
|
||||||
if (reds[i] > 0 || greens[i] > 0 || blues[i] > 0)
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
val brightest8Bit: Int
|
|
||||||
get() { val b = brightest
|
|
||||||
return if (brightest > 255) 255 else b
|
|
||||||
}
|
|
||||||
|
|
||||||
val dimmest: Int
|
|
||||||
get() {
|
|
||||||
for (i in 0..255) {
|
|
||||||
if (reds[i] > 0 || greens[i] > 0 || blues[i] > 0)
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
return 255
|
|
||||||
}
|
|
||||||
|
|
||||||
val range: Int = 255
|
|
||||||
|
|
||||||
fun get(index: Int): IntArray {
|
|
||||||
return when (index) {
|
|
||||||
RED -> reds
|
|
||||||
GREEN -> greens
|
|
||||||
BLUE -> blues
|
|
||||||
UV -> uvs
|
|
||||||
else -> throw IllegalArgumentException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun interpolateLinear(scale: Float, startValue: Float, endValue: Float): Float {
|
|
||||||
if (startValue == endValue) {
|
|
||||||
return startValue
|
|
||||||
}
|
|
||||||
if (scale <= 0f) {
|
|
||||||
return startValue
|
|
||||||
}
|
|
||||||
if (scale >= 1f) {
|
|
||||||
return endValue
|
|
||||||
}
|
|
||||||
return (1f - scale) * startValue + scale * endValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Cvec.toRGBA() = (255 * r).toInt() shl 24 or ((255 * g).toInt() shl 16) or ((255 * b).toInt() shl 8) or (255 * a).toInt()
|
|
||||||
fun Color.toRGBA() = (255 * r).toInt() shl 24 or ((255 * g).toInt() shl 16) or ((255 * b).toInt() shl 8) or (255 * a).toInt()
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user