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
No known key found for this signature in database
GPG Key ID: C4E68E5647420E10
9 changed files with 84 additions and 21 deletions

View File

@ -104,7 +104,7 @@ class BlockChangeTimelapseCommand : CliktCommand("Block Change Timelapse", name
changelog = changelog,
blockTrackMode = if (considerAirBlocks) BlockTrackMode.AirOnDelete else BlockTrackMode.RemoveOnDelete,
delegate = timelapse,
createRendererFunction = { expanse -> render.create(expanse, db) },
createRendererFunction = { expanse -> render.createNewRenderer(expanse, db) },
threadPoolExecutor = threadPoolExecutor
) { slice, result ->
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.options.option
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 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 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 }
override fun run() {
val tracker = BlockLogTracker(isConcurrent = true)
val loader = ChunkExportLoader(tracker = tracker)
loader.loadAllChunksForWorld(exportDirectoryPath, world, fast = true)
loader.loadAllChunksForWorld(exportDirectoryPath, world, fast = true, limit = chunkLoadLimit)
if (render != null) {
val expanse = BlockExpanse.zeroOffsetAndMax(tracker.calculateZeroBlockOffset(), tracker.calculateMaxBlock())
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)
image.savePngFile("full.png")
}

View File

@ -1,18 +1,16 @@
package cloud.kubelet.foundation.gjallarhorn.commands
import cloud.kubelet.foundation.gjallarhorn.render.BlockDiversityRenderer
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.render.*
import cloud.kubelet.foundation.gjallarhorn.state.BlockExpanse
import org.jetbrains.exposed.sql.Database
@Suppress("unused")
enum class ImageRenderType(
val id: String,
val create: (BlockExpanse, Database) -> BlockImageRenderer
val createNewRenderer: (BlockExpanse, Database) -> BlockImageRenderer
) {
BlockDiversity("block-diversity", { expanse, _ -> BlockDiversityRenderer(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
class ChunkExportLoader(val map: SparseBlockStateMap? = null, val tracker: BlockLogTracker? = null) {
fun loadAllChunksForWorld(path: Path, world: String, fast: Boolean = false) {
val chunkFiles = path.listDirectoryEntries("${world}_chunk_*.json.gz")
fun loadAllChunksForWorld(path: Path, world: String, fast: Boolean = false, limit: Int? = null) {
var chunkFiles = path.listDirectoryEntries("${world}_chunk_*.json.gz")
if (limit != null) {
chunkFiles = chunkFiles.take(limit)
}
if (fast) {
chunkFiles.parallelStream().forEach { loadChunkFile(it, id = chunkFiles.indexOf(it)) }
} 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
import cloud.kubelet.foundation.gjallarhorn.util.maxOfAll
import cloud.kubelet.foundation.gjallarhorn.util.minOfAll
import java.util.concurrent.ConcurrentHashMap
import kotlin.math.absoluteValue
@ -23,10 +25,7 @@ class BlockLogTracker(private val mode: BlockTrackMode = BlockTrackMode.RemoveOn
}
fun calculateZeroBlockOffset(): BlockCoordinate {
val x = blocks.keys.minOf { it.x }
val y = blocks.keys.minOf { it.y }
val z = blocks.keys.minOf { it.z }
val (x, y, z) = blocks.keys.minOfAll(3) { listOf(it.x, it.y, it.z) }
val xOffset = if (x < 0) x.absoluteValue else 0
val yOffset = if (y < 0) y.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 {
val x = blocks.keys.maxOf { it.x }
val y = blocks.keys.maxOf { it.y }
val z = blocks.keys.maxOf { it.z }
val (x, y, z) = blocks.keys.maxOfAll(3) { listOf(it.x, it.y, it.z) }
return BlockCoordinate(x, y, z)
}

View File

@ -1,6 +1,6 @@
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 results = fieldRange.map { Long.MAX_VALUE }.toMutableList()
for (item in this) {
@ -16,7 +16,7 @@ fun <T> Sequence<T>.minOfAll(fieldCount: Int, block: (value: T) -> List<Long>):
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 results = fieldRange.map { Long.MIN_VALUE }.toMutableList()
for (item in this) {
@ -31,3 +31,9 @@ fun <T> Sequence<T>.maxOfAll(fieldCount: Int, block: (value: T) -> List<Long>):
}
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)