mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-12 06:41:51 +09:00
612 lines
30 KiB
Kotlin
612 lines
30 KiB
Kotlin
package net.torvald.terrarum.worlddrawer
|
|
|
|
import com.badlogic.gdx.Gdx
|
|
import com.badlogic.gdx.files.FileHandle
|
|
import com.badlogic.gdx.graphics.Color
|
|
import com.badlogic.gdx.graphics.Pixmap
|
|
import com.badlogic.gdx.graphics.Texture
|
|
import com.badlogic.gdx.utils.GdxRuntimeException
|
|
import com.jme3.math.FastMath
|
|
import net.torvald.gdx.graphics.Cvec
|
|
import net.torvald.terrarum.*
|
|
import net.torvald.terrarum.App.printdbg
|
|
import net.torvald.terrarum.TerrarumAppConfiguration.TILE_SIZE
|
|
import net.torvald.terrarum.gameitems.ItemID
|
|
import net.torvald.terrarum.utils.HashArray
|
|
import net.torvald.terrarum.worlddrawer.CreateTileAtlas.AtlasSource.*
|
|
import net.torvald.terrarum.worlddrawer.CreateTileAtlas.RenderTag.Companion.MASK_47
|
|
import kotlin.math.sqrt
|
|
|
|
/**
|
|
* This class implements work_files/dynamic_shape_2_0.psd
|
|
*
|
|
* Tile at (0,0) AND (5,0) must be transparent. Former is because block 0 is considered as an air, and the latter
|
|
* is because it's breakage of 0, and 0 means no breakage. Breakage part is hard-coded in the tiling shader.
|
|
*
|
|
* Any real tiles must begin from (0,16), the first 256x16 section is reserved for special purpose (terrain: breakage, fluid: empty)
|
|
*
|
|
* Created by minjaesong on 2019-02-28.
|
|
*/
|
|
class CreateTileAtlas {
|
|
|
|
companion object {
|
|
val WALL_OVERLAY_COLOUR = Color(.65f, .65f, .65f, 1f)
|
|
}
|
|
|
|
var MAX_TEX_SIZE = App.getConfigInt("atlastexsize").coerceIn(1024, App.glInfo.GL_MAX_TEXTURE_SIZE); private set
|
|
var TILES_IN_X = MAX_TEX_SIZE / TILE_SIZE; private set
|
|
|
|
var SHADER_SIZE_KEYS = floatArrayOf(MAX_TEX_SIZE.toFloat(), MAX_TEX_SIZE.toFloat(), TILES_IN_X.toFloat(), TILES_IN_X.toFloat()); private set
|
|
|
|
private var TOTAL_TILES = TILES_IN_X * TILES_IN_X
|
|
|
|
lateinit var atlasPrevernal: Pixmap
|
|
lateinit var atlasVernal: Pixmap
|
|
lateinit var atlasAestival: Pixmap
|
|
lateinit var atlasSerotinal: Pixmap
|
|
lateinit var atlasAutumnal: Pixmap
|
|
lateinit var atlasHibernal: Pixmap
|
|
lateinit var atlasFluid: Pixmap
|
|
lateinit var atlasGlow: Pixmap // glowing won't be affected by the season... for now
|
|
lateinit var atlasEmissive: Pixmap // glowing won't be affected by the season... for now
|
|
lateinit var itemTerrainTexture: Texture
|
|
lateinit var itemTerrainTextureGlow: Texture
|
|
lateinit var itemTerrainTextureEmissive: Texture
|
|
lateinit var itemWallTexture: Texture
|
|
lateinit var itemWallTextureGlow: Texture
|
|
lateinit var itemWallTextureEmissive: Texture
|
|
lateinit var terrainTileColourMap: HashMap<ItemID, Cvec>
|
|
lateinit var tags: HashMap<ItemID, RenderTag> // TileID, RenderTag
|
|
private set
|
|
lateinit var tagsByTileNum: HashArray<RenderTag>; private set
|
|
lateinit var itemSheetNumbers: HashMap<ItemID, Int> // TileID, Int
|
|
private set
|
|
private val defaultRenderTag = RenderTag(3, RenderTag.CONNECT_SELF, RenderTag.MASK_NA) // 'update' block
|
|
var initialised = false
|
|
private set
|
|
|
|
/** 0.tga, 1.tga.gz, 3242423.tga, 33.tga.gz */
|
|
private val tileNameRegex = Regex("""(0|[1-9][0-9]*)\.tga(\.gz)?""")
|
|
|
|
// 16 tiles are reserved for internal use: solid black, solid white, breakage stages.
|
|
// 0th tile is complete transparent tile and is also a BlockID of zero: air.
|
|
private var atlasCursor = 64 // 64 predefined tiles. The normal blocks (e.g. Air) should start from this number
|
|
private val atlasInit = "./assets/graphics/blocks/init.tga"
|
|
private var itemSheetCursor = 16
|
|
|
|
internal lateinit var itemTerrainPixmap: Pixmap
|
|
internal lateinit var itemTerrainPixmapGlow: Pixmap
|
|
internal lateinit var itemTerrainPixmapEmissive: Pixmap
|
|
internal lateinit var itemWallPixmap: Pixmap
|
|
internal lateinit var itemWallPixmapGlow: Pixmap
|
|
internal lateinit var itemWallPixmapEmissive: Pixmap
|
|
|
|
val atlas: Pixmap
|
|
get() = atlasVernal
|
|
|
|
private fun drawInitPixmap() {
|
|
val initPixmap = Pixmap(Gdx.files.internal(atlasInit))
|
|
|
|
val tilesInInitPixmap = (initPixmap.width * initPixmap.height) / (TILE_SIZE * TILE_SIZE)
|
|
val tilesPossibleInCurrentPixmap = (atlas.width * atlas.height) / (TILE_SIZE * TILE_SIZE)
|
|
|
|
if (tilesInInitPixmap > tilesPossibleInCurrentPixmap) throw Error("Atlas size too small -- can't even fit the init.tga (MAX_TEX_SIZE must be at least ${FastMath.nextPowerOfTwo((sqrt(tilesInInitPixmap.toFloat()) * TILE_SIZE).ceilToInt())})")
|
|
|
|
if (MAX_TEX_SIZE >= initPixmap.width) {
|
|
atlasPrevernal.drawPixmap(initPixmap, 0, 0)
|
|
atlasVernal.drawPixmap(initPixmap, 0, 0)
|
|
atlasAestival.drawPixmap(initPixmap, 0, 0)
|
|
atlasSerotinal.drawPixmap(initPixmap, 0, 0)
|
|
atlasAutumnal.drawPixmap(initPixmap, 0, 0)
|
|
atlasHibernal.drawPixmap(initPixmap, 0, 0)
|
|
}
|
|
else {
|
|
/*
|
|
What's happening:
|
|
|
|
src: dest:
|
|
AAAABBBBCCCCDDDD AAAA
|
|
BBBB
|
|
CCCC
|
|
DDDD
|
|
*/
|
|
val destX = 0
|
|
val srcY = 0
|
|
val scanW = MAX_TEX_SIZE
|
|
val scanH = TILE_SIZE
|
|
for (scantile in 0 until (initPixmap.width.toFloat() / MAX_TEX_SIZE).ceilToInt()) {
|
|
val srcX = scantile * scanW
|
|
val destY = scantile * TILE_SIZE
|
|
|
|
atlasPrevernal.drawPixmap(initPixmap, srcX, srcY, scanW, scanH, destX, destY, scanW, scanH)
|
|
atlasVernal.drawPixmap(initPixmap, srcX, srcY, scanW, scanH, destX, destY, scanW, scanH)
|
|
atlasAestival.drawPixmap(initPixmap, srcX, srcY, scanW, scanH, destX, destY, scanW, scanH)
|
|
atlasSerotinal.drawPixmap(initPixmap, srcX, srcY, scanW, scanH, destX, destY, scanW, scanH)
|
|
atlasAutumnal.drawPixmap(initPixmap, srcX, srcY, scanW, scanH, destX, destY, scanW, scanH)
|
|
atlasHibernal.drawPixmap(initPixmap, srcX, srcY, scanW, scanH, destX, destY, scanW, scanH)
|
|
}
|
|
}
|
|
|
|
initPixmap.dispose()
|
|
}
|
|
|
|
/**
|
|
* Must be called AFTER mods' loading so that all the block props are loaded
|
|
*/
|
|
operator fun invoke(updateExisting: Boolean = false) { if (updateExisting || !initialised) {
|
|
|
|
tags = HashMap<ItemID, RenderTag>()
|
|
tagsByTileNum = HashArray()
|
|
itemSheetNumbers = HashMap<ItemID, Int>()
|
|
|
|
atlasPrevernal = Pixmap(TILES_IN_X * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888).also { it.blending = Pixmap.Blending.None }
|
|
atlasVernal = Pixmap(TILES_IN_X * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888).also { it.blending = Pixmap.Blending.None }
|
|
atlasAestival = Pixmap(TILES_IN_X * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888).also { it.blending = Pixmap.Blending.None }
|
|
atlasSerotinal = Pixmap(TILES_IN_X * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888).also { it.blending = Pixmap.Blending.None }
|
|
atlasAutumnal = Pixmap(TILES_IN_X * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888).also { it.blending = Pixmap.Blending.None }
|
|
atlasHibernal = Pixmap(TILES_IN_X * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888).also { it.blending = Pixmap.Blending.None }
|
|
atlasFluid = Pixmap(TILES_IN_X * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888).also { it.blending = Pixmap.Blending.None }
|
|
atlasGlow = Pixmap(TILES_IN_X * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888).also { it.blending = Pixmap.Blending.None }
|
|
atlasEmissive = Pixmap(TILES_IN_X * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888).also { it.blending = Pixmap.Blending.None }
|
|
|
|
// populate the atlantes with atlasInit
|
|
// this just directly copies the image to the atlantes :p
|
|
drawInitPixmap()
|
|
|
|
|
|
// get all the files applicable
|
|
// first, get all the '/blocks' directory, and add all the files, regardless of their extension, to the list
|
|
// val tgaList = ArrayList<Pair<String, FileHandle>>() //Pair of <modname, filehandle>
|
|
// val tgaListOres = ArrayList<Pair<String, FileHandle>>()
|
|
|
|
val tgaList = HashMap<String, ArrayList<Pair<String, FileHandle>>>() // Key: directory name, value: pair of <modname, filehandle>
|
|
val dirList = listOf("blocks", "ores")
|
|
dirList.forEach { dirName ->
|
|
tgaList[dirName] = ArrayList()
|
|
ModMgr.getGdxFilesFromEveryMod(dirName).forEach { (modname, dir) ->
|
|
if (!dir.isDirectory) {
|
|
throw Error("Path '${dir.path()}' is not a directory")
|
|
}
|
|
|
|
if (dirName == "blocks") {
|
|
// filter files that do not exist on the blockcodex
|
|
dir.list()
|
|
.filter { tgaFile -> tgaFile.extension() == "tga" && !tgaFile.isDirectory && (BlockCodex.getOrNull("$modname:${tgaFile.nameWithoutExtension()}") != null) }
|
|
.sortedBy { it.nameWithoutExtension().toInt() }
|
|
.forEach { tgaFile: FileHandle -> // toInt() to sort by the number, not lexicographically
|
|
// tgaFile be like: ./assets/mods/basegame/blocks/32.tga (which is not always .tga)
|
|
val newFile = ModMgr.GameRetextureLoader.altFilePaths.getOrDefault(tgaFile.path(), tgaFile)
|
|
tgaList[dirName]!!.add(modname to newFile)
|
|
// printdbg(this, "modname = $modname, file = $newFile")
|
|
}
|
|
}
|
|
else {
|
|
// filter files that do not exist on the orecodex
|
|
dir.list()
|
|
.filter { tgaFile -> tgaFile.extension() == "tga" && !tgaFile.isDirectory && (OreCodex.getOrNull("ores@$modname:${tgaFile.nameWithoutExtension()}") != null) }
|
|
.sortedBy { it.nameWithoutExtension().toInt() }
|
|
.forEach { tgaFile: FileHandle -> // toInt() to sort by the number, not lexicographically
|
|
// tgaFile be like: ./assets/mods/basegame/blocks/32.tga (which is not always .tga)
|
|
val newFile = ModMgr.GameRetextureLoader.altFilePaths.getOrDefault(tgaFile.path(), tgaFile)
|
|
tgaList[dirName]!!.add(modname to newFile)
|
|
// printdbg(this, "modname = $modname, file = $newFile")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Sift through the file list for blocks, but TGA format first
|
|
dirList.forEach { dirName ->
|
|
tgaList[dirName]!!.forEach { (modname, filehandle) ->
|
|
printdbg(this, "processing $dirName $modname:${filehandle.name()}")
|
|
|
|
try {
|
|
val glowFile = Gdx.files.internal(
|
|
filehandle.path().dropLast(4) + "_glow.tga"
|
|
) // assuming strict ".tga" file for now...
|
|
val emissiveFile = Gdx.files.internal(
|
|
filehandle.path().dropLast(4) + "_emsv.tga"
|
|
) // assuming strict ".tga" file for now...
|
|
fileToAtlantes(
|
|
modname, filehandle,
|
|
if (glowFile.exists()) glowFile else null,
|
|
if (emissiveFile.exists()) emissiveFile else null,
|
|
if (dirName == "blocks") null else dirName
|
|
)
|
|
}
|
|
catch (e: GdxRuntimeException) {
|
|
System.err.println("Couldn't load file $filehandle from $modname, skipping...")
|
|
}
|
|
}
|
|
}
|
|
|
|
// test print
|
|
// PixmapIO2.writeTGA(Gdx.files.absolute("${App.defaultDir}/atlas.tga"), atlas, false)
|
|
// PixmapIO2.writeTGA(Gdx.files.absolute("${AppLoader.defaultDir}/atlasGlow.tga"), atlasGlow, false)
|
|
|
|
|
|
|
|
// Sift throuth the file list, second TGA.GZ
|
|
/*tgaList.filter { it.name().toUpperCase().endsWith(".TGA.GZ") }.forEach {
|
|
try {
|
|
fileToAtlantes(it)
|
|
}
|
|
catch (e: GdxRuntimeException) {
|
|
System.err.println("Couldn't load file $it, skipping...")
|
|
}
|
|
}*/
|
|
|
|
// create item_wall images
|
|
|
|
fun maskTypetoTileIDForItemImage(maskType: Int) = when(maskType) {
|
|
CreateTileAtlas.RenderTag.MASK_47 -> 17
|
|
CreateTileAtlas.RenderTag.MASK_PLATFORM -> 7
|
|
else -> 0
|
|
}
|
|
|
|
// val itemTerrainPixmap = Pixmap(16 * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888)
|
|
// val itemWallPixmap = Pixmap(16 * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888)
|
|
|
|
itemTerrainPixmap = Pixmap(TILES_IN_X * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888)
|
|
itemTerrainPixmapGlow = Pixmap(TILES_IN_X * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888)
|
|
itemTerrainPixmapEmissive = Pixmap(TILES_IN_X * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888)
|
|
itemWallPixmap = Pixmap(TILES_IN_X * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888)
|
|
itemWallPixmapGlow = Pixmap(TILES_IN_X * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888)
|
|
itemWallPixmapEmissive = Pixmap(TILES_IN_X * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888)
|
|
|
|
tags.toMap().forEach { id, tag ->
|
|
val tilePosFromAtlas = tag.tileNumber + maskTypetoTileIDForItemImage(tag.maskType)
|
|
val srcX = (tilePosFromAtlas % TILES_IN_X) * TILE_SIZE
|
|
val srcY = (tilePosFromAtlas / TILES_IN_X) * TILE_SIZE
|
|
val t = tileIDtoItemSheetNumber(id)
|
|
val destX = (t % TILES_IN_X) * TILE_SIZE
|
|
val destY = (t / TILES_IN_X) * TILE_SIZE
|
|
itemTerrainPixmap.drawPixmap(atlas, srcX, srcY, TILE_SIZE, TILE_SIZE, destX, destY, TILE_SIZE, TILE_SIZE)
|
|
itemTerrainPixmapGlow.drawPixmap(atlasGlow, srcX, srcY, TILE_SIZE, TILE_SIZE, destX, destY, TILE_SIZE, TILE_SIZE)
|
|
itemTerrainPixmapEmissive.drawPixmap(atlasEmissive, srcX, srcY, TILE_SIZE, TILE_SIZE, destX, destY, TILE_SIZE, TILE_SIZE)
|
|
itemWallPixmap.drawPixmap(atlas, srcX, srcY, TILE_SIZE, TILE_SIZE, destX, destY, TILE_SIZE, TILE_SIZE)
|
|
itemWallPixmapGlow.drawPixmap(atlasGlow, srcX, srcY, TILE_SIZE, TILE_SIZE, destX, destY, TILE_SIZE, TILE_SIZE)
|
|
itemWallPixmapEmissive.drawPixmap(atlasEmissive, srcX, srcY, TILE_SIZE, TILE_SIZE, destX, destY, TILE_SIZE, TILE_SIZE)
|
|
}
|
|
// darken things for the wall
|
|
for (y in 0 until itemWallPixmap.height) {
|
|
for (x in 0 until itemWallPixmap.width) {
|
|
val c1 = Color(itemWallPixmap.getPixel(x, y)).mulAndAssign(WALL_OVERLAY_COLOUR).toRGBA()
|
|
itemWallPixmap.drawPixel(x, y, c1)
|
|
val c2 = Color(itemWallPixmapGlow.getPixel(x, y)).mulAndAssign(WALL_OVERLAY_COLOUR).toRGBA()
|
|
itemWallPixmapGlow.drawPixel(x, y, c2)
|
|
val c3 = Color(itemWallPixmapEmissive.getPixel(x, y)).mulAndAssign(WALL_OVERLAY_COLOUR).toRGBA()
|
|
itemWallPixmapEmissive.drawPixel(x, y, c3)
|
|
}
|
|
}
|
|
|
|
|
|
// create terrain colourmap
|
|
terrainTileColourMap = HashMap<ItemID, Cvec>()
|
|
val pxCount = TILE_SIZE * TILE_SIZE
|
|
for (id in itemSheetNumbers) {
|
|
val tilenum = id.value
|
|
val tx = (tilenum % TILES_IN_X) * TILE_SIZE
|
|
val ty = (tilenum / TILES_IN_X) * TILE_SIZE
|
|
var r = 0f; var g = 0f; var b = 0f; var a = 0f
|
|
// average out the whole block
|
|
for (y in ty until ty + TILE_SIZE) {
|
|
for (x in tx until tx + TILE_SIZE) {
|
|
val data = itemTerrainPixmap.getPixel(x, y)
|
|
r += ((data ushr 24) and 255).div(255f)
|
|
g += ((data ushr 16) and 255).div(255f)
|
|
b += ((data ushr 8) and 255).div(255f)
|
|
a += (data and 255).div(255f)
|
|
}
|
|
}
|
|
|
|
terrainTileColourMap[id.key] = Cvec(
|
|
(r / pxCount),
|
|
(g / pxCount),
|
|
(b / pxCount),
|
|
(a / pxCount)
|
|
)
|
|
}
|
|
|
|
itemTerrainTexture = Texture(itemTerrainPixmap)
|
|
itemTerrainTextureGlow = Texture(itemTerrainPixmapGlow)
|
|
itemTerrainTextureEmissive = Texture(itemTerrainPixmapEmissive)
|
|
itemWallTexture = Texture(itemWallPixmap)
|
|
itemWallTextureGlow = Texture(itemWallPixmapGlow)
|
|
itemWallTextureEmissive = Texture(itemWallPixmapEmissive)
|
|
// itemTerrainPixmap.dispose()
|
|
// itemWallPixmap.dispose()
|
|
|
|
initialised = true
|
|
} }
|
|
|
|
fun getRenderTag(blockID: ItemID): RenderTag {
|
|
return tags.getOrDefault(blockID, defaultRenderTag)
|
|
}
|
|
|
|
fun getRenderTag(tilenum: Int): RenderTag {
|
|
return tagsByTileNum.getOrDefault(tilenum.toLong(), defaultRenderTag)
|
|
}
|
|
|
|
|
|
val nullTile = Pixmap(TILE_SIZE * 16, TILE_SIZE * 16, Pixmap.Format.RGBA8888)
|
|
|
|
private fun fileToAtlantes(modname: String, diffuse: FileHandle, glow: FileHandle?, emissive: FileHandle?, mode: String?) {
|
|
val tilesPixmap = Pixmap(diffuse)
|
|
val tilesGlowPixmap = if (glow != null) Pixmap(glow) else nullTile
|
|
val tilesEmissivePixmap = if (emissive != null) Pixmap(emissive) else nullTile
|
|
val blockName = diffuse.nameWithoutExtension().split('-').last().toInt() // basically a filename
|
|
val blockID = if (mode != null) "$mode@$modname:$blockName" else "$modname:$blockName"
|
|
|
|
|
|
// determine the type of the block (populate tags list)
|
|
// predefined by the image dimension: 16x16 for (1,0)
|
|
if (tilesPixmap.width == TILE_SIZE && tilesPixmap.height == TILE_SIZE) {
|
|
addTag(blockID, RenderTag.CONNECT_SELF, RenderTag.MASK_NA)
|
|
drawToAtlantes(tilesPixmap, tilesGlowPixmap, tilesEmissivePixmap, RenderTag.MASK_NA)
|
|
}
|
|
// predefined by the image dimension: 64x16 for (2,3)
|
|
else if (tilesPixmap.width == TILE_SIZE * 4 && tilesPixmap.height == TILE_SIZE) {
|
|
addTag(blockID, RenderTag.CONNECT_WALL_STICKER, RenderTag.MASK_TORCH)
|
|
drawToAtlantes(tilesPixmap, tilesGlowPixmap, tilesEmissivePixmap, RenderTag.MASK_TORCH)
|
|
}
|
|
// predefined by the image dimension: 128x16 for (3,4)
|
|
else if (tilesPixmap.width == TILE_SIZE * 8 && tilesPixmap.height == TILE_SIZE) {
|
|
addTag(blockID, RenderTag.CONNECT_WALL_STICKER_CONNECT_SELF, RenderTag.MASK_PLATFORM)
|
|
drawToAtlantes(tilesPixmap, tilesGlowPixmap, tilesEmissivePixmap, RenderTag.MASK_PLATFORM)
|
|
}
|
|
// predefined by the image dimension: 256x16
|
|
else if (tilesPixmap.width == TILE_SIZE * 16 && tilesPixmap.height == TILE_SIZE) {
|
|
addTag(blockID, RenderTag.CONNECT_SELF, RenderTag.MASK_16)
|
|
drawToAtlantes(tilesPixmap, tilesGlowPixmap, tilesEmissivePixmap, RenderTag.MASK_16)
|
|
}
|
|
// predefined by the image dimension: 256x64
|
|
else if (tilesPixmap.width == TILE_SIZE * 16 && tilesPixmap.height == TILE_SIZE * 4) {
|
|
addTag(blockID, RenderTag.CONNECT_SELF, RenderTag.MASK_16X4)
|
|
drawToAtlantes(tilesPixmap, tilesGlowPixmap, tilesEmissivePixmap, RenderTag.MASK_16X4)
|
|
}
|
|
// predefined by the image dimension: 256x256
|
|
else if (tilesPixmap.width == TILE_SIZE * 16 && tilesPixmap.height == TILE_SIZE * 16) {
|
|
addTag(blockID, RenderTag.CONNECT_SELF, RenderTag.MASK_16X16)
|
|
drawToAtlantes(tilesPixmap, tilesGlowPixmap, tilesEmissivePixmap, RenderTag.MASK_16X16)
|
|
}
|
|
// 112x112 or 224x224
|
|
else {
|
|
if (tilesPixmap.width != tilesPixmap.height && tilesPixmap.width % (7 * TILE_SIZE) >= 2) {
|
|
throw IllegalArgumentException("Unrecognized image dimension ${tilesPixmap.width}x${tilesPixmap.height} from $modname:${diffuse.name()}")
|
|
}
|
|
// figure out the tags
|
|
var connectionType = 0
|
|
var maskType = 0
|
|
for (bit in 0 until TILE_SIZE) {
|
|
val x = (7 * TILE_SIZE - 1) - bit
|
|
val y1 = 5 * TILE_SIZE; val y2 = y1 + 1
|
|
val pixel1 = (tilesPixmap.getPixel(x, y1).and(255) >= 128).toInt()
|
|
val pixel2 = (tilesPixmap.getPixel(x, y2).and(255) >= 128).toInt()
|
|
|
|
connectionType += pixel1 shl bit
|
|
maskType += pixel2 shl bit
|
|
}
|
|
|
|
addTag(blockID, connectionType, maskType)
|
|
drawToAtlantes(tilesPixmap, tilesGlowPixmap, tilesEmissivePixmap, maskType)
|
|
}
|
|
|
|
itemSheetNumbers[blockID] = itemSheetCursor
|
|
itemSheetCursor += 1
|
|
|
|
tilesPixmap.dispose()
|
|
}
|
|
|
|
fun tileIDtoAtlasNumber(tileID: ItemID) = tags[tileID]?.tileNumber
|
|
?: throw NullPointerException("AtlasNumbers mapping from $tileID does not exist")
|
|
fun tileIDtoItemSheetNumber(tileID: ItemID) = itemSheetNumbers[tileID]
|
|
?: throw NullPointerException("ItemSheetNumber mapping from $tileID does not exist")
|
|
|
|
/**
|
|
* This function must precede the drawToAtlantes() function, as the marking requires the variable
|
|
* 'atlasCursor' and the draw function modifies it!
|
|
*/
|
|
private fun addTag(id: ItemID, connectionType: Int, maskType: Int) {
|
|
if (tags.containsKey(id)) {
|
|
throw Error("Block $id already exists")
|
|
}
|
|
|
|
tags[id] = RenderTag(atlasCursor, connectionType, maskType)
|
|
tagsByTileNum[atlasCursor.toLong()] = RenderTag(atlasCursor, connectionType, maskType)
|
|
|
|
printdbg(this, "tileName ${id} ->> tileNumber ${atlasCursor}")
|
|
}
|
|
|
|
private fun drawToAtlantes(diffuse: Pixmap, glow: Pixmap, emissive: Pixmap, renderMask: Int) {
|
|
val tilesCount = RenderTag.maskTypeToTileCount(renderMask)
|
|
if (atlasCursor + tilesCount >= TOTAL_TILES) {
|
|
// throw Error("Too much tiles for $MAX_TEX_SIZE texture size: $atlasCursor")
|
|
println("[CreateTileAtlas] Too much tiles for atlas of ${MAX_TEX_SIZE}x$MAX_TEX_SIZE (tiles so far: $atlasCursor/${(MAX_TEX_SIZE*MAX_TEX_SIZE)/(TILE_SIZE* TILE_SIZE)}, tiles to be added: $tilesCount), trying to expand the atlas...")
|
|
expandAtlantes()
|
|
}
|
|
|
|
val sixSeasonal = diffuse.width == 21 * TILE_SIZE && diffuse.height == 14 * TILE_SIZE
|
|
val txOfPixmap = diffuse.width / TILE_SIZE
|
|
val txOfPixmapGlow = glow.width / TILE_SIZE
|
|
val txOfPixmapEmissive = emissive.width / TILE_SIZE
|
|
for (i in 0 until tilesCount) {
|
|
//printdbg(this, "Rendering to atlas, tile# $atlasCursor, tilesCount = $tilesCount, seasonal = $seasonal")
|
|
|
|
// different texture for different seasons (224x224)
|
|
if (sixSeasonal) {
|
|
val i = if (renderMask == MASK_47) (if (i < 41) i else i + 1) else i // to compensate the discontinuity between 40th and 41st tile
|
|
_drawToAtlantes(diffuse, atlasCursor, i % 7 , i / 7, PREVERNAL)
|
|
_drawToAtlantes(diffuse, atlasCursor, i % 7 + 7 , i / 7, VERNAL)
|
|
_drawToAtlantes(diffuse, atlasCursor, i % 7 + 14, i / 7, AESTIVAL)
|
|
|
|
_drawToAtlantes(diffuse, atlasCursor, i % 7 + 14, i / 7 + 7, SEROTINAL)
|
|
_drawToAtlantes(diffuse, atlasCursor, i % 7 + 7 , i / 7 + 7, AUTUMNAL)
|
|
_drawToAtlantes(diffuse, atlasCursor, i % 7 , i / 7 + 7, HIBERNAL)
|
|
|
|
_drawToAtlantes(glow, atlasCursor, i % 7, i / 7, GLOW)
|
|
_drawToAtlantes(emissive, atlasCursor, i % 7, i / 7, EMISSIVE)
|
|
atlasCursor += 1
|
|
}
|
|
else {
|
|
val i = if (renderMask == MASK_47) (if (i < 41) i else i + 1) else i // to compensate the discontinuity between 40th and 41st tile
|
|
_drawToAtlantes(diffuse, atlasCursor, i % txOfPixmap, i / txOfPixmap, SIX_SEASONS)
|
|
_drawToAtlantes(glow, atlasCursor, i % txOfPixmapGlow, i / txOfPixmapGlow, GLOW)
|
|
_drawToAtlantes(emissive, atlasCursor, i % txOfPixmapGlow, i / txOfPixmapGlow, EMISSIVE)
|
|
atlasCursor += 1
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* mode: 0 for all the atlantes, 1-4 for summer/autumn/winter/spring atlas
|
|
*/
|
|
private fun _drawToAtlantes(pixmap: Pixmap, destTileNum: Int, srcTileX: Int, srcTileY: Int, source: AtlasSource) {
|
|
if (source == SIX_SEASONS) {
|
|
_drawToAtlantes(pixmap, destTileNum, srcTileX, srcTileY, PREVERNAL)
|
|
_drawToAtlantes(pixmap, destTileNum, srcTileX, srcTileY, VERNAL)
|
|
_drawToAtlantes(pixmap, destTileNum, srcTileX, srcTileY, AESTIVAL)
|
|
_drawToAtlantes(pixmap, destTileNum, srcTileX, srcTileY, SEROTINAL)
|
|
_drawToAtlantes(pixmap, destTileNum, srcTileX, srcTileY, AUTUMNAL)
|
|
_drawToAtlantes(pixmap, destTileNum, srcTileX, srcTileY, HIBERNAL)
|
|
}
|
|
else {
|
|
val atlasX = (destTileNum % TILES_IN_X) * TILE_SIZE
|
|
val atlasY = (destTileNum / TILES_IN_X) * TILE_SIZE
|
|
val sourceX = srcTileX * TILE_SIZE
|
|
val sourceY = srcTileY * TILE_SIZE
|
|
|
|
//if (mode == 1) printdbg(this, "atlaspos: ($atlasX, $atlasY), srcpos: ($sourceX, $sourceY), srcpixmap = $pixmap")
|
|
|
|
when (source) {
|
|
PREVERNAL -> atlasPrevernal.drawPixmap(pixmap, sourceX, sourceY, TILE_SIZE, TILE_SIZE, atlasX, atlasY, TILE_SIZE, TILE_SIZE)
|
|
VERNAL -> atlasVernal.drawPixmap(pixmap, sourceX, sourceY, TILE_SIZE, TILE_SIZE, atlasX, atlasY, TILE_SIZE, TILE_SIZE)
|
|
AESTIVAL -> atlasAestival.drawPixmap(pixmap, sourceX, sourceY, TILE_SIZE, TILE_SIZE, atlasX, atlasY, TILE_SIZE, TILE_SIZE)
|
|
SEROTINAL -> atlasSerotinal.drawPixmap(pixmap, sourceX, sourceY, TILE_SIZE, TILE_SIZE, atlasX, atlasY, TILE_SIZE, TILE_SIZE)
|
|
AUTUMNAL -> atlasAutumnal.drawPixmap(pixmap, sourceX, sourceY, TILE_SIZE, TILE_SIZE, atlasX, atlasY, TILE_SIZE, TILE_SIZE)
|
|
HIBERNAL -> atlasHibernal.drawPixmap(pixmap, sourceX, sourceY, TILE_SIZE, TILE_SIZE, atlasX, atlasY, TILE_SIZE, TILE_SIZE)
|
|
FLUID -> atlasFluid.drawPixmap(pixmap, sourceX, sourceY, TILE_SIZE, TILE_SIZE, atlasX, atlasY, TILE_SIZE, TILE_SIZE)
|
|
GLOW -> atlasGlow.drawPixmap(pixmap, sourceX, sourceY, TILE_SIZE, TILE_SIZE, atlasX, atlasY, TILE_SIZE, TILE_SIZE)
|
|
EMISSIVE -> atlasEmissive.drawPixmap(pixmap, sourceX, sourceY, TILE_SIZE, TILE_SIZE, atlasX, atlasY, TILE_SIZE, TILE_SIZE)
|
|
else -> throw IllegalArgumentException("Unknown draw source $source")
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param tileNumber ordinal number of a tile in the texture atlas
|
|
*/
|
|
data class RenderTag(val tileNumber: Int, val connectionType: Int, val maskType: Int) {
|
|
companion object {
|
|
const val CONNECT_MUTUAL = 0
|
|
const val CONNECT_SELF = 1
|
|
const val CONNECT_WALL_STICKER = 2
|
|
const val CONNECT_WALL_STICKER_CONNECT_SELF = 3
|
|
|
|
const val MASK_NA = 0
|
|
const val MASK_16 = 1
|
|
const val MASK_47 = 2
|
|
const val MASK_TORCH = 3
|
|
const val MASK_PLATFORM = 4
|
|
const val MASK_16X4 = 5
|
|
const val MASK_16X16 = 6
|
|
|
|
fun maskTypeToTileCount(maskType: Int) = when (maskType) {
|
|
MASK_NA -> 1
|
|
MASK_16 -> 16
|
|
MASK_47 -> 47
|
|
MASK_TORCH -> 4
|
|
MASK_PLATFORM -> 8
|
|
MASK_16X4 -> 64
|
|
MASK_16X16 -> 256
|
|
else -> throw IllegalArgumentException("Unknown maskType: $maskType")
|
|
}
|
|
}
|
|
}
|
|
|
|
fun dispose() {
|
|
atlasPrevernal.dispose()
|
|
atlasVernal.dispose()
|
|
atlasAestival.dispose()
|
|
atlasSerotinal.dispose()
|
|
atlasAutumnal.dispose()
|
|
atlasHibernal.dispose()
|
|
atlasFluid.dispose()
|
|
atlasGlow.dispose()
|
|
atlasEmissive.dispose()
|
|
//itemTerrainTexture.dispose() //BlocksDrawer will dispose of it as it disposes of 'tileItemTerrain (TextureRegionPack)'
|
|
//itemTerrainTextureGlow.dispose() //BlocksDrawer will dispose of it as it disposes of 'tileItemTerrain (TextureRegionPack)'
|
|
//itemWallTexture.dispose() //BlocksDrawer will dispose of it as it disposes of 'tileItemWall (TextureRegionPack)'
|
|
//itemWallTextureGlow.dispose() //BlocksDrawer will dispose of it as it disposes of 'tileItemWall (TextureRegionPack)'
|
|
itemTerrainPixmap.dispose()
|
|
itemWallPixmap.dispose()
|
|
|
|
nullTile.dispose()
|
|
}
|
|
|
|
private enum class AtlasSource {
|
|
/*FOUR_SEASONS, SUMMER, AUTUMN, WINTER, SPRING,*/ FLUID, GLOW, EMISSIVE,
|
|
SIX_SEASONS, PREVERNAL, VERNAL, AESTIVAL, SEROTINAL, AUTUMNAL, HIBERNAL,
|
|
}
|
|
|
|
private fun expandAtlantes() {
|
|
if (MAX_TEX_SIZE >= App.glInfo.GL_MAX_TEXTURE_SIZE) {
|
|
throw RuntimeException("Cannot expand atlas: texture size is already at its maximum possible size allowed by the graphics processor (${MAX_TEX_SIZE}x${MAX_TEX_SIZE})")
|
|
}
|
|
|
|
val oldTexSize = MAX_TEX_SIZE
|
|
val newTexSize = oldTexSize * 2
|
|
|
|
|
|
MAX_TEX_SIZE = newTexSize
|
|
TILES_IN_X = MAX_TEX_SIZE / TILE_SIZE
|
|
SHADER_SIZE_KEYS = floatArrayOf(MAX_TEX_SIZE.toFloat(), MAX_TEX_SIZE.toFloat(), TILES_IN_X.toFloat(), TILES_IN_X.toFloat())
|
|
TOTAL_TILES = TILES_IN_X * TILES_IN_X
|
|
|
|
|
|
val newAtlantes = Array(8) {
|
|
Pixmap(TILES_IN_X * TILE_SIZE, TILES_IN_X * TILE_SIZE, Pixmap.Format.RGBA8888).also {
|
|
it.blending = Pixmap.Blending.None
|
|
it.filter = Pixmap.Filter.NearestNeighbour
|
|
}
|
|
}
|
|
listOf(atlasPrevernal, atlasVernal, atlasAestival, atlasSerotinal, atlasAutumnal, atlasHibernal, atlasGlow, atlasEmissive).forEachIndexed { index, pixmap ->
|
|
/*
|
|
How it works:
|
|
|
|
old: new:
|
|
AAAAAAAA AAAAAAAABBBBBBBB
|
|
BBBBBBBB CCCCCCCCDDDDDDDD
|
|
CCCCCCCC ...
|
|
DDDDDDDD
|
|
...
|
|
|
|
*/
|
|
for (scantile in 0 until pixmap.height / TILE_SIZE) {
|
|
val srcX = 0
|
|
val srcY = scantile * TILE_SIZE
|
|
val destX = (scantile % 2) * oldTexSize
|
|
val destY = (scantile / 2) * TILE_SIZE
|
|
val scanW = pixmap.width
|
|
val scanH = TILE_SIZE
|
|
|
|
newAtlantes[index].drawPixmap(pixmap, srcX, srcY, scanW, scanH, destX, destY, scanW, scanH)
|
|
}
|
|
pixmap.dispose()
|
|
}
|
|
|
|
atlasPrevernal = newAtlantes[0]
|
|
atlasVernal = newAtlantes[1]
|
|
atlasAestival = newAtlantes[2]
|
|
atlasSerotinal = newAtlantes[3]
|
|
atlasAutumnal = newAtlantes[4]
|
|
atlasHibernal = newAtlantes[5]
|
|
atlasGlow = newAtlantes[6]
|
|
atlasEmissive = newAtlantes[7]
|
|
|
|
|
|
App.setConfig("atlastexsize", newTexSize)
|
|
}
|
|
} |