mirror of
https://github.com/GayPizzaSpecifications/foundation.git
synced 2025-08-03 05:30:55 +00:00
Gjallarhorn: First attempt at a graphical render system.
This commit is contained in:
@ -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()
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -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) })
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
Reference in New Issue
Block a user