From 0e3bfbb7826146b816bdf34143a1b025e56fb8d7 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Thu, 29 Jul 2021 16:23:18 +0900 Subject: [PATCH] fixed a bug where wall item can be consumend indefinitely because checking for what's already there was not working --- .../modulebasegame/gameitems/BlockBase.kt | 2 +- .../ui/UIItemInventoryItemGrid.kt | 2 +- .../{BlocksDrawerNew.kt => BlocksDrawer.kt} | 2 +- .../terrarum/worlddrawer/BlocksDrawer_old.kt | 724 ------------- .../terrarum/worlddrawer/LightmapRenderer.kt | 955 ++++++++++++++++- .../worlddrawer/LightmapRendererNew.kt | 961 ------------------ 6 files changed, 956 insertions(+), 1690 deletions(-) rename src/net/torvald/terrarum/worlddrawer/{BlocksDrawerNew.kt => BlocksDrawer.kt} (99%) delete mode 100644 src/net/torvald/terrarum/worlddrawer/BlocksDrawer_old.kt delete mode 100644 src/net/torvald/terrarum/worlddrawer/LightmapRendererNew.kt diff --git a/src/net/torvald/terrarum/modulebasegame/gameitems/BlockBase.kt b/src/net/torvald/terrarum/modulebasegame/gameitems/BlockBase.kt index d4be46c7f..47024321a 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameitems/BlockBase.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameitems/BlockBase.kt @@ -50,7 +50,7 @@ object BlockBase { if (gameItem.inventoryCategory == GameItem.Category.BLOCK && gameItem.dynamicID == ingame.world.getTileFromTerrain(mouseTile.x, mouseTile.y) || 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 diff --git a/src/net/torvald/terrarum/modulebasegame/ui/UIItemInventoryItemGrid.kt b/src/net/torvald/terrarum/modulebasegame/ui/UIItemInventoryItemGrid.kt index c897a3538..c0cb02d59 100644 --- a/src/net/torvald/terrarum/modulebasegame/ui/UIItemInventoryItemGrid.kt +++ b/src/net/torvald/terrarum/modulebasegame/ui/UIItemInventoryItemGrid.kt @@ -378,7 +378,7 @@ class UIItemInventoryItemGrid( if (isCompactMode && it.item != null && it.mouseUp && !tooltipSet) { (Terrarum.ingame as? TerrarumIngame)?.setTooltipMessage( 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 { it.item?.name diff --git a/src/net/torvald/terrarum/worlddrawer/BlocksDrawerNew.kt b/src/net/torvald/terrarum/worlddrawer/BlocksDrawer.kt similarity index 99% rename from src/net/torvald/terrarum/worlddrawer/BlocksDrawerNew.kt rename to src/net/torvald/terrarum/worlddrawer/BlocksDrawer.kt index 3fa8952bf..ee0576020 100644 --- a/src/net/torvald/terrarum/worlddrawer/BlocksDrawerNew.kt +++ b/src/net/torvald/terrarum/worlddrawer/BlocksDrawer.kt @@ -589,7 +589,7 @@ internal object BlocksDrawer { } 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) { //Gdx.gl.glClearColor(.094f, .094f, .094f, 0f) diff --git a/src/net/torvald/terrarum/worlddrawer/BlocksDrawer_old.kt b/src/net/torvald/terrarum/worlddrawer/BlocksDrawer_old.kt deleted file mode 100644 index a4054d767..000000000 --- a/src/net/torvald/terrarum/worlddrawer/BlocksDrawer_old.kt +++ /dev/null @@ -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) -} -*/ \ No newline at end of file diff --git a/src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt b/src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt index e2eb5397f..c2648b67f 100644 --- a/src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt +++ b/src/net/torvald/terrarum/worlddrawer/LightmapRenderer.kt @@ -1,10 +1,961 @@ 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. */ +//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((LIGHTMAP_WIDTH + LIGHTMAP_HEIGHT) * 2) + + private val lanternMap = HashMap((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?) { + 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?>) { + 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() + diff --git a/src/net/torvald/terrarum/worlddrawer/LightmapRendererNew.kt b/src/net/torvald/terrarum/worlddrawer/LightmapRendererNew.kt deleted file mode 100644 index c2648b67f..000000000 --- a/src/net/torvald/terrarum/worlddrawer/LightmapRendererNew.kt +++ /dev/null @@ -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((LIGHTMAP_WIDTH + LIGHTMAP_HEIGHT) * 2) - - private val lanternMap = HashMap((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?) { - 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?>) { - 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() -