mirror of
				https://github.com/GayPizzaSpecifications/foundation.git
				synced 2025-11-04 11:39:39 +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