diff options
Diffstat (limited to '')
| -rw-r--r-- | src/main/kotlin/blood/BloodConfigs.kt | 106 | ||||
| -rw-r--r-- | src/main/kotlin/blood/BloodProgram.kt | 120 |
2 files changed, 226 insertions, 0 deletions
diff --git a/src/main/kotlin/blood/BloodConfigs.kt b/src/main/kotlin/blood/BloodConfigs.kt new file mode 100644 index 0000000..d6630e6 --- /dev/null +++ b/src/main/kotlin/blood/BloodConfigs.kt @@ -0,0 +1,106 @@ +package blood + +import kotlin.random.Random +import org.openrndr.math.IntVector2 +import org.openrndr.math.Vector2 + +typealias Cells = Array<Array<Vector2>> + +class BloodConfigs { + abstract class Base { + abstract val feedRate: Double + abstract val killRate: Double + open val iterationsPerFrame: Int = 40 + open val diffusion: Vector2 = Vector2(1.0, 0.5) + open val random: Random = Random(100) + open val initialSize: IntVector2 = IntVector2(20, 20) + open val noiseProbability: Double = 1.0 + + open fun initializeState(cells: Cells) { + val x = cells[0].size / 2 - initialSize.x / 2 + val y = cells.size / 2 - initialSize.y / 2 + for (i in y until (y + initialSize.y)) { + for (j in x until (x + initialSize.x)) { + val v = random.nextDouble(1.0) <= noiseProbability + cells[i][j] = if (v) Vector2(0.0, 1.0) else Vector2(1.0, 0.0) + } + } + } + } + + class mitosis : Base() { + override val feedRate = 0.0367 + override val killRate = 0.0649 + override val noiseProbability = 0.1 + override val random = Random(0) + } + + class coral : Base() { + override val feedRate = 0.055 + override val killRate = 0.062 + } + + class holey : Base() { + override val feedRate = 0.043 + override val killRate = 0.059 + } + + class pulseSettled1 : Base() { + override val feedRate = 0.037 + override val killRate = 0.059 + } + + class pulseSettled2 : Base() { + override val feedRate = 0.029 + override val killRate = 0.057 + } + + class bubbles : Base() { + override val feedRate = 0.014 + override val killRate = 0.054 + override val noiseProbability = 0.2 + } + + class worms : Base() { + override val feedRate = 0.078 + override val killRate = 0.061 + override val random = Random(200) + override val noiseProbability = 0.1 + override val initialSize = IntVector2(200, 50) + } + + class fingies : Base() { + override val feedRate = 0.046 + override val killRate = 0.063 + } + + class maze : Base() { + override val feedRate = 0.029 + override val killRate = 0.057 + override val noiseProbability = 0.2 + } + + class explosions : Base() { + override val feedRate = 0.018 + override val killRate = 0.051 + override val noiseProbability = 0.2 + override val iterationsPerFrame = 18 + } + + class waves : Base() { + override val feedRate = 0.014 + override val killRate = 0.045 + override val iterationsPerFrame = 18 + } +} + +fun getConfig(name: String): BloodConfigs.Base { + val cls = BloodConfigs::class.nestedClasses.first { it.simpleName == name } + val ctor = cls.constructors.first() + val inst = ctor.call() as BloodConfigs.Base + return inst +} + +fun getConfig(): BloodConfigs.Base { + return getConfig(System.getenv("PHENAX_FILE_SUFFIX") ?: "pulseSettled2") +} diff --git a/src/main/kotlin/blood/BloodProgram.kt b/src/main/kotlin/blood/BloodProgram.kt new file mode 100644 index 0000000..ed70213 --- /dev/null +++ b/src/main/kotlin/blood/BloodProgram.kt @@ -0,0 +1,120 @@ +package blood + +// import org.openrndr.extra.timer.repeat + +import blood.* +import org.openrndr.WindowMultisample +import org.openrndr.application +import org.openrndr.color.* +import org.openrndr.draw.* +import org.openrndr.extra.noise.perlinLinear +import org.openrndr.extra.shadestyles.fills.gradients.gradient +import org.openrndr.math.IntVector2 +import org.openrndr.math.Vector2 +import org.openrndr.shape.Rectangle +import phenax.utils.* + +fun main() = application { + recordDuration(15.seconds()) + + if (captureEnabled) { + configure { + width = 1920 + height = 1080 + windowResizable = false + multisample = WindowMultisample.SampleCount(8) + } + } else { + configure { + width = 640 + height = 360 + windowResizable = false + } + } + + oliveProgram { + val cell_size = 3.0 + val cell_count = IntVector2(width, height) / cell_size.toInt() + val config = getConfig() + + var cells: Cells = Array(cell_count.y) { Array(cell_count.x) { Vector2(1.0, 0.0) } } + config.initializeState(cells) + + val feedRate = config.feedRate + val killRate = config.killRate + val diffusion = config.diffusion + + fun laplaceThyNeighbour(row: Int, col: Int): Vector2 { + var d = Vector2.ZERO + for (i in -1 until 2) { + for (j in -1 until 2) { + val r = row + i + val c = col + j + val weight = if (i == 0 && j == 0) -1.0 else if (i == 0 || j == 0) 0.2 else 0.05 + if (r >= 0 && r < cell_count.y && c >= 0 && c < cell_count.x) d += cells[r][c] * weight + } + } + return d + } + + fun coerceIn(v: Vector2, min: Double, max: Double): Vector2 { + return Vector2(v.x.coerceIn(min, max), v.y.coerceIn(min, max)) + } + + fun nextState(dt: Double): Cells { + return Array(cell_count.y) { row -> + Array(cell_count.x) { col -> + val d = cells[row][col] + val lapl = laplaceThyNeighbour(row, col) + val u = -d.x * d.y * d.y + feedRate * (1 - d.x) + val v = d.x * d.y * d.y - (feedRate + killRate) * d.y + var change = lapl * diffusion + Vector2(u, v) + coerceIn(d + change * dt, 0.0, 1.0) + } + } + } + + val col_flesh = ColorRGBa(0.53, 0.20, 0.20) + val col_blood = ColorRGBa(0.34, 0.00, 0.00) + + fun drawCell(pos: Vector2, amounts: Vector2) { + var color = col_flesh.opacify(0.0) + + run { + val noiseScale = 0.99 + val noise = perlinLinear(100, pos.x * noiseScale, pos.y * noiseScale) + color = mix(color, col_flesh.shade(1.1).opacify(0.14), noise) + } + + color = mix(color, col_blood, (amounts.y * 3).coerceIn(0.0, 1.0)) + if (amounts.y > 0.38) color = mix(color, ColorRGBa.BLACK, amounts.y * 0.08) + if (amounts.y > 0.40) color = mix(color, ColorRGBa.WHITE, amounts.y * 0.28) + + drawer.fill = color + drawer.stroke = drawer.fill + drawer.rectangle(pos.x * cell_size, pos.y * cell_size, cell_size, cell_size) + } + + extend { repeat(config.iterationsPerFrame) { cells = nextState(0.8) } } + + extend { + drawer.clear(col_flesh) + drawer.shadeStyle = + gradient<ColorRGBa> { + stops[0.4] = col_flesh + stops[0.9] = col_flesh.shade(0.65) + radial {} + } + drawer.rectangle( + Rectangle.fromCenter(drawer.bounds.center, width.toDouble(), height.toDouble()) + ) + drawer.shadeStyle = null + drawer.strokeWeight = 0.0 + for ((row, rowdiff) in cells.withIndex()) { + for ((col, diff) in rowdiff.withIndex()) { + drawCell(Vector2(col.toDouble(), row.toDouble()), diff) + } + } + } + } +} |
