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 org.jetbrains.exposed.sql.Database
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.awt.Color
|
||||
import java.awt.Font
|
||||
import java.awt.font.TextLayout
|
||||
import java.awt.image.BufferedImage
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor
|
||||
@ -22,6 +25,15 @@ class BlockChangeTimelapseCommand : CliktCommand("Block Change Timelapse", name
|
||||
private val db by requireObject<Database>()
|
||||
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 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 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)
|
||||
|
||||
override fun run() {
|
||||
val threadPoolExecutor = ScheduledThreadPoolExecutor(8)
|
||||
val threadPoolExecutor = ScheduledThreadPoolExecutor(16)
|
||||
val changelog = BlockChangelog.query(db)
|
||||
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 pool = BlockMapRenderPool(
|
||||
changelog = changelog,
|
||||
@ -44,10 +66,20 @@ class BlockChangeTimelapseCommand : CliktCommand("Block Change Timelapse", name
|
||||
rendererFactory = { expanse -> render.create(expanse) },
|
||||
threadPoolExecutor = threadPoolExecutor
|
||||
) { 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 suffix = "-${index.toString().padStart(imagePadCount, '0')}"
|
||||
result.savePngFile("${render.id}${suffix}.png")
|
||||
logger.info("Rendered Timelapse $index")
|
||||
logger.info("Rendered Timelapse Slice $index")
|
||||
}
|
||||
|
||||
pool.render(slices)
|
||||
|
@ -10,12 +10,15 @@ class BlockChangelog(
|
||||
val changes: List<BlockChange>
|
||||
) {
|
||||
fun slice(slice: BlockChangelogSlice): BlockChangelog = BlockChangelog(changes.filter {
|
||||
it.time >= slice.first &&
|
||||
it.time <= slice.second
|
||||
slice.isTimeWithin(it.time)
|
||||
})
|
||||
|
||||
fun countRelativeChangesInSlice(slice: BlockChangelogSlice): Int = changes.count {
|
||||
slice.isRelativeWithin(it.time)
|
||||
}
|
||||
|
||||
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 {
|
||||
fun query(db: Database, filter: Op<Boolean> = Op.TRUE): BlockChangelog = transaction(db) {
|
||||
|
@ -1,5 +1,23 @@
|
||||
package cloud.kubelet.foundation.gjallarhorn.state
|
||||
|
||||
import java.time.Duration
|
||||
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) {
|
||||
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(
|
||||
|
Reference in New Issue
Block a user