Files
Terrarum/src/net/torvald/terrarum/blockstats/MinimapComposer.kt
2021-12-22 16:00:21 +09:00

181 lines
5.8 KiB
Kotlin

package net.torvald.terrarum.blockstats
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.Pixmap
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.graphics.glutils.FrameBuffer
import com.badlogic.gdx.utils.Disposable
import com.badlogic.gdx.utils.GdxRuntimeException
import com.badlogic.gdx.utils.Queue
import net.torvald.terrarum.App
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.abs
import net.torvald.terrarum.concurrent.ThreadExecutor
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.modulebasegame.ui.UIInventoryMinimap.Companion.MINIMAP_HEIGHT
import net.torvald.terrarum.modulebasegame.ui.UIInventoryMinimap.Companion.MINIMAP_WIDTH
import net.torvald.terrarum.sqr
import net.torvald.terrarum.toInt
import java.util.concurrent.Callable
import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.absoluteValue
import kotlin.math.roundToInt
object MinimapComposer : Disposable {
private val threadExecutor = ThreadExecutor(maxOf(1, App.THREAD_COUNT.times(2).div(3)))
const val SQUARE_SIZE = 13 // preferably in odd number
val MINIMAP_TILE_WIDTH = (MINIMAP_WIDTH.toInt() * 3) / SQUARE_SIZE + 4
val MINIMAP_TILE_HEIGHT = (MINIMAP_HEIGHT.toInt() * 3) / SQUARE_SIZE + 4
private var world: GameWorld = GameWorld.makeNullWorld()
fun setWorld(world: GameWorld) {
try {
if (this.world != world) {
App.printdbg(this, "World change detected -- old world: ${this.world.hashCode()}, new world: ${world.hashCode()}")
// TODO, also set totalWidth/Height
}
}
catch (e: UninitializedPropertyAccessException) {
// new init, do nothing
}
finally {
this.world = world
}
}
val pixmaps = Array(SQUARE_SIZE*SQUARE_SIZE) { Pixmap(MINIMAP_TILE_WIDTH, MINIMAP_TILE_HEIGHT, Pixmap.Format.RGBA8888) }
private val updaterQueue = Array<Callable<Unit>?>(pixmaps.size) { null }
private val spiralIndices = ArrayList<Int>()
init {
val X = SQUARE_SIZE
val Y = SQUARE_SIZE
var x = 0
var y = 0
var dx = 0
var dy = -1
(0 until SQUARE_SIZE*SQUARE_SIZE).forEach {
// if ((-(X/2) <= x && x <= X/2) && (-(Y/2) <= y && y <= Y/2)) // just in case it's not a square
spiralIndices.add((y + Y/2) * X + (x + X/2))
if (x == y || (x < 0 && x == -y) || (x > 0 && x == 1-y)) {
val d1 = -dy
val d2 = dx
dx = d1
dy = d2
}
x += dx
y += dy
}
}
private infix fun Int.pow(exp: Int): Int {
var exp = exp
var base = this
var result = 1
while (true) {
if (exp and 1 != 0)
result *= base
exp = exp shr 1
if (exp.inv() != 0)
break
base *= base
}
return result
}
init {
App.disposables.add(this)
}
/**
* @param x player-centric
* @param y player-centric
*/
fun queueRender(x: Int, y: Int) {
val udc = updaterQueue.sumOf { (it != null).toInt() }
if (udc == 0) {
val tlx = x - (MINIMAP_TILE_WIDTH / 2)
val tly = y - (MINIMAP_TILE_HEIGHT / 2)
// make the queueing work
// enqueue first
spiralIndices.forEachIndexed { index, i ->
val pixmap = pixmaps[i]
val tx = tlx + (MINIMAP_TILE_WIDTH * ((i % SQUARE_SIZE) - (SQUARE_SIZE / 2)))
val ty = tly + (MINIMAP_TILE_HEIGHT * ((i / SQUARE_SIZE) - (SQUARE_SIZE / 2)))
updaterQueue[index] = Callable { createUpdater(tx, ty, pixmap, i).run() }
// printdbg(this, "Queueing tilemap update ($tx,$ty) from queue[$i]")
}
threadExecutor.renew()
threadExecutor.submitAll(updaterQueue.filterNotNull())
Thread { threadExecutor.join() }.start()
}
else {
// printdbg(this, "$udc Threads still running, request disregarded")
}
// printdbg(this, "** $udc Threads still running **")
}
private val oobColTop = Color(0)
private val oobColBtm = Color(0x202020FF)
/**
* @param tx top-left of the world
* @param ty top-left of the world
* @param pixmap pixmap to draw pixels on
*/
private fun createUpdater(tx: Int, ty: Int, pixmap: Pixmap, index: Int) = Runnable {
try {
for (y in ty until ty + MINIMAP_TILE_HEIGHT) {
for (x in tx until tx + MINIMAP_TILE_WIDTH) {
val tileTerr = world.getTileFromTerrain(x, y)
val wallTerr = world.getTileFromWall(x, y)
val colTerr = App.tileMaker.terrainTileColourMap.get(tileTerr)!!.toGdxColor()
val colWall = App.tileMaker.terrainTileColourMap.get(wallTerr)!!.toGdxColor().mul(App.tileMaker.wallOverlayColour)
var outCol = if (y < 0)
oobColTop
else if (y >= world.height)
oobColBtm
else if (colTerr.a > 0.1f) colTerr else colWall
// if (index % 2 == 1) outCol.lerp(Color.BLACK, 0.2f)
// else if (index != (SQUARE_SIZE.sqr() - 1) / 2) outCol.lerp(Color.FIREBRICK, 0.12f)
pixmap.blending = Pixmap.Blending.None
pixmap.setColor(outCol)
pixmap.drawPixel(x - tx, y - ty)
}
}
updaterQueue[index] = null
}
catch(e: Throwable) {
e.printStackTrace()
}
}
override fun dispose() {
pixmaps.forEach { it.dispose() }
}
}