[Editor] Enable displaying map

This commit is contained in:
2021-02-02 22:06:32 +01:00
parent 73d0d94553
commit f69823266b
15 changed files with 301 additions and 9 deletions

View File

@@ -34,4 +34,5 @@ compileTestKotlin {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib"
implementation "no.tornado:tornadofx:${tornadoFxVersion}"
implementation "org.joml:joml:${jomlVersion}"
}

View File

@@ -1,16 +1,10 @@
package com.bartlomiejpluta.base.editor
import com.bartlomiejpluta.base.editor.view.main.MainView
import tornadofx.*
class MyView : View() {
override val root = vbox {
button("Press me")
label("Waiting")
}
}
class EditorApp : App(MyView::class)
class EditorApp : App(MainView::class)
fun main(args: Array<String>) {
launch<EditorApp>(args)

View File

@@ -0,0 +1,18 @@
package com.bartlomiejpluta.base.editor.controller.map
import com.bartlomiejpluta.base.editor.controller.tileset.TileSetController
import com.bartlomiejpluta.base.editor.model.map.GameMap
import com.bartlomiejpluta.base.editor.model.map.tileset.TileSet
import tornadofx.Controller
class MapController : Controller() {
private val tileSetController: TileSetController by inject()
val map = GameMap(tileSetController.tileset, 20, 20)
.createTileLayer(0)
.createTileLayer(3, 5)
.createTileLayer(3, 5)
.createTileLayer(3, 5)
.createTileLayer(3, 5)
.createTileLayer(3, 5)
}

View File

@@ -0,0 +1,8 @@
package com.bartlomiejpluta.base.editor.controller.tileset
import com.bartlomiejpluta.base.editor.model.map.tileset.TileSet
import tornadofx.Controller
class TileSetController : Controller() {
val tileset = TileSet(resources.image("/textures/tileset.png"), 160, 8)
}

View File

@@ -0,0 +1,5 @@
package com.bartlomiejpluta.base.editor.model.map.layer
import com.bartlomiejpluta.base.editor.view.render.Renderable
interface Layer : Renderable

View File

@@ -0,0 +1,19 @@
package com.bartlomiejpluta.base.editor.model.map.layer
import com.bartlomiejpluta.base.editor.model.map.tileset.Tile
import javafx.scene.canvas.GraphicsContext
class TileLayer(private val layer: Array<Array<Tile?>>) : Layer {
fun setTile(row: Int, column: Int, tile: Tile?) = apply { layer[row][column] = tile }
override fun render(gc: GraphicsContext) {
for ((row, columns) in layer.withIndex()) {
for ((column, tile) in columns.withIndex()) {
if (tile != null) {
gc.drawImage(tile.image, column * tile.image.width, row * tile.image.height)
}
}
}
}
}

View File

@@ -0,0 +1,74 @@
package com.bartlomiejpluta.base.editor.model.map
import com.bartlomiejpluta.base.editor.model.map.layer.Layer
import com.bartlomiejpluta.base.editor.model.map.layer.TileLayer
import com.bartlomiejpluta.base.editor.model.map.tileset.Tile
import com.bartlomiejpluta.base.editor.model.map.tileset.TileSet
import com.bartlomiejpluta.base.editor.view.render.Renderable
import javafx.beans.property.SimpleIntegerProperty
import javafx.scene.canvas.GraphicsContext
class Grid(private val tileSet: TileSet, private val rows: Int, private val columns: Int) : Renderable {
private var tileWidth = tileSet.tileWidth.toDouble()
private var tileHeight = tileSet.tileHeight.toDouble()
private var mapWidth = columns * tileWidth
private var mapHeight = rows * tileHeight
override fun render(gc: GraphicsContext) {
gc.lineWidth = LINE_WIDTH
for (row in 0 until rows) {
gc.strokeLine(0.0, row * tileHeight, mapWidth, row * tileHeight)
}
for (column in 0 until columns) {
gc.strokeLine(column * tileWidth, 0.0, column * tileWidth, mapHeight)
}
}
companion object {
const val LINE_WIDTH = 1.5
}
}
class GameMap(private val tileSet: TileSet, private val rows: Int, private val columns: Int) : Renderable {
val layers = mutableListOf<Layer>()
private val grid = Grid(tileSet, rows, columns)
val width = columns * tileSet.tileWidth
val height = columns * tileSet.tileWidth
fun createTileLayer(tile: Int) = createTileLayer().apply {
val layerId = layers.size - 1
for (row in 0 until rows) {
for (column in 0 until columns) {
setTile(layerId, row, column, tile)
}
}
}
fun createTileLayer(tileRow: Int, tileColumn: Int) = createTileLayer().apply {
val layerId = layers.size - 1
for (row in 0 until rows) {
for (column in 0 until columns) {
setTile(layerId, row, column, tileRow, tileColumn)
}
}
}
fun createTileLayer() = apply { layers.add(TileLayer(Array(rows) { Array(columns) { null } })) }
fun setTile(layer: Int, row: Int, column: Int, tile: Int) = apply {
(layers[layer] as TileLayer).setTile(row, column, tileSet.getTile(tile))
}
fun setTile(layer: Int, row: Int, column: Int, tileRow: Int, tileColumn: Int) = apply {
(layers[layer] as TileLayer).setTile(row, column, tileSet.getTile(tileRow, tileColumn))
}
override fun render(gc: GraphicsContext) {
layers.forEach { it.render(gc) }
grid.render(gc)
}
}

View File

@@ -0,0 +1,57 @@
package com.bartlomiejpluta.base.editor.model.map.tileset
import javafx.scene.image.Image
import javafx.scene.image.PixelReader
import javafx.scene.image.WritableImage
import javafx.scene.image.PixelWriter
import javafx.scene.paint.Color
class Tile(
tileSet: TileSet,
row: Int,
column: Int,
val image: Image,
) {
val id = row * tileSet.columns + column
private fun cloneImage(image: Image): Image {
val height = image.height.toInt()
val width = image.width.toInt()
val pixelReader = image.pixelReader
val writableImage = WritableImage(width, height)
val pixelWriter = writableImage.pixelWriter
for (y in 0 until height) {
for (x in 0 until width) {
val color = pixelReader.getColor(x, y)
pixelWriter.setColor(x, y, color)
}
}
return writableImage
}
fun scale(image: Image, factor: Int): Image {
val width = image.width.toInt()
val height = image.height.toInt()
val output = WritableImage(width * factor, height * factor)
val reader: PixelReader = image.pixelReader
val writer = output.pixelWriter
for (y in 0 until height) {
for (x in 0 until width) {
val argb = reader.getArgb(x, y)
for (dy in 0 until factor) {
for (dx in 0 until factor) {
writer.setArgb(x * factor + dx, y * factor + dy, argb)
}
}
}
}
return output
}
}

View File

@@ -0,0 +1,31 @@
package com.bartlomiejpluta.base.editor.model.map.tileset
import javafx.scene.image.Image
import javafx.scene.image.PixelFormat
import javafx.scene.image.WritableImage
import java.nio.ByteBuffer
class TileSet(private val image: Image, val rows: Int, val columns: Int) {
val tileWidth = image.width.toInt() / columns
val tileHeight = image.height.toInt() / rows
private val tiles: Array<Array<Tile>> =
Array(rows) { row -> Array(columns) { column -> cropTile(row, column) } }
private fun cropTile(row: Int, column: Int): Tile {
val reader = image.pixelReader
val buffer = ByteBuffer.allocate(tileWidth * tileHeight * 4)
reader.getPixels(column * tileWidth, row * tileHeight, tileWidth, tileHeight, PixelFormat.getByteBgraInstance(), buffer, 4 * tileWidth)
val tile = WritableImage(tileWidth, tileHeight)
val writer = tile.pixelWriter
writer.setPixels(0, 0, tileWidth, tileHeight, PixelFormat.getByteBgraInstance(), buffer, 4 * tileWidth)
return Tile(this, row, column, tile)
}
fun getTile(row: Int, column: Int) = tiles[row][column]
fun getTile(id: Int) = tiles[id / rows][id % columns]
}

View File

@@ -0,0 +1,15 @@
package com.bartlomiejpluta.base.editor.view.component.map
import com.bartlomiejpluta.base.editor.model.map.GameMap
import com.bartlomiejpluta.base.editor.view.render.Renderer
import javafx.scene.canvas.Canvas
class MapPane(map: GameMap) : Canvas() {
private val renderer = Renderer(graphicsContext2D, map)
init {
width = map.width.toDouble()
height = map.height.toDouble()
renderer.start()
}
}

View File

@@ -0,0 +1,24 @@
package com.bartlomiejpluta.base.editor.view.fragment
import com.bartlomiejpluta.base.editor.model.map.GameMap
import com.bartlomiejpluta.base.editor.view.component.map.MapPane
import tornadofx.Fragment
import tornadofx.group
import tornadofx.plusAssign
import tornadofx.scrollpane
class MapFragment : Fragment() {
val map: GameMap by param()
val pane = MapPane(map)
override val root = scrollpane {
prefWidth = 300.0
prefHeight = 300.0
group {
group {
this += pane
}
}
}
}

View File

@@ -0,0 +1,14 @@
package com.bartlomiejpluta.base.editor.view.main
import com.bartlomiejpluta.base.editor.controller.map.MapController
import com.bartlomiejpluta.base.editor.view.fragment.MapFragment
import tornadofx.View
import tornadofx.borderpane
class MainView : View() {
private val mapController: MapController by inject()
override val root = borderpane {
center = find<MapFragment>(mapOf(MapFragment::map to mapController.map)).root
}
}

View File

@@ -0,0 +1,7 @@
package com.bartlomiejpluta.base.editor.view.render
import javafx.scene.canvas.GraphicsContext
interface Renderable {
fun render(gc: GraphicsContext)
}

View File

@@ -0,0 +1,25 @@
package com.bartlomiejpluta.base.editor.view.render
import javafx.animation.AnimationTimer
import javafx.scene.canvas.GraphicsContext
class Renderer(
private val gc: GraphicsContext,
private val renderable: Renderable
) : AnimationTimer() {
private var previous = System.nanoTime()
override fun handle(now: Long) {
val dt = (now - previous) / 1000000000.0
previous = now
gc.isImageSmoothing = false
render()
}
private fun render() {
gc.clearRect(0.0, 0.0, gc.canvas.width, gc.canvas.height);
renderable.render(gc)
}
}

View File

@@ -3,4 +3,4 @@ springBootVersion=2.4.2
springDependencyManagementVersion=1.0.11.RELEASE
jomlVersion=1.10.0
guavaVersion=29.0-jre
tornadoFxVersion=2.0.0-SNAPSHOT
tornadoFxVersion=2.0.0-SNAPSHOT