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.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 -> var f = feedRate var k = killRate Array(cell_count.x) { col -> val d = cells[row][col] val lapl = laplaceThyNeighbour(row, col) val u = -d.x * d.y * d.y + f * (1 - d.x) val v = d.x * d.y * d.y - (f + k) * d.y var change = lapl * diffusion + Vector2(u, v) coerceIn(d + change * dt, 0.0, 1.0) } } } fun srgb(r: Double, g: Double, b: Double): ColorRGBa { return ColorRGBa(Math.pow(r, 2.2), Math.pow(g, 2.2), Math.pow(b, 2.2)) } val col_flesh = srgb(0.53, 0.20, 0.20) val col_blood = srgb(0.34, 0.00, 0.00) val col_s1 = srgb(0.06, 0.28, 0.8) val col_s2 = srgb(0.90, 0.71, 0.13) val col_s3 = srgb(0.04, 0.38, 0.34) val col_s4 = srgb(0.07, 0.1, 0.88) val col_bg = col_s1.shade(0.1) fun drawCell(pos: Vector2, amounts: Vector2) { var color = col_bg.opacify(0.0) // run { // val noiseScale = 0.99 // val noise = perlinLinear(100, pos.x * noiseScale, pos.y * noiseScale) // color = mix(color, col_bg.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) val v = (amounts.y * 3).coerceIn(0.0, 1.0) color = mix(color, col_s1, v) if (amounts.y > 0.20) color = mix(color, col_s1, v) if (amounts.y > 0.26) color = mix(color, col_s2, v) if (amounts.y > 0.30) color = mix(color, col_s1, v) if (amounts.y > 0.34) color = mix(color, col_s1.shade(0.4), v) 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_bg) drawer.shadeStyle = gradient { stops[0.4] = col_bg stops[0.9] = col_bg.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) } } } } }