Gjallarhorn: First attempt at a graphical render system.

This commit is contained in:
Kenneth Endfinger
2022-02-20 05:35:47 -05:00
parent 1afb1c7148
commit 6bcddb15b5
9 changed files with 84 additions and 21 deletions

View File

@ -104,7 +104,7 @@ class BlockChangeTimelapseCommand : CliktCommand("Block Change Timelapse", name
changelog = changelog, changelog = changelog,
blockTrackMode = if (considerAirBlocks) BlockTrackMode.AirOnDelete else BlockTrackMode.RemoveOnDelete, blockTrackMode = if (considerAirBlocks) BlockTrackMode.AirOnDelete else BlockTrackMode.RemoveOnDelete,
delegate = timelapse, delegate = timelapse,
createRendererFunction = { expanse -> render.create(expanse, db) }, createRendererFunction = { expanse -> render.createNewRenderer(expanse, db) },
threadPoolExecutor = threadPoolExecutor threadPoolExecutor = threadPoolExecutor
) { slice, result -> ) { slice, result ->
val speed = slice.sliceRelativeDuration.toSeconds().toDouble() / timelapseMode.interval.toSeconds().toDouble() val speed = slice.sliceRelativeDuration.toSeconds().toDouble() / timelapseMode.interval.toSeconds().toDouble()

View File

@ -10,6 +10,7 @@ import com.github.ajalt.clikt.core.requireObject
import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.enum import com.github.ajalt.clikt.parameters.types.enum
import com.github.ajalt.clikt.parameters.types.int
import com.github.ajalt.clikt.parameters.types.path import com.github.ajalt.clikt.parameters.types.path
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
@ -18,17 +19,17 @@ class ChunkExportLoaderCommand : CliktCommand("Chunk Export Loader", name = "chu
private val exportDirectoryPath by argument("export-directory-path").path() private val exportDirectoryPath by argument("export-directory-path").path()
private val world by argument("world") private val world by argument("world")
private val chunkLoadLimit by option("--chunk-limit", help = "Chunk Limit").int()
private val render by option("--render", help = "Render Top Down Image").enum<ImageRenderType> { it.id } private val render by option("--render", help = "Render Top Down Image").enum<ImageRenderType> { it.id }
override fun run() { override fun run() {
val tracker = BlockLogTracker(isConcurrent = true) val tracker = BlockLogTracker(isConcurrent = true)
val loader = ChunkExportLoader(tracker = tracker) val loader = ChunkExportLoader(tracker = tracker)
loader.loadAllChunksForWorld(exportDirectoryPath, world, fast = true) loader.loadAllChunksForWorld(exportDirectoryPath, world, fast = true, limit = chunkLoadLimit)
if (render != null) { if (render != null) {
val expanse = BlockExpanse.zeroOffsetAndMax(tracker.calculateZeroBlockOffset(), tracker.calculateMaxBlock()) val expanse = BlockExpanse.zeroOffsetAndMax(tracker.calculateZeroBlockOffset(), tracker.calculateMaxBlock())
val map = tracker.buildBlockMap(expanse.offset) val map = tracker.buildBlockMap(expanse.offset)
val renderer = render!!.create(expanse, db) val renderer = render!!.createNewRenderer(expanse, db)
val image = renderer.render(ChangelogSlice.none, map) val image = renderer.render(ChangelogSlice.none, map)
image.savePngFile("full.png") image.savePngFile("full.png")
} }

View File

@ -1,18 +1,16 @@
package cloud.kubelet.foundation.gjallarhorn.commands package cloud.kubelet.foundation.gjallarhorn.commands
import cloud.kubelet.foundation.gjallarhorn.render.BlockDiversityRenderer import cloud.kubelet.foundation.gjallarhorn.render.*
import cloud.kubelet.foundation.gjallarhorn.render.BlockHeightMapRenderer
import cloud.kubelet.foundation.gjallarhorn.render.BlockImageRenderer
import cloud.kubelet.foundation.gjallarhorn.render.PlayerLocationShareRenderer
import cloud.kubelet.foundation.gjallarhorn.state.BlockExpanse import cloud.kubelet.foundation.gjallarhorn.state.BlockExpanse
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
@Suppress("unused") @Suppress("unused")
enum class ImageRenderType( enum class ImageRenderType(
val id: String, val id: String,
val create: (BlockExpanse, Database) -> BlockImageRenderer val createNewRenderer: (BlockExpanse, Database) -> BlockImageRenderer
) { ) {
BlockDiversity("block-diversity", { expanse, _ -> BlockDiversityRenderer(expanse) }), BlockDiversity("block-diversity", { expanse, _ -> BlockDiversityRenderer(expanse) }),
HeightMap("height-map", { expanse, _ -> BlockHeightMapRenderer(expanse) }), HeightMap("height-map", { expanse, _ -> BlockHeightMapRenderer(expanse) }),
PlayerPosition("player-position", { expanse, db -> PlayerLocationShareRenderer(expanse, db) }) PlayerPosition("player-position", { expanse, db -> PlayerLocationShareRenderer(expanse, db) }),
GraphicalSession("graphical", { expanse, _ -> LaunchGraphicalRenderSession(expanse) })
} }

View File

@ -14,8 +14,11 @@ import kotlin.io.path.inputStream
import kotlin.io.path.listDirectoryEntries import kotlin.io.path.listDirectoryEntries
class ChunkExportLoader(val map: SparseBlockStateMap? = null, val tracker: BlockLogTracker? = null) { class ChunkExportLoader(val map: SparseBlockStateMap? = null, val tracker: BlockLogTracker? = null) {
fun loadAllChunksForWorld(path: Path, world: String, fast: Boolean = false) { fun loadAllChunksForWorld(path: Path, world: String, fast: Boolean = false, limit: Int? = null) {
val chunkFiles = path.listDirectoryEntries("${world}_chunk_*.json.gz") var chunkFiles = path.listDirectoryEntries("${world}_chunk_*.json.gz")
if (limit != null) {
chunkFiles = chunkFiles.take(limit)
}
if (fast) { if (fast) {
chunkFiles.parallelStream().forEach { loadChunkFile(it, id = chunkFiles.indexOf(it)) } chunkFiles.parallelStream().forEach { loadChunkFile(it, id = chunkFiles.indexOf(it)) }
} else { } else {

View File

@ -0,0 +1,15 @@
package cloud.kubelet.foundation.gjallarhorn.render
import cloud.kubelet.foundation.gjallarhorn.render.ui.GraphicalRenderSession
import cloud.kubelet.foundation.gjallarhorn.state.BlockExpanse
import cloud.kubelet.foundation.gjallarhorn.state.BlockStateMap
import cloud.kubelet.foundation.gjallarhorn.state.ChangelogSlice
import java.awt.image.BufferedImage
class LaunchGraphicalRenderSession(val expanse: BlockExpanse) : BlockImageRenderer {
override fun render(slice: ChangelogSlice, map: BlockStateMap): BufferedImage {
val session = GraphicalRenderSession(expanse, map)
session.isVisible = true
return BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR)
}
}

View File

@ -0,0 +1,22 @@
package cloud.kubelet.foundation.gjallarhorn.render.ui
import cloud.kubelet.foundation.gjallarhorn.render.BlockDiversityRenderer
import cloud.kubelet.foundation.gjallarhorn.render.BlockHeightMapRenderer
import cloud.kubelet.foundation.gjallarhorn.render.BlockVerticalFillMapRenderer
import cloud.kubelet.foundation.gjallarhorn.state.BlockExpanse
import cloud.kubelet.foundation.gjallarhorn.state.BlockStateMap
import java.awt.Dimension
import javax.swing.JFrame
import javax.swing.JTabbedPane
class GraphicalRenderSession(val expanse: BlockExpanse, val map: BlockStateMap) : JFrame() {
init {
name = "Gjallarhorn Renderer"
size = Dimension(1024, 1024)
val pane = JTabbedPane()
pane.addTab("Block Diversity", LazyImageRenderer(map, BlockDiversityRenderer(expanse)))
pane.addTab("Height Map", LazyImageRenderer(map, BlockHeightMapRenderer(expanse)))
pane.addTab("Vertical Fill Map", LazyImageRenderer(map, BlockVerticalFillMapRenderer(expanse)))
add(pane)
}
}

View File

@ -0,0 +1,21 @@
package cloud.kubelet.foundation.gjallarhorn.render.ui
import cloud.kubelet.foundation.gjallarhorn.render.BlockImageRenderer
import cloud.kubelet.foundation.gjallarhorn.state.BlockStateMap
import cloud.kubelet.foundation.gjallarhorn.state.ChangelogSlice
import java.awt.Graphics
import javax.swing.JComponent
class LazyImageRenderer(val map: BlockStateMap, private val renderer: BlockImageRenderer) : JComponent() {
private val image by lazy {
renderer.render(ChangelogSlice.none, map)
}
override fun paint(g: Graphics?) {
g?.drawImage(image, 0, 0, this)
}
override fun paintComponent(g: Graphics?) {
g?.drawImage(image, 0, 0, this)
}
}

View File

@ -1,5 +1,7 @@
package cloud.kubelet.foundation.gjallarhorn.state package cloud.kubelet.foundation.gjallarhorn.state
import cloud.kubelet.foundation.gjallarhorn.util.maxOfAll
import cloud.kubelet.foundation.gjallarhorn.util.minOfAll
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@ -23,10 +25,7 @@ class BlockLogTracker(private val mode: BlockTrackMode = BlockTrackMode.RemoveOn
} }
fun calculateZeroBlockOffset(): BlockCoordinate { fun calculateZeroBlockOffset(): BlockCoordinate {
val x = blocks.keys.minOf { it.x } val (x, y, z) = blocks.keys.minOfAll(3) { listOf(it.x, it.y, it.z) }
val y = blocks.keys.minOf { it.y }
val z = blocks.keys.minOf { it.z }
val xOffset = if (x < 0) x.absoluteValue else 0 val xOffset = if (x < 0) x.absoluteValue else 0
val yOffset = if (y < 0) y.absoluteValue else 0 val yOffset = if (y < 0) y.absoluteValue else 0
val zOffset = if (z < 0) z.absoluteValue else 0 val zOffset = if (z < 0) z.absoluteValue else 0
@ -35,9 +34,7 @@ class BlockLogTracker(private val mode: BlockTrackMode = BlockTrackMode.RemoveOn
} }
fun calculateMaxBlock(): BlockCoordinate { fun calculateMaxBlock(): BlockCoordinate {
val x = blocks.keys.maxOf { it.x } val (x, y, z) = blocks.keys.maxOfAll(3) { listOf(it.x, it.y, it.z) }
val y = blocks.keys.maxOf { it.y }
val z = blocks.keys.maxOf { it.z }
return BlockCoordinate(x, y, z) return BlockCoordinate(x, y, z)
} }

View File

@ -1,6 +1,6 @@
package cloud.kubelet.foundation.gjallarhorn.util package cloud.kubelet.foundation.gjallarhorn.util
fun <T> Sequence<T>.minOfAll(fieldCount: Int, block: (value: T) -> List<Long>): List<Long> { fun <T> Iterable<T>.minOfAll(fieldCount: Int, block: (value: T) -> List<Long>): List<Long> {
val fieldRange = 0 until fieldCount val fieldRange = 0 until fieldCount
val results = fieldRange.map { Long.MAX_VALUE }.toMutableList() val results = fieldRange.map { Long.MAX_VALUE }.toMutableList()
for (item in this) { for (item in this) {
@ -16,7 +16,7 @@ fun <T> Sequence<T>.minOfAll(fieldCount: Int, block: (value: T) -> List<Long>):
return results return results
} }
fun <T> Sequence<T>.maxOfAll(fieldCount: Int, block: (value: T) -> List<Long>): List<Long> { fun <T> Iterable<T>.maxOfAll(fieldCount: Int, block: (value: T) -> List<Long>): List<Long> {
val fieldRange = 0 until fieldCount val fieldRange = 0 until fieldCount
val results = fieldRange.map { Long.MIN_VALUE }.toMutableList() val results = fieldRange.map { Long.MIN_VALUE }.toMutableList()
for (item in this) { for (item in this) {
@ -31,3 +31,9 @@ fun <T> Sequence<T>.maxOfAll(fieldCount: Int, block: (value: T) -> List<Long>):
} }
return results return results
} }
fun <T> Sequence<T>.minOfAll(fieldCount: Int, block: (value: T) -> List<Long>): List<Long> =
asIterable().minOfAll(fieldCount, block)
fun <T> Sequence<T>.maxOfAll(fieldCount: Int, block: (value: T) -> List<Long>): List<Long> =
asIterable().maxOfAll(fieldCount, block)