Search code examples
algorithmkotlinlibgdxtexture-packing

I need help on implementing an algorithm that will parse specific textures from a texture in libGDX


Consider:

import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.graphics.g2d.TextureRegion
import java.lang.Exception
import kotlin.math.floor

lateinit var AssetMap: MutableMap<String, Texture>
lateinit var SheetAssets: MutableMap<String, MutableMap<String, TextureRegion>?>
object Assets {
    lateinit var fileHandle: FileHandle
    fun loadAssets()
    {
        loadAssets(DefaultAssetDirectory)
    }
    fun loadAssets(directory: String)
    {
        AssetMap = mapOf<String, Texture>().toMutableMap() // Current implementation resets the map at load
        fileHandle = Gdx.files.local(directory)
        var fileList = fileHandle.list()
        for(file in fileList)
        {
            if(file.extension() == "png")
                try
                {
                    AssetMap[file.nameWithoutExtension()] = Texture(directory+file.name())
                }
                catch(e: Exception) {

                }
        }
    }

    /**
     * For very basic sprite sheets.
     * Since you won't input a name List in this function, they will be named by their index.
     * The function will probably start scanning from top left 0,0
     * @param directoryToSheet local directory to sheet
     * @param spaceBetweenX only the space between textures, doesnt work forspace between the texture and the border
     * @param spaceBetweenY only the space between textures, doesnt work forspace between the texture and the border
     */
    fun loadSheetAtlas(directoryToSheet: String, width: Int, height: Int, spaceBetweenX: Int, spaceBetweenY: Int, spritesX: Int, spritesY: Int)
    {
        loadSheetAtlas(directoryToSheet, null, width, height, spaceBetweenX, spaceBetweenY, spritesX, spritesY)
    }

    /**
     * For very basic sprite sheets.
     * Since you won't input a name List in this function, they will be named by their index.
     * @param directoryToSheet local directory to sheet
     * @param names if this is null, they will be named by their index
     * @param spaceBetweenX only the space between textures, doesnt work forspace between the texture and the border
     * @param spaceBetweenY only the space between textures, doesnt work forspace between the texture and the border
     */
    fun loadSheetAtlas(directoryToSheet: String, names: List<String?>?, width: Int, height: Int, spaceBetweenX: Int, spaceBetweenY: Int, spritesX: Int, spritesY: Int)
    {
        SheetAssets = mutableMapOf<String, MutableMap<String,TextureRegion>?>()
        var nameToConvert = Gdx.files.local(directoryToSheet).nameWithoutExtension()
        nameToConvert = nameToConvert.replace(' ', '-').toLowerCase()
        var sheet = Texture(directoryToSheet)
        var nameIndex = 0
        if(!SheetAssets.keys.contains(nameToConvert)) // To see if we have parsed it already
        {
            SheetAssets[nameToConvert] = mutableMapOf<String, TextureRegion>()
            for(x in 0 until spritesX)
            {
                for(y in 0 until spritesY) // When I put a -1 to spritesX/Y, the keycount becomes 121 for 12,12?
                {   // I check for if 'names' is null every single loop. It can be optimised to check for once, but that'd be premature optimisation
                    var keyToAdd = if((names != null) && (names[nameIndex] != null) ) names[nameIndex]!! else nameIndex.toString()
                    if(x == spritesX - 1)
                    {
                        var textureToAdd = TextureRegion(sheet,
                            ((spritesX+spaceBetweenX))-spaceBetweenX,
                            ((spritesY+spaceBetweenY))-spaceBetweenY,
                            width,height)
                        SheetAssets[nameToConvert]!![keyToAdd] = textureToAdd
                    }
                    else // These will work if textures use bottom left origin point
                    {
                        var textureToAdd = TextureRegion(sheet,  // These don’t work. Continue from here
                            ((spritesX+spaceBetweenX)*spritesX),
                            ((spritesY+spaceBetweenY)*spritesY),
                            width,height)
                        SheetAssets[nameToConvert]!![keyToAdd] = textureToAdd
                    }
                    nameIndex++
                }
            }
        }
    }
}

This is the code that I wrote. It for some reason only returns a specific texture from a specific area in the entire texture. However, the width and the height is right.

Now to explain this problem, it can be basically solved as xPos = texture.width / texture size % texture size something. But the problem here is that there are offsets. offsets in-between textures and absolutely not inbetween textures and borders. You can see my code above. I tried adding the offset, subtracting the offset, etc., but it doesn’t seem to work... any help?

Also with the current implementation, the only thing that the mutable has inside is the textureRegion found within 144,144, width: 16, height:16 originpoint:topleft of the spritesheet

I think this is called spritesheet cutting or spritesheet cutter.

Code illustration

Code illustration


Solution

  • This is the best I can do from your explanation. I think you're over-complicating how to define the regions. This matches your description. No reason to treat the last row differently than the rest.

    You also should make SheetAssets a val instead of lateinit var that you're redefining every time this function is called. That is leaking all your old textures and making them inaccessible.

    And I used an if-statement with early return to reduce nested code. And I used getOrPut and ?: to eliminate some of your redundant null-assertions.

    val SheetAssets = mutableMapOf<String, MutableMap<String,TextureRegion>>()
    
    //...
    
    fun loadSheetAtlas(directoryToSheet: String, names: List<String?>?, width: Int, height: Int, spaceBetweenX: Int, spaceBetweenY: Int, spritesX: Int, spritesY: Int)
    {
        val nameToConvert = Gdx.files.local(directoryToSheet).nameWithoutExtension()
            .replace(' ', '-').toLowerCase()
        if (nameToConvert in SheetAssets.keys) {
            return
        }
        val sheet = Texture(directoryToSheet)
        var nameIndex = 0
        for(x in 0 until spritesX)
        {
            for(y in 0 until spritesY)
            {   
                val keyToAdd = names?.get(nameIndex) ?: nameIndex.toString()            
                val outMap = SheetAssets.getOrPut(nameToConvert) { mutableMapOf() }
                outMap[keyToAdd] = TextureRegion(
                    sheet,
                    x * (spaceBetweenX + width),
                    y * (spaceBetweenY + height),
                    width,
                    height
                )
                nameIndex++
            }
        }
    
    }