package ru.dbotthepony.kstarbound.defs.dungeon import com.google.common.collect.ImmutableList import it.unimi.dsi.fastutil.ints.IntAVLTreeSet import it.unimi.dsi.fastutil.ints.IntArrayList import it.unimi.dsi.fastutil.objects.ObjectArrayList import org.lwjgl.stb.STBImage import org.lwjgl.system.MemoryUtil import ru.dbotthepony.kommons.arrays.Object2DArray import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.kstarbound.math.vector.Vector2i import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.image.Image import java.lang.ref.Reference class ImagePartReader(part: DungeonPart, val images: ImmutableList) : PartReader(part) { override val size: Vector2i get() = if (images.isEmpty()) Vector2i.ZERO else images.first().size // ObjectArrayList doesn't check for concurrent modifications private val layers = ObjectArrayList() private class Layer(val palette: Array, val data: VectorizedBitSet) override fun bind(def: DungeonDefinition) { check(def.tiles.isNotEmpty) { "Image parts require 'tiles' palette to be present in .dungeon definition" } for ((i, image) in images.withIndex()) { // go around image cache, since image will be loaded exactly once // and then forgotten val (bytes, width, height) = Image.readImageDirect(image.source) val tileData = IntArray(width * height) for (x in 0 until image.width) { for (y in 0 until image.height) { val offset = (x + y * image.width) * 4 // flip image as we go tileData[x + (image.height - y - 1) * image.width] = bytes[offset].toInt().and(0xFF) or bytes[offset + 1].toInt().and(0xFF).shl(8) or bytes[offset + 2].toInt().and(0xFF).shl(16) or bytes[offset + 3].toInt().and(0xFF).shl(24) } } // determine unique tiles val uniqueTiles = IntAVLTreeSet() for (y in 0 until image.height) { for (x in 0 until image.width) { val color = tileData[x + y * image.width] if (uniqueTiles.add(color)) { if (part.dungeon.tiles[color] == null) { val parse = RGBAColor.abgr(color) throw IllegalStateException("Unknown tile inside ${image.path} at $x, $y with color [${parse.redInt}, ${parse.greenInt}, ${parse.blueInt}, ${parse.alphaInt}] (index $color)") } } } } // construct palette val indices = IntArrayList(uniqueTiles) val palette = Array(indices.size) { part.dungeon.tiles[indices.getInt(it)]!! } val data = VectorizedBitSet(32 - Integer.numberOfLeadingZeros(uniqueTiles.size), image.width, image.height) // fill data for (y in 0 until image.height) { for (x in 0 until image.width) { val index = indices.indexOf(tileData[x + y * image.width]) check(index != -1) data[x, y] = index } } layers.add(Layer(palette, data)) } } override fun walkTiles(callback: TileCallback): KOptional { for (layer in layers) { // walk bottom-top first, this way we will place bottom objects/tiles before top ones for (y in 0 until layer.data.height) { for (x in 0 until layer.data.width) { val get = callback(x, y, layer.palette[layer.data[x, y]]) if (get.isPresent) return get } } } return KOptional() } override fun walkTilesAt(x: Int, y: Int, callback: TileCallback): KOptional { for (layer in layers) { if (x in 0 until layer.data.width && y in 0 until layer.data.height) { val get = callback(x, y, layer.palette[layer.data[x, y]]) if (get.isPresent) return get } } return KOptional() } }