package net.torvald.terrarum.worlddrawer import com.badlogic.gdx.Gdx import com.badlogic.gdx.graphics.* import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.glutils.ShaderProgram import com.badlogic.gdx.math.Matrix4 import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.PairedMapLayer import net.torvald.terrarum.blockproperties.Block import net.torvald.terrarum.blockproperties.BlockCodex import net.torvald.terrarum.* import net.torvald.terrarum.AppLoader.printdbg import net.torvald.terrarum.ceilInt import net.torvald.terrarum.gameworld.fmod 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 /** * Sub-portion of IngameRenderer. You are not supposed to directly deal with this. * * The terrain texture atlas is HARD CODED as "4096x4096, on which 256x256 tiles are contained" * in the shader (tiling.frag). This will not be a problem in the base game, but if you are modifying * this engine for your project, you must edit the shader program accordingly. * * Created by minjaesong on 2016-01-19. */ internal object BlocksDrawer { lateinit var world: GameWorld private val TILE_SIZE = FeaturesDrawer.TILE_SIZE private val TILE_SIZEF = FeaturesDrawer.TILE_SIZE.toFloat() 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) const val breakAnimSteps = 10 val WALL = GameWorld.WALL val TERRAIN = GameWorld.TERRAIN val WIRE = GameWorld.WIRE private const val NEARBY_TILE_KEY_UP = 0 private const val NEARBY_TILE_KEY_RIGHT = 1 private const val NEARBY_TILE_KEY_DOWN = 2 private const val NEARBY_TILE_KEY_LEFT = 3 private const val NEARBY_TILE_CODE_UP = 1 private const val NEARBY_TILE_CODE_RIGHT = 2 private const val NEARBY_TILE_CODE_DOWN = 4 private const val NEARBY_TILE_CODE_LEFT = 8 private const val GZIP_READBUF_SIZE = 8192 private lateinit var terrainTilesBuffer: Array private lateinit var wallTilesBuffer: Array private lateinit var wireTilesBuffer: Array private lateinit var tilesBuffer: Pixmap private lateinit var tilesQuad: Mesh private val shader = ShaderProgram(Gdx.files.internal("assets/4096.vert"), Gdx.files.internal("assets/tiling.frag")) 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 tileX = (tileID % 16) * 16 val tileY = tileID / 16 val tile = tilesTerrain.get(tileX, tileY) // slow memory copy :\ I'm afraid I can't random-access bytebuffer... for (scanline in 0 until tileItemImgPixMap.height) { for (x in 0 until TILE_SIZE) { val pixel = terrainPixMap.getPixel(tileX + x, scanline) tileItemImgPixMap.drawPixel(x + TILE_SIZE * (tileID % 16), scanline, pixel) } } } 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, Block.ORE_COPPER, Block.ORE_IRON, Block.ORE_GOLD, Block.ORE_SILVER, Block.ORE_ILMENITE, Block.ORE_AURICHALCUM ) /** * 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.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( 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 /////////////////////////////////////////// internal fun renderWall(projectionMatrix: Matrix4) { // blend normal Gdx.gl.glEnable(GL20.GL_TEXTURE_2D) Gdx.gl.glEnable(GL20.GL_BLEND) Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA) drawTiles(WALL, false) renderUsingBuffer(WALL, projectionMatrix) } internal fun renderTerrain(projectionMatrix: Matrix4) { // blend normal Gdx.gl.glEnable(GL20.GL_TEXTURE_2D) Gdx.gl.glEnable(GL20.GL_BLEND) Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA) drawTiles(TERRAIN, false) // regular tiles renderUsingBuffer(TERRAIN, projectionMatrix) } internal fun renderFront(projectionMatrix: Matrix4, drawWires: Boolean) { // blend mul Gdx.gl.glEnable(GL20.GL_TEXTURE_2D) Gdx.gl.glEnable(GL20.GL_BLEND) Gdx.gl.glBlendFunc(GL20.GL_DST_COLOR, GL20.GL_ONE_MINUS_SRC_ALPHA) drawTiles(TERRAIN, true) // blendmul tiles renderUsingBuffer(TERRAIN, projectionMatrix) // blend normal Gdx.gl.glEnable(GL20.GL_TEXTURE_2D) Gdx.gl.glEnable(GL20.GL_BLEND) Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA) if (drawWires) { drawTiles(WIRE, false) renderUsingBuffer(WIRE, projectionMatrix) } } 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 ) /** * Writes to buffer. Actual draw code must be called after this operation. */ private fun drawTiles(mode: Int, drawModeTilesBlendMul: Boolean) { val for_y_start = WorldCamera.y / TILE_SIZE val for_y_end = for_y_start + tilesBuffer.height - 1//clampHTile(for_y_start + (WorldCamera.height / TILE_SIZE) + 2) val for_x_start = WorldCamera.x / TILE_SIZE val for_x_end = for_x_start + tilesBuffer.width - 1//for_x_start + (WorldCamera.width / TILE_SIZE) + 3 // loop for (y in for_y_start..for_y_end) { 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 // if (thisTile == 0) // writeToBuffer(mode, x - for_x_start, y - for_y_start, 0, 0) // else // writeToBuffer(mode, x - for_x_start, y - for_y_start, 2, 0) //} //else { val nearbyTilesInfo: Int if (isPlatform(thisTile)) { nearbyTilesInfo = getNearbyTilesInfoPlatform(x, y) } else if (isWallSticker(thisTile)) { nearbyTilesInfo = getNearbyTilesInfoWallSticker(x, y) } else if (isConnectMutual(thisTile)) { nearbyTilesInfo = getNearbyTilesInfoNonSolid(x, y, mode) } else if (isConnectSelf(thisTile)) { nearbyTilesInfo = getNearbyTilesInfo(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 val breakage = if (mode == TERRAIN) world.getTerrainDamage(x, y) else world.getWallDamage(x, y) val maxHealth = BlockCodex[world.getTileFromTerrain(x, y)].strength val breakingStage = (breakage / maxHealth).times(breakAnimSteps).roundInt() // draw a tile if (drawModeTilesBlendMul) { // while iterating through, only the some tiles are actually eligible to be drawn as MUL, // so obviously when we caught not eligible tile, we need to skip that by marking as Tile No. zero if (isBlendMul(thisTile)) { writeToBuffer(mode, x - for_x_start, y - for_y_start, thisTileX, thisTileY, breakingStage) } else { writeToBuffer(mode, x - for_x_start, y - for_y_start, 0, 0, 0) } } else { // do NOT add "if (!isBlendMul(thisTile))"! // or else they will not look like they should be when backed with wall writeToBuffer(mode, x - for_x_start, y - for_y_start, thisTileX, thisTileY, breakingStage) } // 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).roundInt() // actual drawing if (stage > 0) { writeToBuffer(mode, x - for_x_start, y - for_y_start, 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. } } } } /** * @param x * * * @param y * * * @return binary [0-15] 1: up, 2: right, 4: down, 8: left */ internal fun getNearbyTilesInfo(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 } internal fun getNearbyTilesInfoNonSolid(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 } internal 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 } } internal 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 } /** * Raw format of RGBA8888, where RGB portion actually encodes the absolute tile number and A is always 255. * * @return Raw colour bits in RGBA8888 format */ private fun sheetXYToTilemapColour(mode: Int, sheetX: Int, sheetY: Int, breakage: Int): Int = when (mode) { // the tail ".or(255)" is there to write 1.0 to the A channel (remember, return type is RGBA) TERRAIN, WALL -> (tilesTerrain.horizontalCount * sheetY + sheetX).shl(8).or(255) or // the actual tile bits breakage.and(15).shl(28) // breakage bits WIRE -> (tilesWire.horizontalCount * sheetY + sheetX).shl(8).or(255) else -> throw IllegalArgumentException() } private fun writeToBuffer(mode: Int, bufferPosX: Int, bufferPosY: Int, sheetX: Int, sheetY: Int, breakage: Int) { val sourceBuffer = when(mode) { TERRAIN -> terrainTilesBuffer WALL -> wallTilesBuffer WIRE -> wireTilesBuffer else -> throw IllegalArgumentException() } sourceBuffer[bufferPosY][bufferPosX] = sheetXYToTilemapColour(mode, sheetX, sheetY, breakage) } private fun renderUsingBuffer(mode: Int, projectionMatrix: Matrix4) { //Gdx.gl.glClearColor(.094f, .094f, .094f, 0f) //Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) //val tilesInHorizontal = tilesBuffer.width //val tilesInVertical = tilesBuffer.height val tileAtlas = when (mode) { TERRAIN, WALL -> tilesTerrain WIRE -> tilesWire else -> throw IllegalArgumentException() } val sourceBuffer = when(mode) { TERRAIN -> terrainTilesBuffer WALL -> wallTilesBuffer WIRE -> wireTilesBuffer else -> throw IllegalArgumentException() } val vertexColour = when (mode) { TERRAIN, WIRE -> Color.WHITE WALL -> wallOverlayColour else -> throw IllegalArgumentException() } // write to colour buffer for (y in 0 until tilesBuffer.height) { for (x in 0 until tilesBuffer.width) { val color = sourceBuffer[y][x] tilesBuffer.setColor(color) tilesBuffer.drawPixel(x, y) } } val tilesBufferAsTex = Texture(tilesBuffer) tilesBufferAsTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest) tilesBufferAsTex.bind(1) // trying 1 and 0... tileAtlas.texture.bind(0) // for some fuck reason, it must be bound as last shader.begin() shader.setUniformMatrix("u_projTrans", projectionMatrix)//camera.combined) shader.setUniformf("colourFilter", vertexColour) shader.setUniformf("screenDimension", Gdx.graphics.width.toFloat(), Gdx.graphics.height.toFloat()) shader.setUniformi("tilesAtlas", 0) shader.setUniformi("tilemap", 1) shader.setUniformi("tilemapDimension", tilesBuffer.width, tilesBuffer.height) shader.setUniformf("tilesInAxes", tilesInHorizontal.toFloat(), tilesInVertical.toFloat()) shader.setUniformi("cameraTranslation", WorldCamera.x fmod TILE_SIZE, WorldCamera.y fmod TILE_SIZE) /*shader hard-code*/shader.setUniformi("tilesInAtlas", tileAtlas.horizontalCount, tileAtlas.verticalCount) //depends on the tile atlas /*shader hard-code*/shader.setUniformi("atlasTexSize", tileAtlas.texture.width, tileAtlas.texture.height) //depends on the tile atlas tilesQuad.render(shader, GL20.GL_TRIANGLES) shader.end() //tilesBufferAsTex.dispose() } private var oldScreenW = 0 private var oldScreenH = 0 var tilesInHorizontal = -1; private set var tilesInVertical = -1; private set fun resize(screenW: Int, screenH: Int) { tilesInHorizontal = (screenW.toFloat() / TILE_SIZE).ceilInt() + 1 tilesInVertical = (screenH.toFloat() / TILE_SIZE).ceilInt() + 1 val oldTH = (oldScreenW.toFloat() / TILE_SIZE).ceilInt() + 1 val oldTV = (oldScreenH.toFloat() / TILE_SIZE).ceilInt() + 1 // only update if it's really necessary if (oldTH != tilesInHorizontal || oldTV != tilesInVertical) { terrainTilesBuffer = Array(tilesInVertical, { kotlin.IntArray(tilesInHorizontal) }) wallTilesBuffer = Array(tilesInVertical, { kotlin.IntArray(tilesInHorizontal) }) wireTilesBuffer = Array(tilesInVertical, { kotlin.IntArray(tilesInHorizontal) }) tilesBuffer = Pixmap(tilesInHorizontal, tilesInVertical, Pixmap.Format.RGB888) } if (oldScreenW != screenW || oldScreenH != screenH) { tilesQuad = Mesh( true, 4, 6, VertexAttribute.Position(), VertexAttribute.ColorUnpacked(), VertexAttribute.TexCoords(0) ) tilesQuad.setVertices(floatArrayOf( // WARNING! not ususal quads; TexCoords of Y is flipped 0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f, 0f, screenW.toFloat(), 0f, 0f, 1f, 1f, 1f, 1f, 1f, 0f, screenW.toFloat(), screenH.toFloat(), 0f, 1f, 1f, 1f, 1f, 1f, 1f, 0f, screenH.toFloat(), 0f, 1f, 1f, 1f, 1f, 0f, 1f )) tilesQuad.setIndices(shortArrayOf(0, 1, 2, 2, 3, 0)) } oldScreenW = screenW oldScreenH = screenH printdbg(this, "Resize event") } 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) }