mirror of
https://github.com/GayPizzaSpecifications/foundation.git
synced 2025-08-03 13:31:32 +00:00
Gjallarhorn: Dynamic Timelapse Slices
This commit is contained in:
@ -14,6 +14,9 @@ import com.github.ajalt.clikt.parameters.types.enum
|
|||||||
import com.github.ajalt.clikt.parameters.types.int
|
import com.github.ajalt.clikt.parameters.types.int
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.awt.Color
|
||||||
|
import java.awt.Font
|
||||||
|
import java.awt.font.TextLayout
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.concurrent.ScheduledThreadPoolExecutor
|
import java.util.concurrent.ScheduledThreadPoolExecutor
|
||||||
@ -22,6 +25,15 @@ class BlockChangeTimelapseCommand : CliktCommand("Block Change Timelapse", name
|
|||||||
private val db by requireObject<Database>()
|
private val db by requireObject<Database>()
|
||||||
private val timelapseIntervalLimit by option("--timelapse-limit", help = "Timelapse Limit Intervals").int()
|
private val timelapseIntervalLimit by option("--timelapse-limit", help = "Timelapse Limit Intervals").int()
|
||||||
private val timelapseMode by option("--timelapse", help = "Timelapse Mode").enum<TimelapseMode> { it.id }.required()
|
private val timelapseMode by option("--timelapse", help = "Timelapse Mode").enum<TimelapseMode> { it.id }.required()
|
||||||
|
private val timelapseSpeedChangeThreshold by option(
|
||||||
|
"--timelapse-change-speed-threshold",
|
||||||
|
help = "Timelapse Change Speed Threshold"
|
||||||
|
).int()
|
||||||
|
private val timelapseSpeedChangeMinimumIntervalSeconds by option(
|
||||||
|
"--timelapse-change-speed-minimum-interval-seconds",
|
||||||
|
help = "Timelapse Change Speed Minimum Interval Seconds"
|
||||||
|
).int()
|
||||||
|
|
||||||
private val render by option("--render", help = "Render Top Down Image").enum<RenderType> { it.id }.required()
|
private val render by option("--render", help = "Render Top Down Image").enum<RenderType> { it.id }.required()
|
||||||
|
|
||||||
private val considerAirBlocks by option("--consider-air-blocks", help = "Enable Air Block Consideration").flag()
|
private val considerAirBlocks by option("--consider-air-blocks", help = "Enable Air Block Consideration").flag()
|
||||||
@ -32,10 +44,20 @@ class BlockChangeTimelapseCommand : CliktCommand("Block Change Timelapse", name
|
|||||||
private val logger = LoggerFactory.getLogger(BlockChangeTimelapseCommand::class.java)
|
private val logger = LoggerFactory.getLogger(BlockChangeTimelapseCommand::class.java)
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
val threadPoolExecutor = ScheduledThreadPoolExecutor(8)
|
val threadPoolExecutor = ScheduledThreadPoolExecutor(16)
|
||||||
val changelog = BlockChangelog.query(db)
|
val changelog = BlockChangelog.query(db)
|
||||||
val timelapse = BlockMapTimelapse<BufferedImage>(maybeBuildTrim())
|
val timelapse = BlockMapTimelapse<BufferedImage>(maybeBuildTrim())
|
||||||
val slices = timelapse.calculateChangelogSlices(changelog, timelapseMode.interval, timelapseIntervalLimit)
|
var slices = timelapse.calculateChangelogSlices(changelog, timelapseMode.interval, timelapseIntervalLimit)
|
||||||
|
|
||||||
|
if (timelapseSpeedChangeThreshold != null && timelapseSpeedChangeMinimumIntervalSeconds != null) {
|
||||||
|
val minimumInterval = Duration.ofSeconds(timelapseSpeedChangeMinimumIntervalSeconds!!.toLong())
|
||||||
|
val blockChangeThreshold = timelapseSpeedChangeThreshold!!
|
||||||
|
|
||||||
|
slices = timelapse.splitChangelogSlicesWithThreshold(changelog, blockChangeThreshold, minimumInterval, slices)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Timelapse Slices: ${slices.size} slices")
|
||||||
|
|
||||||
val imagePadCount = slices.size.toString().length
|
val imagePadCount = slices.size.toString().length
|
||||||
val pool = BlockMapRenderPool(
|
val pool = BlockMapRenderPool(
|
||||||
changelog = changelog,
|
changelog = changelog,
|
||||||
@ -44,10 +66,20 @@ class BlockChangeTimelapseCommand : CliktCommand("Block Change Timelapse", name
|
|||||||
rendererFactory = { expanse -> render.create(expanse) },
|
rendererFactory = { expanse -> render.create(expanse) },
|
||||||
threadPoolExecutor = threadPoolExecutor
|
threadPoolExecutor = threadPoolExecutor
|
||||||
) { slice, result ->
|
) { slice, result ->
|
||||||
|
val speed = slice.relative.toSeconds().toDouble() / timelapseMode.interval.toSeconds().toDouble()
|
||||||
|
val graphics = result.createGraphics()
|
||||||
|
val font = Font.decode("Arial Black").deriveFont(36.0f)
|
||||||
|
graphics.color = Color.black
|
||||||
|
graphics.font = font
|
||||||
|
val context = graphics.fontRenderContext
|
||||||
|
val layout =
|
||||||
|
TextLayout("${slice.to} @ ${speed}x (1 frame = ${slice.relative.toSeconds()} seconds)", font, context)
|
||||||
|
layout.draw(graphics, 60f, 60f)
|
||||||
|
graphics.dispose()
|
||||||
val index = slices.indexOf(slice) + 1
|
val index = slices.indexOf(slice) + 1
|
||||||
val suffix = "-${index.toString().padStart(imagePadCount, '0')}"
|
val suffix = "-${index.toString().padStart(imagePadCount, '0')}"
|
||||||
result.savePngFile("${render.id}${suffix}.png")
|
result.savePngFile("${render.id}${suffix}.png")
|
||||||
logger.info("Rendered Timelapse $index")
|
logger.info("Rendered Timelapse Slice $index")
|
||||||
}
|
}
|
||||||
|
|
||||||
pool.render(slices)
|
pool.render(slices)
|
||||||
|
@ -10,12 +10,15 @@ class BlockChangelog(
|
|||||||
val changes: List<BlockChange>
|
val changes: List<BlockChange>
|
||||||
) {
|
) {
|
||||||
fun slice(slice: BlockChangelogSlice): BlockChangelog = BlockChangelog(changes.filter {
|
fun slice(slice: BlockChangelogSlice): BlockChangelog = BlockChangelog(changes.filter {
|
||||||
it.time >= slice.first &&
|
slice.isTimeWithin(it.time)
|
||||||
it.time <= slice.second
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
fun countRelativeChangesInSlice(slice: BlockChangelogSlice): Int = changes.count {
|
||||||
|
slice.isRelativeWithin(it.time)
|
||||||
|
}
|
||||||
|
|
||||||
val changeTimeRange: BlockChangelogSlice
|
val changeTimeRange: BlockChangelogSlice
|
||||||
get() = changes.minOf { it.time } to changes.maxOf { it.time }
|
get() = BlockChangelogSlice(changes.minOf { it.time }, changes.maxOf { it.time })
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun query(db: Database, filter: Op<Boolean> = Op.TRUE): BlockChangelog = transaction(db) {
|
fun query(db: Database, filter: Op<Boolean> = Op.TRUE): BlockChangelog = transaction(db) {
|
||||||
|
@ -1,5 +1,23 @@
|
|||||||
package cloud.kubelet.foundation.gjallarhorn.state
|
package cloud.kubelet.foundation.gjallarhorn.state
|
||||||
|
|
||||||
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
typealias BlockChangelogSlice = Pair<Instant, Instant>
|
data class BlockChangelogSlice(val from: Instant, val to: Instant, val relative: Duration) {
|
||||||
|
constructor(from: Instant, to: Instant) : this(from, to, Duration.ofMillis(to.toEpochMilli() - from.toEpochMilli()))
|
||||||
|
|
||||||
|
fun changeResolutionTime(): Instant = to.minus(relative)
|
||||||
|
|
||||||
|
fun isTimeWithin(time: Instant) = time in from..to
|
||||||
|
fun isRelativeWithin(time: Instant) = time in changeResolutionTime()..to
|
||||||
|
|
||||||
|
fun split(): List<BlockChangelogSlice> {
|
||||||
|
val half = relative.dividedBy(2)
|
||||||
|
val initial = to.minus(relative)
|
||||||
|
val first = initial.plus(half)
|
||||||
|
return listOf(
|
||||||
|
BlockChangelogSlice(from, first, half),
|
||||||
|
BlockChangelogSlice(from, to, half)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,7 +19,26 @@ class BlockMapTimelapse<T>(val trim: Pair<BlockCoordinate, BlockCoordinate>? = n
|
|||||||
if (limit != null) {
|
if (limit != null) {
|
||||||
intervals = intervals.takeLast(limit).toMutableList()
|
intervals = intervals.takeLast(limit).toMutableList()
|
||||||
}
|
}
|
||||||
return intervals.map { start to it }
|
return intervals.map { BlockChangelogSlice(start, it, interval) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun splitChangelogSlicesWithThreshold(
|
||||||
|
changelog: BlockChangelog,
|
||||||
|
targetChangeThreshold: Int,
|
||||||
|
minimumTimeInterval: Duration,
|
||||||
|
slices: List<BlockChangelogSlice>
|
||||||
|
): List<BlockChangelogSlice> {
|
||||||
|
return slices.flatMap { slice ->
|
||||||
|
val count = changelog.countRelativeChangesInSlice(slice)
|
||||||
|
if (count < targetChangeThreshold ||
|
||||||
|
slice.relative < minimumTimeInterval
|
||||||
|
) {
|
||||||
|
return@flatMap listOf(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
val split = slice.split()
|
||||||
|
return@flatMap splitChangelogSlicesWithThreshold(changelog, targetChangeThreshold, minimumTimeInterval, split)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun buildRenderJobs(
|
override fun buildRenderJobs(
|
||||||
|
Reference in New Issue
Block a user