Atlas reorg - packer to loader via json (#5014)
Before Width: | Height: | Size: 951 B After Width: | Height: | Size: 951 B |
Before Width: | Height: | Size: 631 B After Width: | Height: | Size: 631 B |
Before Width: | Height: | Size: 176 B After Width: | Height: | Size: 176 B |
Before Width: | Height: | Size: 832 B After Width: | Height: | Size: 832 B |
Before Width: | Height: | Size: 381 B After Width: | Height: | Size: 381 B |
Before Width: | Height: | Size: 385 B After Width: | Height: | Size: 385 B |
12
android/Images.Skin/TexturePacker.settings
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"paddingX":8,
|
||||||
|
"paddingY":8,
|
||||||
|
"duplicatePadding":true,
|
||||||
|
"maxWidth":2048,
|
||||||
|
"maxHeight":2048,
|
||||||
|
"filterMin":"Linear",
|
||||||
|
"filterMag":"Linear",
|
||||||
|
"fast":false,
|
||||||
|
"limitMemory":false,
|
||||||
|
"combineSubdirectories":true
|
||||||
|
}
|
1
android/assets/Atlases.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
[Construction,Tech,Skin,Flags]
|
@ -4,42 +4,42 @@ size: 256, 64
|
|||||||
format: RGBA8888
|
format: RGBA8888
|
||||||
filter: Linear, Linear
|
filter: Linear, Linear
|
||||||
repeat: none
|
repeat: none
|
||||||
checkbox
|
Skin/checkbox
|
||||||
rotate: false
|
rotate: false
|
||||||
xy: 160, 23
|
xy: 160, 23
|
||||||
size: 31, 31
|
size: 31, 31
|
||||||
orig: 31, 31
|
orig: 31, 31
|
||||||
offset: 0, 0
|
offset: 0, 0
|
||||||
index: -1
|
index: -1
|
||||||
checkbox-pressed
|
Skin/checkbox-pressed
|
||||||
rotate: false
|
rotate: false
|
||||||
xy: 199, 23
|
xy: 199, 23
|
||||||
size: 31, 31
|
size: 31, 31
|
||||||
orig: 31, 31
|
orig: 31, 31
|
||||||
offset: 0, 0
|
offset: 0, 0
|
||||||
index: -1
|
index: -1
|
||||||
rectangleWithOutline
|
Skin/rectangleWithOutline
|
||||||
rotate: false
|
rotate: false
|
||||||
xy: 64, 13
|
xy: 64, 13
|
||||||
size: 3, 3
|
size: 3, 3
|
||||||
orig: 3, 3
|
orig: 3, 3
|
||||||
offset: 0, 0
|
offset: 0, 0
|
||||||
index: -1
|
index: -1
|
||||||
roundedEdgeRectangle
|
Skin/roundedEdgeRectangle
|
||||||
rotate: false
|
rotate: false
|
||||||
xy: 4, 4
|
xy: 4, 4
|
||||||
size: 52, 50
|
size: 52, 50
|
||||||
orig: 52, 50
|
orig: 52, 50
|
||||||
offset: 0, 0
|
offset: 0, 0
|
||||||
index: -1
|
index: -1
|
||||||
select-box
|
Skin/select-box
|
||||||
rotate: false
|
rotate: false
|
||||||
xy: 64, 24
|
xy: 64, 24
|
||||||
size: 40, 30
|
size: 40, 30
|
||||||
orig: 40, 30
|
orig: 40, 30
|
||||||
offset: 0, 0
|
offset: 0, 0
|
||||||
index: -1
|
index: -1
|
||||||
select-box-pressed
|
Skin/select-box-pressed
|
||||||
rotate: false
|
rotate: false
|
||||||
xy: 112, 24
|
xy: 112, 24
|
||||||
size: 40, 30
|
size: 40, 30
|
||||||
|
@ -17,6 +17,7 @@ import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable
|
|||||||
import com.badlogic.gdx.utils.Align
|
import com.badlogic.gdx.utils.Align
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
|
import com.unciv.logic.GameSaver
|
||||||
import com.unciv.models.ruleset.Era
|
import com.unciv.models.ruleset.Era
|
||||||
import com.unciv.models.ruleset.Nation
|
import com.unciv.models.ruleset.Nation
|
||||||
import com.unciv.models.ruleset.Ruleset
|
import com.unciv.models.ruleset.Ruleset
|
||||||
@ -42,7 +43,7 @@ object ImageGetter {
|
|||||||
|
|
||||||
// We then shove all the drawables into a hashmap, because the atlas specifically tells us
|
// We then shove all the drawables into a hashmap, because the atlas specifically tells us
|
||||||
// that the search on it is inefficient
|
// that the search on it is inefficient
|
||||||
internal val textureRegionDrawables = HashMap<String, TextureRegionDrawable>()
|
private val textureRegionDrawables = HashMap<String, TextureRegionDrawable>()
|
||||||
|
|
||||||
fun resetAtlases() {
|
fun resetAtlases() {
|
||||||
atlases.values.forEach { it.dispose() }
|
atlases.values.forEach { it.dispose() }
|
||||||
@ -61,8 +62,8 @@ object ImageGetter {
|
|||||||
textureRegionDrawables[region.name] = drawable
|
textureRegionDrawables[region.name] = drawable
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a quickfix for #4993, since you can't .list() on a jar file. This should be fixed in a nicer way.
|
// See #4993 - you can't .list() on a jar file, so the ImagePacker leaves us the list of actual atlases.
|
||||||
val fileNames = listOf("game","Flags","Tech","Skin","Construction")
|
val fileNames = GameSaver.json().fromJson(Array<String>::class.java, Gdx.files.internal("Atlases.json"))
|
||||||
for (fileName in fileNames) {
|
for (fileName in fileNames) {
|
||||||
val file = Gdx.files.internal("$fileName.atlas")
|
val file = Gdx.files.internal("$fileName.atlas")
|
||||||
val extraAtlas = file.nameWithoutExtension()
|
val extraAtlas = file.nameWithoutExtension()
|
||||||
@ -70,10 +71,9 @@ object ImageGetter {
|
|||||||
?: TextureAtlas(file.name()).apply { // load if not
|
?: TextureAtlas(file.name()).apply { // load if not
|
||||||
atlases[extraAtlas] = this // cache the freshly loaded
|
atlases[extraAtlas] = this // cache the freshly loaded
|
||||||
}
|
}
|
||||||
val prefix = if (extraAtlas == "Skin") "Skin/" else "" // Only Skin is packed without folder prefix
|
|
||||||
for (region in tempAtlas.regions) {
|
for (region in tempAtlas.regions) {
|
||||||
val drawable = TextureRegionDrawable(region)
|
val drawable = TextureRegionDrawable(region)
|
||||||
textureRegionDrawables[prefix + region.name] = drawable
|
textureRegionDrawables[region.name] = drawable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ object ImageGetter {
|
|||||||
* getLayeredImageColored("TileSets/FantasyHex/Units/Warrior", null, Color.GOLD, Color.RED)
|
* getLayeredImageColored("TileSets/FantasyHex/Units/Warrior", null, Color.GOLD, Color.RED)
|
||||||
*
|
*
|
||||||
* All images in the atlas that match the pattern "TileSets/FantasyHex/Units/Warrior" or
|
* All images in the atlas that match the pattern "TileSets/FantasyHex/Units/Warrior" or
|
||||||
* "TileSets/FantasyHex/Units/Warrior-NUMBER" are retrieved. NUMBERs must start from 1 and
|
* "TileSets/FantasyHex/Units/Warrior-NUMBER" are retrieved. NUMBER must start from 1 and
|
||||||
* be incremented by 1 per layer. If the n-th NUMBER is missing, the (n-1)-th layer is the
|
* be incremented by 1 per layer. If the n-th NUMBER is missing, the (n-1)-th layer is the
|
||||||
* last one retrieved:
|
* last one retrieved:
|
||||||
* Given the layer names:
|
* Given the layer names:
|
||||||
|
@ -2,9 +2,12 @@ package com.unciv.app.desktop
|
|||||||
|
|
||||||
import com.badlogic.gdx.graphics.Texture
|
import com.badlogic.gdx.graphics.Texture
|
||||||
import com.badlogic.gdx.tools.texturepacker.TexturePacker
|
import com.badlogic.gdx.tools.texturepacker.TexturePacker
|
||||||
|
import com.badlogic.gdx.utils.Json
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Entry point: _ImagePacker.[packImages] ()_
|
||||||
|
*
|
||||||
* Re-packs our texture assets into atlas + png File pairs, which will be loaded by the game.
|
* Re-packs our texture assets into atlas + png File pairs, which will be loaded by the game.
|
||||||
* With the exception of the ExtraImages folder and the Font system these are the only
|
* With the exception of the ExtraImages folder and the Font system these are the only
|
||||||
* graphics used (The source Image folders are unused at run time except here).
|
* graphics used (The source Image folders are unused at run time except here).
|
||||||
@ -12,11 +15,7 @@ import java.io.File
|
|||||||
* [TexturePacker] documentation is [here](https://github.com/libgdx/libgdx/wiki/Texture-packer)
|
* [TexturePacker] documentation is [here](https://github.com/libgdx/libgdx/wiki/Texture-packer)
|
||||||
*/
|
*/
|
||||||
internal object ImagePacker {
|
internal object ImagePacker {
|
||||||
|
private fun getDefaultSettings() = TexturePacker.Settings().apply {
|
||||||
fun packImages() {
|
|
||||||
val startTime = System.currentTimeMillis()
|
|
||||||
|
|
||||||
val settings = TexturePacker.Settings()
|
|
||||||
// Apparently some chipsets, like NVIDIA Tegra 3 graphics chipset (used in Asus TF700T tablet),
|
// Apparently some chipsets, like NVIDIA Tegra 3 graphics chipset (used in Asus TF700T tablet),
|
||||||
// don't support non-power-of-two texture sizes - kudos @yuroller!
|
// don't support non-power-of-two texture sizes - kudos @yuroller!
|
||||||
// https://github.com/yairm210/UnCiv/issues/1340
|
// https://github.com/yairm210/UnCiv/issues/1340
|
||||||
@ -35,33 +34,38 @@ internal object ImagePacker {
|
|||||||
*
|
*
|
||||||
* TL;DR this should be 2048.
|
* TL;DR this should be 2048.
|
||||||
*/
|
*/
|
||||||
settings.maxWidth = 2048
|
maxWidth = 2048
|
||||||
settings.maxHeight = 2048
|
maxHeight = 2048
|
||||||
|
|
||||||
// Trying to disable the subdirectory combine lead to even worse results. Don't.
|
// Trying to disable the subdirectory combine lead to even worse results. Don't.
|
||||||
settings.combineSubdirectories = true
|
combineSubdirectories = true
|
||||||
settings.pot = true // powers of two only for width/height
|
pot = true // powers of two only for width/height
|
||||||
settings.fast = true // with pot on this just resorts by width
|
fast = true // with pot on this just sorts by width
|
||||||
// settings.rotation - do not set. Allows rotation, potentially packing tighter.
|
// settings.rotation - do not set. Allows rotation, potentially packing tighter.
|
||||||
// Proper rendering is mostly automatic - except borders which overwrite rotation.
|
// Proper rendering is mostly automatic - except borders which overwrite rotation.
|
||||||
|
|
||||||
// Set some additional padding and enable duplicatePadding to prevent image edges from bleeding into each other due to mipmapping
|
// Set some additional padding and enable duplicatePadding to prevent image edges from bleeding into each other due to mipmapping
|
||||||
settings.paddingX = 8
|
paddingX = 8
|
||||||
settings.paddingY = 8
|
paddingY = 8
|
||||||
settings.duplicatePadding = true
|
duplicatePadding = true
|
||||||
settings.filterMin = Texture.TextureFilter.MipMapLinearLinear
|
filterMin = Texture.TextureFilter.MipMapLinearLinear
|
||||||
settings.filterMag = Texture.TextureFilter.MipMapLinearLinear // I'm pretty sure this doesn't make sense for magnification, but setting it to Linear gives strange results
|
filterMag = Texture.TextureFilter.MipMapLinearLinear // I'm pretty sure this doesn't make sense for magnification, but setting it to Linear gives strange results
|
||||||
|
}
|
||||||
|
|
||||||
|
fun packImages() {
|
||||||
|
val startTime = System.currentTimeMillis()
|
||||||
|
|
||||||
|
val defaultSettings = getDefaultSettings()
|
||||||
|
|
||||||
|
// Scan for Image folders and build one atlas each
|
||||||
if (File("../Images").exists()) { // So we don't run this from within a fat JAR
|
if (File("../Images").exists()) { // So we don't run this from within a fat JAR
|
||||||
|
val atlasList = mutableListOf<String>()
|
||||||
for ((file, packFileName) in imageFolders()) {
|
for ((file, packFileName) in imageFolders()) {
|
||||||
packImagesIfOutdated(settings, file, ".", packFileName)
|
atlasList += packFileName
|
||||||
|
packImagesIfOutdated(defaultSettings, file, ".", packFileName)
|
||||||
}
|
}
|
||||||
}
|
atlasList.remove("game")
|
||||||
|
File("Atlases.json").writeText(atlasList.joinToString(",","[","]"))
|
||||||
if (File("../Skin").exists()) {
|
|
||||||
settings.filterMag = Texture.TextureFilter.Linear
|
|
||||||
settings.filterMin = Texture.TextureFilter.Linear
|
|
||||||
packImagesIfOutdated(settings, "../Skin", ".", "Skin")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pack for mods as well
|
// pack for mods as well
|
||||||
@ -69,7 +73,7 @@ internal object ImagePacker {
|
|||||||
if (modDirectory.exists()) {
|
if (modDirectory.exists()) {
|
||||||
for (mod in modDirectory.listFiles()!!) {
|
for (mod in modDirectory.listFiles()!!) {
|
||||||
if (!mod.isHidden && File(mod.path + "/Images").exists())
|
if (!mod.isHidden && File(mod.path + "/Images").exists())
|
||||||
packImagesIfOutdated(settings, mod.path + "/Images", mod.path, "game")
|
packImagesIfOutdated(defaultSettings, mod.path + "/Images", mod.path, "game")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,22 +81,31 @@ internal object ImagePacker {
|
|||||||
println("Packing textures - " + texturePackingTime + "ms")
|
println("Packing textures - " + texturePackingTime + "ms")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun packImagesIfOutdated(settings: TexturePacker.Settings, input: String, output: String, packFileName: String) {
|
// Process one Image folder, checking for atlas older than contained images first
|
||||||
|
private fun packImagesIfOutdated(defaultSettings: TexturePacker.Settings, input: String, output: String, packFileName: String) {
|
||||||
fun File.listTree(): Sequence<File> = when {
|
fun File.listTree(): Sequence<File> = when {
|
||||||
this.isFile -> sequenceOf(this)
|
this.isFile -> sequenceOf(this)
|
||||||
this.isDirectory -> this.listFiles()!!.asSequence().flatMap { it.listTree() }
|
this.isDirectory -> this.listFiles()!!.asSequence().flatMap { it.listTree() }
|
||||||
else -> sequenceOf()
|
else -> sequenceOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if outdated
|
||||||
val atlasFile = File("$output${File.separator}$packFileName.atlas")
|
val atlasFile = File("$output${File.separator}$packFileName.atlas")
|
||||||
if (atlasFile.exists() && File("$output${File.separator}$packFileName.png").exists()) {
|
if (atlasFile.exists() && File("$output${File.separator}$packFileName.png").exists()) {
|
||||||
val atlasModTime = atlasFile.lastModified()
|
val atlasModTime = atlasFile.lastModified()
|
||||||
if (File(input).listTree().none { it.extension in listOf("png", "jpg", "jpeg") && it.lastModified() > atlasModTime }) return
|
if (File(input).listTree().none { it.extension in listOf("png", "jpg", "jpeg") && it.lastModified() > atlasModTime }) return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An image folder can optionally have a TexturePacker settings file
|
||||||
|
val settingsFile = File("$input${File.separator}TexturePacker.settings")
|
||||||
|
val settings = if (settingsFile.exists())
|
||||||
|
Json().fromJson(TexturePacker.Settings::class.java, settingsFile.reader())
|
||||||
|
else defaultSettings
|
||||||
|
|
||||||
TexturePacker.process(settings, input, output, packFileName)
|
TexturePacker.process(settings, input, output, packFileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Iterator providing all Image folders to process with the destination atlas name
|
||||||
private data class ImageFolderResult(val folder: String, val atlasName: String)
|
private data class ImageFolderResult(val folder: String, val atlasName: String)
|
||||||
private fun imageFolders() = sequence {
|
private fun imageFolders() = sequence {
|
||||||
val parent = File("..")
|
val parent = File("..")
|
||||||
|