mirror of
https://github.com/GayPizzaSpecifications/foundation.git
synced 2025-08-02 21:20:55 +00:00
Gjallarhorn: Refactor Rendering Code
This commit is contained in:
parent
9f8d417e5d
commit
08ba582931
@ -1,8 +1,10 @@
|
|||||||
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.BlockHeightMapRenderer
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.render.BlockImageRenderer
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.state.*
|
||||||
import cloud.kubelet.foundation.gjallarhorn.util.compose
|
import cloud.kubelet.foundation.gjallarhorn.util.compose
|
||||||
import cloud.kubelet.foundation.gjallarhorn.render.*
|
|
||||||
import cloud.kubelet.foundation.gjallarhorn.util.RandomColorKey
|
|
||||||
import cloud.kubelet.foundation.gjallarhorn.util.savePngFile
|
import cloud.kubelet.foundation.gjallarhorn.util.savePngFile
|
||||||
import cloud.kubelet.foundation.heimdall.view.BlockChangeView
|
import cloud.kubelet.foundation.heimdall.view.BlockChangeView
|
||||||
import com.github.ajalt.clikt.core.CliktCommand
|
import com.github.ajalt.clikt.core.CliktCommand
|
||||||
@ -17,7 +19,6 @@ import org.jetbrains.exposed.sql.*
|
|||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.lessEq
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.lessEq
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.awt.image.BufferedImage
|
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
@ -60,7 +61,7 @@ class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-lo
|
|||||||
}
|
}
|
||||||
|
|
||||||
val trackerPool = ScheduledThreadPoolExecutor(8)
|
val trackerPool = ScheduledThreadPoolExecutor(8)
|
||||||
val trackers = ConcurrentHashMap<Int, BlockStateTracker>()
|
val trackers = ConcurrentHashMap<Int, BlockLogTracker>()
|
||||||
for (time in intervals) {
|
for (time in intervals) {
|
||||||
trackerPool.submit {
|
trackerPool.submit {
|
||||||
val index = intervals.indexOf(time) + 1
|
val index = intervals.indexOf(time) + 1
|
||||||
@ -77,19 +78,20 @@ class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-lo
|
|||||||
}
|
}
|
||||||
logger.info("State Tracking Completed")
|
logger.info("State Tracking Completed")
|
||||||
val allBlockOffsets = trackers.map { it.value.calculateZeroBlockOffset() }
|
val allBlockOffsets = trackers.map { it.value.calculateZeroBlockOffset() }
|
||||||
val globalBlockOffset = BlockPosition.maxOf(allBlockOffsets.asSequence())
|
val globalBlockOffset = BlockCoordinate.maxOf(allBlockOffsets.asSequence())
|
||||||
val allBlockMaxes = trackers.map { it.value.calculateMaxBlock() }
|
val allBlockMaxes = trackers.map { it.value.calculateMaxBlock() }
|
||||||
val globalBlockMax = BlockPosition.maxOf(allBlockMaxes.asSequence())
|
val globalBlockMax = BlockCoordinate.maxOf(allBlockMaxes.asSequence())
|
||||||
val globalBlockExpanse = BlockExpanse.offsetAndMax(globalBlockOffset, globalBlockMax)
|
val globalBlockExpanse = BlockExpanse.offsetAndMax(globalBlockOffset, globalBlockMax)
|
||||||
|
|
||||||
logger.info("Calculations Completed")
|
logger.info("Calculations Completed")
|
||||||
|
|
||||||
val renderState = render.createState()
|
val renderer = render.create(globalBlockExpanse)
|
||||||
val renderPool = ScheduledThreadPoolExecutor(16)
|
val renderPool = ScheduledThreadPoolExecutor(16)
|
||||||
|
val imagePadCount = trackers.size.toString().length
|
||||||
for ((i, tracker) in trackers.entries) {
|
for ((i, tracker) in trackers.entries) {
|
||||||
renderPool.submit {
|
renderPool.submit {
|
||||||
val count = trackers.size.toString().length
|
val suffix = "-${i.toString().padStart(imagePadCount, '0')}"
|
||||||
saveRenderImage(renderState, tracker, globalBlockExpanse, "-${i.toString().padStart(count, '0')}")
|
saveRenderImage(renderer, tracker, globalBlockExpanse, suffix)
|
||||||
logger.info("Rendered Timelapse $i")
|
logger.info("Rendered Timelapse $i")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,25 +104,24 @@ class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-lo
|
|||||||
val time = if (exactTimeAsString != null) Instant.parse(exactTimeAsString) else null
|
val time = if (exactTimeAsString != null) Instant.parse(exactTimeAsString) else null
|
||||||
val tracker = buildTrackerState(time, "Single-Time")
|
val tracker = buildTrackerState(time, "Single-Time")
|
||||||
val expanse = BlockExpanse.offsetAndMax(tracker.calculateZeroBlockOffset(), tracker.calculateMaxBlock())
|
val expanse = BlockExpanse.offsetAndMax(tracker.calculateZeroBlockOffset(), tracker.calculateMaxBlock())
|
||||||
saveRenderImage(render.createState(), tracker, expanse)
|
saveRenderImage(render.create(expanse), tracker, expanse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveRenderImage(renderState: Any, tracker: BlockStateTracker, expanse: BlockExpanse, suffix: String = "") {
|
fun saveRenderImage(renderer: BlockImageRenderer, tracker: BlockLogTracker, expanse: BlockExpanse, suffix: String = "") {
|
||||||
val state = BlockStateImage()
|
val map = tracker.buildBlockMap(expanse.offset)
|
||||||
tracker.populateStateImage(state, expanse.offset)
|
val image = renderer.render(map)
|
||||||
val image = render.renderBufferedImage(renderState, state, expanse)
|
|
||||||
image.savePngFile("${render.id}${suffix}.png")
|
image.savePngFile("${render.id}${suffix}.png")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildTrackerState(time: Instant?, job: String): BlockStateTracker {
|
fun buildTrackerState(time: Instant?, job: String): BlockLogTracker {
|
||||||
val filter = compose(
|
val filter = compose(
|
||||||
combine = { a, b -> a and b },
|
combine = { a, b -> a and b },
|
||||||
{ time != null } to { BlockChangeView.time lessEq time!! }
|
{ time != null } to { BlockChangeView.time lessEq time!! }
|
||||||
)
|
)
|
||||||
|
|
||||||
val tracker =
|
val tracker =
|
||||||
BlockStateTracker(if (considerAirBlocks) BlockTrackMode.AirOnDelete else BlockTrackMode.RemoveOnDelete)
|
BlockLogTracker(if (considerAirBlocks) BlockTrackMode.AirOnDelete else BlockTrackMode.RemoveOnDelete)
|
||||||
|
|
||||||
val blockChangeCounter = AtomicLong()
|
val blockChangeCounter = AtomicLong()
|
||||||
transaction(db) {
|
transaction(db) {
|
||||||
@ -131,7 +132,7 @@ class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-lo
|
|||||||
val z = row[BlockChangeView.z]
|
val z = row[BlockChangeView.z]
|
||||||
val block = row[BlockChangeView.block]
|
val block = row[BlockChangeView.block]
|
||||||
|
|
||||||
val location = BlockPosition(x.toLong(), y.toLong(), z.toLong())
|
val location = BlockCoordinate(x.toLong(), y.toLong(), z.toLong())
|
||||||
if (changeIsBreak) {
|
if (changeIsBreak) {
|
||||||
tracker.delete(location)
|
tracker.delete(location)
|
||||||
} else {
|
} else {
|
||||||
@ -152,7 +153,7 @@ class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-lo
|
|||||||
return tracker
|
return tracker
|
||||||
}
|
}
|
||||||
|
|
||||||
fun maybeTrimState(tracker: BlockStateTracker) {
|
fun maybeTrimState(tracker: BlockLogTracker) {
|
||||||
if (fromCoordinate == null || toCoordinate == null) {
|
if (fromCoordinate == null || toCoordinate == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -160,8 +161,8 @@ class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-lo
|
|||||||
val from = fromCoordinate!!.split(",").map { it.toLong() }
|
val from = fromCoordinate!!.split(",").map { it.toLong() }
|
||||||
val to = toCoordinate!!.split(",").map { it.toLong() }
|
val to = toCoordinate!!.split(",").map { it.toLong() }
|
||||||
|
|
||||||
val fromBlock = BlockPosition(from[0], 0, from[1])
|
val fromBlock = BlockCoordinate(from[0], 0, from[1])
|
||||||
val toBlock = BlockPosition(to[0], 0, to[1])
|
val toBlock = BlockCoordinate(to[0], 0, to[1])
|
||||||
|
|
||||||
tracker.trimOutsideXAndZRange(fromBlock, toBlock)
|
tracker.trimOutsideXAndZRange(fromBlock, toBlock)
|
||||||
}
|
}
|
||||||
@ -169,17 +170,12 @@ class BlockLogReplay : CliktCommand("Replay Block Logs", name = "replay-block-lo
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
enum class RenderType(
|
enum class RenderType(
|
||||||
val id: String,
|
val id: String,
|
||||||
val createState: () -> Any,
|
val create: (BlockExpanse) -> BlockImageRenderer
|
||||||
val renderBufferedImage: (Any, BlockStateImage, BlockExpanse) -> BufferedImage
|
|
||||||
) {
|
) {
|
||||||
TopDown("top-down",
|
BlockDiversity("block-diversity", { expanse -> BlockDiversityRenderer(expanse) }),
|
||||||
{ TopDownState(RandomColorKey()) },
|
HeightMap("height-map", { expanse -> BlockHeightMapRenderer(expanse) })
|
||||||
{ state, image, expanse -> image.buildTopDownImage(expanse, (state as TopDownState).randomColorKey) }),
|
|
||||||
HeightMap("height-map", { }, { _, image, expanse -> image.buildHeightMapImage(expanse) })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TopDownState(val randomColorKey: RandomColorKey)
|
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
enum class TimelapseMode(val id: String, val interval: Duration) {
|
enum class TimelapseMode(val id: String, val interval: Duration) {
|
||||||
ByHour("hours", Duration.ofHours(1)),
|
ByHour("hours", Duration.ofHours(1)),
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
package cloud.kubelet.foundation.gjallarhorn.render
|
||||||
|
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.state.BlockExpanse
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.state.BlockMap
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.util.RandomColorKey
|
||||||
|
import java.awt.Color
|
||||||
|
import java.awt.image.BufferedImage
|
||||||
|
|
||||||
|
class BlockDiversityRenderer(val expanse: BlockExpanse, quadPixelSize: Int = defaultQuadPixelSize) : BlockGridRenderer(quadPixelSize) {
|
||||||
|
private val randomColorKey = RandomColorKey()
|
||||||
|
|
||||||
|
override fun render(map: BlockMap): BufferedImage = buildPixelQuadImage(expanse) { x, z ->
|
||||||
|
val maybeYBlocks = map.blocks[x]?.get(z)
|
||||||
|
if (maybeYBlocks == null) {
|
||||||
|
setPixelQuad(x, z, Color.white.rgb)
|
||||||
|
return@buildPixelQuadImage
|
||||||
|
}
|
||||||
|
val maxBlockState = maybeYBlocks.maxByOrNull { it.key }?.value
|
||||||
|
if (maxBlockState == null) {
|
||||||
|
setPixelQuad(x, z, Color.white.rgb)
|
||||||
|
return@buildPixelQuadImage
|
||||||
|
}
|
||||||
|
|
||||||
|
val color = randomColorKey.map(maxBlockState.type)
|
||||||
|
setPixelQuad(x, z, color.rgb)
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
package cloud.kubelet.foundation.gjallarhorn.render
|
|
||||||
|
|
||||||
class BlockExpanse(
|
|
||||||
val offset: BlockPosition,
|
|
||||||
val size: BlockPosition
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
fun offsetAndMax(offset: BlockPosition, max: BlockPosition) = BlockExpanse(
|
|
||||||
offset,
|
|
||||||
offset.applyAsOffset(max)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,42 @@
|
|||||||
|
package cloud.kubelet.foundation.gjallarhorn.render
|
||||||
|
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.state.BlockExpanse
|
||||||
|
import java.awt.Color
|
||||||
|
import java.awt.Rectangle
|
||||||
|
import java.awt.image.BufferedImage
|
||||||
|
|
||||||
|
abstract class BlockGridRenderer(val quadPixelSize: Int = defaultQuadPixelSize) : BlockImageRenderer {
|
||||||
|
protected fun BufferedImage.setPixelQuad(x: Long, z: Long, rgb: Int) {
|
||||||
|
drawSquare(x * quadPixelSize, z * quadPixelSize, quadPixelSize.toLong(), rgb)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun BufferedImage.drawSquare(x: Long, y: Long, side: Long, rgb: Int) {
|
||||||
|
val graphics = createGraphics()
|
||||||
|
graphics.color = Color(rgb)
|
||||||
|
graphics.fill(Rectangle(x.toInt(), y.toInt(), side.toInt(), side.toInt()))
|
||||||
|
graphics.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun buildPixelQuadImage(
|
||||||
|
expanse: BlockExpanse,
|
||||||
|
callback: BufferedImage.(Long, Long) -> Unit
|
||||||
|
): BufferedImage {
|
||||||
|
val widthInBlocks = expanse.size.x
|
||||||
|
val heightInBlocks = expanse.size.z
|
||||||
|
val widthInPixels = widthInBlocks.toInt() * quadPixelSize
|
||||||
|
val heightInPixels = heightInBlocks.toInt() * quadPixelSize
|
||||||
|
val bufferedImage =
|
||||||
|
BufferedImage(widthInPixels, heightInPixels, BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
|
|
||||||
|
for (x in 0 until widthInBlocks) {
|
||||||
|
for (z in 0 until heightInBlocks) {
|
||||||
|
callback(bufferedImage, x, z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bufferedImage
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val defaultQuadPixelSize = 4
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package cloud.kubelet.foundation.gjallarhorn.render
|
||||||
|
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.state.BlockExpanse
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.util.ColorGradient
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.util.FloatClamp
|
||||||
|
import java.awt.Color
|
||||||
|
import java.awt.image.BufferedImage
|
||||||
|
|
||||||
|
abstract class BlockHeatMapRenderer(quadPixelSize: Int = defaultQuadPixelSize) : BlockGridRenderer(quadPixelSize) {
|
||||||
|
protected fun buildHeatMapImage(expanse: BlockExpanse, clamp: FloatClamp, calculate: (Long, Long) -> Long?): BufferedImage =
|
||||||
|
buildPixelQuadImage(expanse) { x, z ->
|
||||||
|
val value = calculate(x, z)
|
||||||
|
val color = if (value != null) {
|
||||||
|
val floatValue = clamp.convert(value)
|
||||||
|
ColorGradient.HeatMap.getColorAtValue(floatValue)
|
||||||
|
} else {
|
||||||
|
Color.white
|
||||||
|
}
|
||||||
|
|
||||||
|
setPixelQuad(x, z, color.rgb)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package cloud.kubelet.foundation.gjallarhorn.render
|
||||||
|
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.state.BlockExpanse
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.state.BlockMap
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.util.FloatClamp
|
||||||
|
import java.awt.image.BufferedImage
|
||||||
|
|
||||||
|
class BlockHeightMapRenderer(val expanse: BlockExpanse, quadPixelSize: Int = defaultQuadPixelSize) :
|
||||||
|
BlockHeatMapRenderer(quadPixelSize) {
|
||||||
|
override fun render(map: BlockMap): BufferedImage {
|
||||||
|
val yMin = map.blocks.minOf { xSection -> xSection.value.minOf { zSection -> zSection.value.minOf { it.key } } }
|
||||||
|
val yMax = map.blocks.maxOf { xSection -> xSection.value.maxOf { zSection -> zSection.value.maxOf { it.key } } }
|
||||||
|
val clamp = FloatClamp(yMin, yMax)
|
||||||
|
|
||||||
|
return buildHeatMapImage(expanse, clamp) { x, z -> map.blocks[x]?.get(z)?.maxOf { it.key } }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package cloud.kubelet.foundation.gjallarhorn.render
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage
|
||||||
|
|
||||||
|
interface BlockImageRenderer : BlockMapRenderer<BufferedImage>
|
@ -0,0 +1,7 @@
|
|||||||
|
package cloud.kubelet.foundation.gjallarhorn.render
|
||||||
|
|
||||||
|
import cloud.kubelet.foundation.gjallarhorn.state.BlockMap
|
||||||
|
|
||||||
|
interface BlockMapRenderer<T> {
|
||||||
|
fun render(map: BlockMap): T
|
||||||
|
}
|
@ -1,37 +0,0 @@
|
|||||||
package cloud.kubelet.foundation.gjallarhorn.render
|
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
data class BlockPosition(
|
|
||||||
val x: Long,
|
|
||||||
val y: Long,
|
|
||||||
val z: Long
|
|
||||||
) {
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (other !is BlockPosition) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return other.x == x && other.y == y && other.z == z
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int = Objects.hash(x, y, z)
|
|
||||||
|
|
||||||
fun applyAsOffset(position: BlockPosition) = position.copy(
|
|
||||||
x = position.x + x,
|
|
||||||
y = position.y + y,
|
|
||||||
z = position.z + z
|
|
||||||
)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val zero = BlockPosition(0, 0, 0)
|
|
||||||
|
|
||||||
fun maxOf(positions: Sequence<BlockPosition>): BlockPosition {
|
|
||||||
val x = positions.maxOf { it.x }
|
|
||||||
val y = positions.maxOf { it.y }
|
|
||||||
val z = positions.maxOf { it.z }
|
|
||||||
|
|
||||||
return BlockPosition(x, y, z)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
package cloud.kubelet.foundation.gjallarhorn.render
|
|
||||||
|
|
||||||
import cloud.kubelet.foundation.gjallarhorn.util.ColorGradient
|
|
||||||
import cloud.kubelet.foundation.gjallarhorn.util.FloatClamp
|
|
||||||
import cloud.kubelet.foundation.gjallarhorn.util.RandomColorKey
|
|
||||||
import java.awt.Color
|
|
||||||
import java.awt.Rectangle
|
|
||||||
import java.awt.image.BufferedImage
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class BlockStateImage {
|
|
||||||
private val blocks = TreeMap<Long, TreeMap<Long, TreeMap<Long, BlockState>>>()
|
|
||||||
|
|
||||||
fun put(position: BlockPosition, state: BlockState) {
|
|
||||||
blocks.getOrPut(position.x) {
|
|
||||||
TreeMap()
|
|
||||||
}.getOrPut(position.z) {
|
|
||||||
TreeMap()
|
|
||||||
}[position.y] = state
|
|
||||||
}
|
|
||||||
|
|
||||||
fun buildTopDownImage(expanse: BlockExpanse, randomColorKey: RandomColorKey): BufferedImage {
|
|
||||||
return buildPixelQuadImage(expanse) { x, z ->
|
|
||||||
val maybeYBlocks = blocks[x]?.get(z)
|
|
||||||
if (maybeYBlocks == null) {
|
|
||||||
setPixelQuad(x, z, Color.white.rgb)
|
|
||||||
return@buildPixelQuadImage
|
|
||||||
}
|
|
||||||
val maxBlockState = maybeYBlocks.maxByOrNull { it.key }?.value
|
|
||||||
if (maxBlockState == null) {
|
|
||||||
setPixelQuad(x, z, Color.white.rgb)
|
|
||||||
return@buildPixelQuadImage
|
|
||||||
}
|
|
||||||
|
|
||||||
val color = randomColorKey.map(maxBlockState.type)
|
|
||||||
setPixelQuad(x, z, color.rgb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun buildHeightMapImage(expanse: BlockExpanse): BufferedImage {
|
|
||||||
val yMin = blocks.minOf { xSection -> xSection.value.minOf { zSection -> zSection.value.minOf { it.key } } }
|
|
||||||
val yMax = blocks.maxOf { xSection -> xSection.value.maxOf { zSection -> zSection.value.maxOf { it.key } } }
|
|
||||||
val clamp = FloatClamp(yMin, yMax)
|
|
||||||
|
|
||||||
return buildHeatMapImage(expanse, clamp) { x, z -> blocks[x]?.get(z)?.maxOf { it.key } }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun buildHeatMapImage(expanse: BlockExpanse, clamp: FloatClamp, calculate: (Long, Long) -> Long?): BufferedImage =
|
|
||||||
buildPixelQuadImage(expanse) { x, z ->
|
|
||||||
val value = calculate(x, z)
|
|
||||||
val color = if (value != null) {
|
|
||||||
val floatValue = clamp.convert(value)
|
|
||||||
ColorGradient.HeatMap.getColorAtValue(floatValue)
|
|
||||||
} else {
|
|
||||||
Color.white
|
|
||||||
}
|
|
||||||
|
|
||||||
setPixelQuad(x, z, color.rgb)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun BufferedImage.setPixelQuad(x: Long, z: Long, rgb: Int) {
|
|
||||||
drawSquare(x * quadImageSize, z * quadImageSize, quadImageSize.toLong(), rgb)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun BufferedImage.drawSquare(x: Long, y: Long, side: Long, rgb: Int) {
|
|
||||||
val graphics = createGraphics()
|
|
||||||
graphics.color = Color(rgb)
|
|
||||||
graphics.fill(Rectangle(x.toInt(), y.toInt(), side.toInt(), side.toInt()))
|
|
||||||
graphics.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildPixelQuadImage(expanse: BlockExpanse, callback: BufferedImage.(Long, Long) -> Unit): BufferedImage {
|
|
||||||
val width = expanse.size.x
|
|
||||||
val height = expanse.size.z
|
|
||||||
val bufferedImage =
|
|
||||||
BufferedImage(width.toInt() * quadImageSize, height.toInt() * quadImageSize, BufferedImage.TYPE_4BYTE_ABGR)
|
|
||||||
|
|
||||||
for (x in 0 until width) {
|
|
||||||
for (z in 0 until height) {
|
|
||||||
callback(bufferedImage, x, z)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bufferedImage
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val quadImageSize = 4
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,37 @@
|
|||||||
|
package cloud.kubelet.foundation.gjallarhorn.state
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
data class BlockCoordinate(
|
||||||
|
val x: Long,
|
||||||
|
val y: Long,
|
||||||
|
val z: Long
|
||||||
|
) {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is BlockCoordinate) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return other.x == x && other.y == y && other.z == z
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int = Objects.hash(x, y, z)
|
||||||
|
|
||||||
|
fun applyAsOffset(coordinate: BlockCoordinate) = coordinate.copy(
|
||||||
|
x = coordinate.x + x,
|
||||||
|
y = coordinate.y + y,
|
||||||
|
z = coordinate.z + z
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val zero = BlockCoordinate(0, 0, 0)
|
||||||
|
|
||||||
|
fun maxOf(coordinates: Sequence<BlockCoordinate>): BlockCoordinate {
|
||||||
|
val x = coordinates.maxOf { it.x }
|
||||||
|
val y = coordinates.maxOf { it.y }
|
||||||
|
val z = coordinates.maxOf { it.z }
|
||||||
|
|
||||||
|
return BlockCoordinate(x, y, z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package cloud.kubelet.foundation.gjallarhorn.state
|
||||||
|
|
||||||
|
class BlockExpanse(
|
||||||
|
val offset: BlockCoordinate,
|
||||||
|
val size: BlockCoordinate
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun offsetAndMax(offset: BlockCoordinate, max: BlockCoordinate) = BlockExpanse(
|
||||||
|
offset,
|
||||||
|
offset.applyAsOffset(max)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,15 @@
|
|||||||
package cloud.kubelet.foundation.gjallarhorn.render
|
package cloud.kubelet.foundation.gjallarhorn.state
|
||||||
|
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
class BlockStateTracker(private val mode: BlockTrackMode = BlockTrackMode.RemoveOnDelete) {
|
class BlockLogTracker(private val mode: BlockTrackMode = BlockTrackMode.RemoveOnDelete) {
|
||||||
val blocks = HashMap<BlockPosition, BlockState>()
|
val blocks = HashMap<BlockCoordinate, BlockState>()
|
||||||
|
|
||||||
fun place(position: BlockPosition, state: BlockState) {
|
fun place(position: BlockCoordinate, state: BlockState) {
|
||||||
blocks[position] = state
|
blocks[position] = state
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete(position: BlockPosition) {
|
fun delete(position: BlockCoordinate) {
|
||||||
if (mode == BlockTrackMode.AirOnDelete) {
|
if (mode == BlockTrackMode.AirOnDelete) {
|
||||||
blocks[position] = BlockState("minecraft:air")
|
blocks[position] = BlockState("minecraft:air")
|
||||||
} else {
|
} else {
|
||||||
@ -17,7 +17,7 @@ class BlockStateTracker(private val mode: BlockTrackMode = BlockTrackMode.Remove
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun trimOutsideXAndZRange(min: BlockPosition, max: BlockPosition) {
|
fun trimOutsideXAndZRange(min: BlockCoordinate, max: BlockCoordinate) {
|
||||||
val blockPositionsToRemove = blocks.keys.filter {
|
val blockPositionsToRemove = blocks.keys.filter {
|
||||||
it.x < min.x ||
|
it.x < min.x ||
|
||||||
it.z < min.z ||
|
it.z < min.z ||
|
||||||
@ -28,7 +28,7 @@ class BlockStateTracker(private val mode: BlockTrackMode = BlockTrackMode.Remove
|
|||||||
blockPositionsToRemove.forEach { blocks.remove(it) }
|
blockPositionsToRemove.forEach { blocks.remove(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun calculateZeroBlockOffset(): BlockPosition {
|
fun calculateZeroBlockOffset(): BlockCoordinate {
|
||||||
val x = blocks.keys.minOf { it.x }
|
val x = blocks.keys.minOf { it.x }
|
||||||
val y = blocks.keys.minOf { it.y }
|
val y = blocks.keys.minOf { it.y }
|
||||||
val z = blocks.keys.minOf { it.z }
|
val z = blocks.keys.minOf { it.z }
|
||||||
@ -37,22 +37,24 @@ class BlockStateTracker(private val mode: BlockTrackMode = BlockTrackMode.Remove
|
|||||||
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
|
||||||
|
|
||||||
return BlockPosition(xOffset, yOffset, zOffset)
|
return BlockCoordinate(xOffset, yOffset, zOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun calculateMaxBlock(): BlockPosition {
|
fun calculateMaxBlock(): BlockCoordinate {
|
||||||
val x = blocks.keys.maxOf { it.x }
|
val x = blocks.keys.maxOf { it.x }
|
||||||
val y = blocks.keys.maxOf { it.y }
|
val y = blocks.keys.maxOf { it.y }
|
||||||
val z = blocks.keys.maxOf { it.z }
|
val z = blocks.keys.maxOf { it.z }
|
||||||
return BlockPosition(x, y, z)
|
return BlockCoordinate(x, y, z)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isEmpty() = blocks.isEmpty()
|
fun isEmpty() = blocks.isEmpty()
|
||||||
|
|
||||||
fun populateStateImage(image: BlockStateImage, offset: BlockPosition = BlockPosition.zero) {
|
fun buildBlockMap(offset: BlockCoordinate = BlockCoordinate.zero): BlockMap {
|
||||||
|
val map = BlockMap()
|
||||||
blocks.forEach { (position, state) ->
|
blocks.forEach { (position, state) ->
|
||||||
val realPosition = offset.applyAsOffset(position)
|
val realPosition = offset.applyAsOffset(position)
|
||||||
image.put(realPosition, state)
|
map.put(realPosition, state)
|
||||||
}
|
}
|
||||||
|
return map
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package cloud.kubelet.foundation.gjallarhorn.state
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class BlockMap {
|
||||||
|
val blocks = TreeMap<Long, TreeMap<Long, TreeMap<Long, BlockState>>>()
|
||||||
|
|
||||||
|
fun put(position: BlockCoordinate, state: BlockState) {
|
||||||
|
blocks.getOrPut(position.x) {
|
||||||
|
TreeMap()
|
||||||
|
}.getOrPut(position.z) {
|
||||||
|
TreeMap()
|
||||||
|
}[position.y] = state
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package cloud.kubelet.foundation.gjallarhorn.render
|
package cloud.kubelet.foundation.gjallarhorn.state
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package cloud.kubelet.foundation.gjallarhorn.render
|
package cloud.kubelet.foundation.gjallarhorn.state
|
||||||
|
|
||||||
enum class BlockTrackMode {
|
enum class BlockTrackMode {
|
||||||
RemoveOnDelete,
|
RemoveOnDelete,
|
Loading…
Reference in New Issue
Block a user