aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/blood/BloodProgram.kt
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/blood/BloodProgram.kt')
-rw-r--r--src/main/kotlin/blood/BloodProgram.kt120
1 files changed, 120 insertions, 0 deletions
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)
+ }
+ }
+ }
+ }
+}