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