[Editor] Add support for compiled auto tiles in single asset #2

This commit is contained in:
2022-08-26 23:17:14 +02:00
parent ab22d2760d
commit b804178f69
12 changed files with 133 additions and 88 deletions

View File

@@ -4,5 +4,5 @@ import com.bartlomiejpluta.base.editor.asset.model.GraphicAsset
import com.bartlomiejpluta.base.editor.autotile.model.AutoTile import com.bartlomiejpluta.base.editor.autotile.model.AutoTile
import com.bartlomiejpluta.base.editor.project.model.Project import com.bartlomiejpluta.base.editor.project.model.Project
class AutoTileAsset(project: Project, uid: String, source: String, name: String) : class AutoTileAsset(project: Project, uid: String, source: String, name: String, rows: Int, columns: Int) :
GraphicAsset(project.autoTilesDirectoryProperty, uid, source, name, AutoTile.ROWS, AutoTile.COLUMNS) GraphicAsset(project.autoTilesDirectoryProperty, uid, source, name, rows, columns)

View File

@@ -12,17 +12,17 @@ class AutoTileAssetData {
val nameProperty = SimpleStringProperty() val nameProperty = SimpleStringProperty()
var name by nameProperty var name by nameProperty
val rowsProperty = SimpleIntegerProperty(AutoTile.ROWS) val rowsProperty = SimpleIntegerProperty(1)
var rows by rowsProperty var rows by rowsProperty
val columnsProperty = SimpleIntegerProperty(AutoTile.COLUMNS) val columnsProperty = SimpleIntegerProperty(1)
var columns by columnsProperty var columns by columnsProperty
val tileWidthProperty = SimpleIntegerProperty(1) val tileSetWidthProperty = SimpleIntegerProperty(1)
var tileWidth by tileWidthProperty var tileWidth by tileSetWidthProperty
val tileHeightProperty = SimpleIntegerProperty(1) val tileSetHeightProperty = SimpleIntegerProperty(1)
var tileHeight by tileHeightProperty var tileHeight by tileSetHeightProperty
val fileProperty = SimpleObjectProperty<File>() val fileProperty = SimpleObjectProperty<File>()
var file by fileProperty var file by fileProperty

View File

@@ -11,7 +11,7 @@ import tornadofx.div
import tornadofx.getValue import tornadofx.getValue
// Algorithm source: https://love2d.org/forums/viewtopic.php?t=7826 // Algorithm source: https://love2d.org/forums/viewtopic.php?t=7826
class AutoTile(uid: String, name: String, image: Image) { class AutoTile(uid: String, name: String, image: Image, rows: Int, columns: Int) {
val uidProperty = ReadOnlyStringWrapper(uid) val uidProperty = ReadOnlyStringWrapper(uid)
val uid by uidProperty val uid by uidProperty
@@ -21,43 +21,52 @@ class AutoTile(uid: String, name: String, image: Image) {
val imageProperty = SimpleObjectProperty(image) val imageProperty = SimpleObjectProperty(image)
val image by imageProperty val image by imageProperty
val rowsProperty = SimpleIntegerProperty(ROWS) val rowsProperty = SimpleIntegerProperty(rows)
val rows by rowsProperty val rows by rowsProperty
val columnsProperty = SimpleIntegerProperty(COLUMNS) val columnsProperty = SimpleIntegerProperty(columns)
val columns by columnsProperty val columns by columnsProperty
val tileWidthProperty = SimpleIntegerProperty(image.width.toInt() / columns) val tileSetWidthProperty = SimpleIntegerProperty(image.width.toInt() / columns)
val tileSetWidth by tileSetWidthProperty
val tileSetHeightProperty = SimpleIntegerProperty(image.height.toInt() / rows)
val tileSetHeight by tileSetHeightProperty
val tileWidthProperty = tileSetWidthProperty.div(COLUMNS)
val tileWidth by tileWidthProperty val tileWidth by tileWidthProperty
val tileHeightProperty = SimpleIntegerProperty(image.height.toInt() / rows) val tileHeightProperty = tileSetHeightProperty.div(ROWS)
val tileHeight by tileHeightProperty val tileHeight by tileHeightProperty
val widthProperty = SimpleIntegerProperty(tileWidth * columns) val widthProperty = SimpleIntegerProperty(image.width.toInt())
val width by widthProperty val width by widthProperty
val heightProperty = SimpleIntegerProperty(tileHeight * rows) val heightProperty = SimpleIntegerProperty(image.height.toInt() )
val height by heightProperty val height by heightProperty
val halfWidthProperty = tileWidthProperty.div(2)
val halfWidth by halfWidthProperty
val halfHeightProperty = tileWidthProperty.div(2) val islandSubTiles: Array<Array<Image>>
val halfHeight by halfHeightProperty val topLeftSubTiles: Array<Array<Image>>
val topRightSubTiles: Array<Array<Image>>
val islandSubTiles: Array<Image> val bottomLeftSubTiles: Array<Array<Image>>
val topLeftSubTiles: Array<Image> val bottomRightSubTiles: Array<Array<Image>>
val topRightSubTiles: Array<Image>
val bottomLeftSubTiles: Array<Image>
val bottomRightSubTiles: Array<Image>
init { init {
val islandTile = cropTile(0, 0) val islandSubTiles: MutableList<Array<Image>> = mutableListOf()
val crossTile = cropTile(1, 0) val topLeftSubTiles: MutableList<Array<Image>> = mutableListOf()
val topLeftCornerTile = cropTile(0, 1) val topRightSubTiles: MutableList<Array<Image>> = mutableListOf()
val topRightCornerTile = cropTile(1, 1) val bottomLeftSubTiles: MutableList<Array<Image>> = mutableListOf()
val bottomLeftCornerTile = cropTile(0, 2) val bottomRightSubTiles: MutableList<Array<Image>> = mutableListOf()
val bottomRightCornerTile = cropTile(1, 2)
for (i in 0 until columns * rows) {
val tileSet = cropTileSet(i)
val islandTile = cropTile(tileSet, 0, 0)
val crossTile = cropTile(tileSet, 1, 0)
val topLeftCornerTile = cropTile(tileSet, 0, 1)
val topRightCornerTile = cropTile(tileSet, 1, 1)
val bottomLeftCornerTile = cropTile(tileSet, 0, 2)
val bottomRightCornerTile = cropTile(tileSet, 1, 2)
/* /*
* Indexes: * Indexes:
@@ -73,11 +82,17 @@ class AutoTile(uid: String, name: String, image: Image) {
val (tl2, tr4, bl0, br1) = cutSubTiles(bottomLeftCornerTile) val (tl2, tr4, bl0, br1) = cutSubTiles(bottomLeftCornerTile)
val (tl4, tr1, bl2, br0) = cutSubTiles(bottomRightCornerTile) val (tl4, tr1, bl2, br0) = cutSubTiles(bottomRightCornerTile)
islandSubTiles = cutSubTiles(islandTile) islandSubTiles += cutSubTiles(islandTile)
topLeftSubTiles = arrayOf(tl0, tl1, tl2, tl3, tl4) topLeftSubTiles += arrayOf(tl0, tl1, tl2, tl3, tl4)
topRightSubTiles = arrayOf(tr0, tr1, tr2, tr3, tr4) topRightSubTiles += arrayOf(tr0, tr1, tr2, tr3, tr4)
bottomLeftSubTiles = arrayOf(bl0, bl1, bl2, bl3, bl4) bottomLeftSubTiles += arrayOf(bl0, bl1, bl2, bl3, bl4)
bottomRightSubTiles = arrayOf(br0, br1, br2, br3, br4) bottomRightSubTiles += arrayOf(br0, br1, br2, br3, br4)
}
this.islandSubTiles = islandSubTiles.toTypedArray()
this.topLeftSubTiles = topLeftSubTiles.toTypedArray()
this.topRightSubTiles = topRightSubTiles.toTypedArray()
this.bottomLeftSubTiles = bottomLeftSubTiles.toTypedArray()
this.bottomRightSubTiles = bottomRightSubTiles.toTypedArray()
} }
fun getTile(layer: AutoTileLayer, row: Int, column: Int): Array<Image> { fun getTile(layer: AutoTileLayer, row: Int, column: Int): Array<Image> {
@@ -86,59 +101,71 @@ class AutoTile(uid: String, name: String, image: Image) {
var bottomLeft = 0 var bottomLeft = 0
var bottomRight = 0 var bottomRight = 0
val id = layer.layer[row][column]
// Top // Top
if (row > 0 && layer.layer[row - 1][column]) { if (row > 0 && layer.layer[row - 1][column] > 0) {
topLeft += 2 topLeft += 2
topRight += 1 topRight += 1
} }
// Bottom // Bottom
if (row < layer.rows - 1 && layer.layer[row + 1][column]) { if (row < layer.rows - 1 && layer.layer[row + 1][column] > 0) {
bottomLeft += 1 bottomLeft += 1
bottomRight += 2 bottomRight += 2
} }
// Left // Left
if (column > 0 && layer.layer[row][column - 1]) { if (column > 0 && layer.layer[row][column - 1] > 0) {
topLeft += 1 topLeft += 1
bottomLeft += 2 bottomLeft += 2
} }
// Right // Right
if (column < layer.columns - 1 && layer.layer[row][column + 1]) { if (column < layer.columns - 1 && layer.layer[row][column + 1] > 0) {
topRight += 2 topRight += 2
bottomRight += 1 bottomRight += 1
} }
// Top left // Top left
if (row > 0 && column > 0 && layer.layer[row - 1][column - 1] && topLeft == 3) { if (row > 0 && column > 0 && layer.layer[row - 1][column - 1] > 0 && topLeft == 3) {
topLeft = 4 topLeft = 4
} }
// Top right // Top right
if (row > 0 && column < layer.columns - 1 && layer.layer[row - 1][column + 1] && topRight == 3) { if (row > 0 && column < layer.columns - 1 && layer.layer[row - 1][column + 1] > 0 && topRight == 3) {
topRight = 4 topRight = 4
} }
// Bottom left // Bottom left
if (row < layer.rows - 1 && column > 0 && layer.layer[row + 1][column - 1] && bottomLeft == 3) { if (row < layer.rows - 1 && column > 0 && layer.layer[row + 1][column - 1] > 0 && bottomLeft == 3) {
bottomLeft = 4 bottomLeft = 4
} }
// Bottom right // Bottom right
if (row < layer.rows - 1 && column < layer.columns - 1 && layer.layer[row + 1][column + 1] && bottomRight == 3) { if (row < layer.rows - 1 && column < layer.columns - 1 && layer.layer[row + 1][column + 1] > 0 && bottomRight == 3) {
bottomRight = 4 bottomRight = 4
} }
if (topLeft == 0 && topRight == 0 && bottomLeft == 0 && bottomRight == 0) { if (topLeft == 0 && topRight == 0 && bottomLeft == 0 && bottomRight == 0) {
return islandSubTiles return islandSubTiles[id - 1]
} }
return arrayOf(topLeftSubTiles[topLeft], topRightSubTiles[topRight], bottomLeftSubTiles[bottomLeft], bottomRightSubTiles[bottomRight]) return arrayOf(
topLeftSubTiles[id - 1][topLeft],
topRightSubTiles[id - 1][topRight],
bottomLeftSubTiles[id - 1][bottomLeft],
bottomRightSubTiles[id - 1][bottomRight]
)
} }
private fun cropTile(column: Int, row: Int) = private fun cropTileSet(id: Int): Image {
ImageUtil.cropImage(image, column * tileWidth, row * tileHeight, tileWidth, tileHeight) return ImageUtil.cropImage(image, tileSetWidth * (id % columns), tileSetHeight * (id / columns), tileSetWidth, tileSetHeight)
}
private fun cropTile(tileSet: Image, column: Int, row: Int): Image {
return ImageUtil.cropImage(tileSet, column * tileWidth, row * tileHeight, tileWidth, tileHeight)
}
private fun cutSubTiles(tile: Image): Array<Image> { private fun cutSubTiles(tile: Image): Array<Image> {
val halfWidth = tileWidth / 2 val halfWidth = tileWidth / 2

View File

@@ -23,11 +23,25 @@ class ImportAutoTileFragment : Fragment("Import Auto Tile") {
} }
} }
imagePreview.addListener { _, _, image -> dataVM.tileSetWidthProperty.addListener { _, _, width ->
image?.let { dataVM.columns = (imagePreview.value?.width?.toInt() ?: 1) / width.toInt()
dataVM.tileWidth = it.width.toInt() / dataVM.columns
dataVM.tileHeight = it.height.toInt() / dataVM.rows
} }
dataVM.tileSetHeightProperty.addListener { _, _, height ->
dataVM.rows = (imagePreview.value?.height?.toInt() ?: 1) / height.toInt()
}
dataVM.columnsProperty.addListener { _, _, columns ->
dataVM.tileSetWidth = (imagePreview.value?.width?.toInt() ?: 1) / columns.toInt()
}
dataVM.rowsProperty.addListener { _, _, rows ->
dataVM.tileSetHeight = (imagePreview.value?.height?.toInt() ?: 1) / rows.toInt()
}
imagePreview.addListener { _, _, _ ->
dataVM.columns = 1
dataVM.rows = 1
} }
} }
@@ -76,7 +90,7 @@ class ImportAutoTileFragment : Fragment("Import Auto Tile") {
} }
field("Auto Tile Rows") { field("Auto Tile Rows") {
enableWhen(false.toProperty()) enableWhen(imagePreview.isNotNull)
spinner(min = 1, max = Integer.MAX_VALUE, property = dataVM.rowsProperty, editable = true) { spinner(min = 1, max = Integer.MAX_VALUE, property = dataVM.rowsProperty, editable = true) {
required() required()
editor.textFormatter = TextFieldUtil.integerFormatter(dataVM.rows) editor.textFormatter = TextFieldUtil.integerFormatter(dataVM.rows)
@@ -84,24 +98,24 @@ class ImportAutoTileFragment : Fragment("Import Auto Tile") {
} }
field("Auto Tile Columns") { field("Auto Tile Columns") {
enableWhen(false.toProperty()) enableWhen(imagePreview.isNotNull)
spinner(min = 1, max = Integer.MAX_VALUE, property = dataVM.columnsProperty, editable = true) { spinner(min = 1, max = Integer.MAX_VALUE, property = dataVM.columnsProperty, editable = true) {
required() required()
editor.textFormatter = TextFieldUtil.integerFormatter(dataVM.columns) editor.textFormatter = TextFieldUtil.integerFormatter(dataVM.columns)
} }
} }
field("Tile width") { field("Tile Set width") {
enableWhen(false.toProperty()) enableWhen(imagePreview.isNotNull)
spinner(min = 1, max = Integer.MAX_VALUE, property = dataVM.tileWidthProperty, editable = true) { spinner(min = 1, max = Integer.MAX_VALUE, property = dataVM.tileSetWidthProperty, editable = true) {
required() required()
editor.textFormatter = TextFieldUtil.integerFormatter(dataVM.rows) editor.textFormatter = TextFieldUtil.integerFormatter(dataVM.rows)
} }
} }
field("Tile height") { field("Tile Set height") {
enableWhen(false.toProperty()) enableWhen(imagePreview.isNotNull)
spinner(min = 1, max = Integer.MAX_VALUE, property = dataVM.tileHeightProperty, editable = true) { spinner(min = 1, max = Integer.MAX_VALUE, property = dataVM.tileSetHeightProperty, editable = true) {
required() required()
editor.textFormatter = TextFieldUtil.integerFormatter(dataVM.columns) editor.textFormatter = TextFieldUtil.integerFormatter(dataVM.columns)
} }

View File

@@ -13,11 +13,11 @@ class AutoTileAssetDataVM : ItemViewModel<AutoTileAssetData>(AutoTileAssetData()
val columnsProperty = bind(AutoTileAssetData::columnsProperty) val columnsProperty = bind(AutoTileAssetData::columnsProperty)
var columns by columnsProperty var columns by columnsProperty
val tileWidthProperty = bind(AutoTileAssetData::tileWidthProperty) val tileSetWidthProperty = bind(AutoTileAssetData::tileSetWidthProperty)
var tileWidth by tileWidthProperty var tileSetWidth by tileSetWidthProperty
val tileHeightProperty = bind(AutoTileAssetData::tileHeightProperty) val tileSetHeightProperty = bind(AutoTileAssetData::tileSetHeightProperty)
var tileHeight by tileHeightProperty var tileSetHeight by tileSetHeightProperty
val fileProperty = bind(AutoTileAssetData::fileProperty) val fileProperty = bind(AutoTileAssetData::fileProperty)
var file by fileProperty var file by fileProperty

View File

@@ -14,7 +14,7 @@ class AutoTilePaintingTrace(val map: GameMapVM, override val commandName: String
override var executed = false override var executed = false
private set private set
private fun paint(layerIndex: Int, row: Int, column: Int, tile: Boolean) { private fun paint(layerIndex: Int, row: Int, column: Int, tile: Int) {
if (row >= map.rows || column >= map.columns || row < 0 || column < 0 || layerIndex < 0) { if (row >= map.rows || column >= map.columns || row < 0 || column < 0 || layerIndex < 0) {
return return
} }
@@ -45,9 +45,9 @@ class AutoTilePaintingTrace(val map: GameMapVM, override val commandName: String
editorStateVM.cursorRow - centerRow + row, editorStateVM.cursorRow - centerRow + row,
editorStateVM.cursorColumn - centerColumn + column, editorStateVM.cursorColumn - centerColumn + column,
when { when {
brushVM.mode == BrushMode.ERASING_MODE -> false brushVM.mode == BrushMode.ERASING_MODE -> 0
mouseEvent.button == MouseButton.PRIMARY -> true mouseEvent.button == MouseButton.PRIMARY -> 1
else -> false else -> 0
} }
) )
} }
@@ -60,9 +60,9 @@ class AutoTilePaintingTrace(val map: GameMapVM, override val commandName: String
editorStateVM.cursorRow - centerRow + row, editorStateVM.cursorRow - centerRow + row,
editorStateVM.cursorColumn - centerColumn + column, editorStateVM.cursorColumn - centerColumn + column,
when { when {
brushVM.mode == BrushMode.ERASING_MODE -> false brushVM.mode == BrushMode.ERASING_MODE -> 0
mouseEvent.button == MouseButton.PRIMARY -> true mouseEvent.button == MouseButton.PRIMARY -> 1
else -> false else -> 0
} }
) )
} }
@@ -91,8 +91,8 @@ class AutoTilePaintingTrace(val map: GameMapVM, override val commandName: String
val layerIndex: Int, val layerIndex: Int,
val row: Int, val row: Int,
val column: Int, val column: Int,
val formerTile: Boolean, val formerTile: Int,
val tile: Boolean val tile: Int
) )
} }
} }

View File

@@ -130,7 +130,7 @@ class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, pr
private fun renderAutoTileLayer(gc: GraphicsContext, layer: AutoTileLayer) { private fun renderAutoTileLayer(gc: GraphicsContext, layer: AutoTileLayer) {
for ((row, columns) in layer.layer.withIndex()) { for ((row, columns) in layer.layer.withIndex()) {
for ((column, tile) in columns.withIndex()) { for ((column, tile) in columns.withIndex()) {
if(tile) { if(tile > 0) {
renderAutoTile(gc, layer.autoTile, layer, column, row) renderAutoTile(gc, layer.autoTile, layer, column, row)
} }
} }

View File

@@ -14,7 +14,7 @@ class AutoTileLayer(
rows: Int, rows: Int,
columns: Int, columns: Int,
autoTileAsset: AutoTileAsset, autoTileAsset: AutoTileAsset,
layer: Array<Array<Boolean>> = Array(rows) { Array(columns) { false } } layer: Array<Array<Int>> = Array(rows) { Array(columns) { 0 } }
) : Layer { ) : Layer {
var layer = layer var layer = layer
private set private set
@@ -29,7 +29,7 @@ class AutoTileLayer(
var autoTileAsset by autoTileAssetProperty var autoTileAsset by autoTileAssetProperty
val autoTileProperty = Bindings.createObjectBinding({ val autoTileProperty = Bindings.createObjectBinding({
autoTileAsset.file.inputStream().use { fis -> AutoTile(autoTileAsset.uid, autoTileAsset.name, Image(fis)) } autoTileAsset.file.inputStream().use { fis -> AutoTile(autoTileAsset.uid, autoTileAsset.name, Image(fis), autoTileAsset.rows, autoTileAsset.columns) }
}, autoTileAssetProperty) }, autoTileAssetProperty)
val autoTile by autoTileProperty val autoTile by autoTileProperty
@@ -41,7 +41,7 @@ class AutoTileLayer(
layer = Array(rows) { row -> layer = Array(rows) { row ->
Array(columns) { column -> Array(columns) { column ->
layer.getOrNull(row)?.getOrNull(column) ?: false layer.getOrNull(row)?.getOrNull(column) ?: 0
} }
} }
} }

View File

@@ -68,7 +68,7 @@ class ProtobufMapDeserializer : MapDeserializer {
} }
private fun deserializeAutoTileLayer(rows: Int, columns: Int, proto: GameMapProto.Layer, replaceTileSet: (String, String) -> String): AutoTileLayer { private fun deserializeAutoTileLayer(rows: Int, columns: Int, proto: GameMapProto.Layer, replaceTileSet: (String, String) -> String): AutoTileLayer {
val layer: Array<Array<Boolean>> = Array(rows) { Array(columns) { false } } val layer: Array<Array<Int>> = Array(rows) { Array(columns) { 0 } }
val autoTile = projectContext.findAutoTileAsset(replaceTileSet(proto.name, proto.autoTileLayer.autotileUID)) val autoTile = projectContext.findAutoTileAsset(replaceTileSet(proto.name, proto.autoTileLayer.autotileUID))
proto.autoTileLayer.tilesList.forEachIndexed { index, tile -> proto.autoTileLayer.tilesList.forEachIndexed { index, tile ->

View File

@@ -188,7 +188,7 @@ class DefaultProjectContext : ProjectContext {
val source = "$uid.${data.file.extension}" val source = "$uid.${data.file.extension}"
val targetFile = File(it.autoTilesDirectory, source) val targetFile = File(it.autoTilesDirectory, source)
data.file.copyTo(targetFile) data.file.copyTo(targetFile)
it.autoTiles += AutoTileAsset(it, uid, source, data.name) it.autoTiles += AutoTileAsset(it, uid, source, data.name, data.rows, data.columns)
save() save()
} }
@@ -202,7 +202,7 @@ class DefaultProjectContext : ProjectContext {
val image = File(it.autoTilesDirectory, asset.source).inputStream().use { fis -> Image(fis) } val image = File(it.autoTilesDirectory, asset.source).inputStream().use { fis -> Image(fis) }
AutoTile(uid, asset.name, image) AutoTile(uid, asset.name, image, asset.rows, asset.columns)
} ?: throw IllegalStateException("There is no open project in the context") } ?: throw IllegalStateException("There is no open project in the context")
} }

View File

@@ -56,7 +56,9 @@ class ProtobufProjectDeserializer : ProjectDeserializer {
project = project, project = project,
uid = autoTile.uid, uid = autoTile.uid,
source = autoTile.source, source = autoTile.source,
name = autoTile.name name = autoTile.name,
rows = autoTile.rows,
columns = autoTile.columns
) )
private fun deserializeImage(project: Project, image: ProjectProto.ImageAsset) = ImageAsset( private fun deserializeImage(project: Project, image: ProjectProto.ImageAsset) = ImageAsset(

View File

@@ -53,6 +53,8 @@ class ProtobufProjectSerializer : ProjectSerializer {
.setUid(autoTile.uid) .setUid(autoTile.uid)
.setSource(autoTile.source) .setSource(autoTile.source)
.setName(autoTile.name) .setName(autoTile.name)
.setRows(autoTile.rows)
.setColumns(autoTile.columns)
.build() .build()
private fun serializeImage(image: ImageAsset) = ProjectProto.ImageAsset.newBuilder() private fun serializeImage(image: ImageAsset) = ProjectProto.ImageAsset.newBuilder()