Flow fields
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//> using scala "3.3.1" | |
//> using lib "eu.joaocosta::minart::0.5.3" | |
import eu.joaocosta.minart.graphics.* | |
import eu.joaocosta.minart.backend.defaults.* | |
import eu.joaocosta.minart.runtime.* | |
import scala.util.Random | |
val canvasWidth = 512 | |
val canvasHeight = 512 | |
val backgroundColor = Color(0, 0, 0) | |
val gridWidth = 100 | |
val gridHeight = 100 | |
val yScale = gridHeight.toDouble / canvasHeight | |
val xScale = gridWidth.toDouble / canvasWidth | |
val displacement = Random.nextDouble() * 5 | |
def noise(x: Double, y: Double, t: Double): Double = | |
math.cos(t + x * 0.005) * math.cos(t + y * 0.01) * math.cos( | |
t + x * y * 0.0005 | |
) | |
val grid = Vector.tabulate(gridHeight): y => | |
Vector.tabulate(gridWidth): x => | |
2 * math.Pi * noise(x, y, displacement) | |
def screenToGrid(x: Double, y: Double): Option[Double] = | |
if (x >= 0 && x < canvasWidth && y >= 0 && y < canvasHeight) | |
Some(grid((y * yScale).toInt)((x * xScale).toInt)) | |
else None | |
def addPixel( | |
surface: MutableSurface, | |
x: Int, | |
y: Int, | |
color: Color | |
): Unit = | |
surface.putPixel( | |
x, | |
y, | |
color + surface.getPixel(x, y).getOrElse(backgroundColor) | |
) | |
def drawLine( | |
surface: MutableSurface, | |
x0: Int, | |
y0: Int, | |
x1: Int, | |
y1: Int, | |
color: Color | |
): Unit = | |
val dx = x1 - x0 | |
val dy = y1 - y0 | |
if (dx == 0) | |
((y0 until y1) ++ (y1 until y0)).foreach: y => | |
addPixel(surface, x0, y, color) | |
else if (dy == 0) | |
((x0 until x1) ++ (x1 until x0)).foreach: x => | |
addPixel(surface, x, y0, color) | |
else if (dx > dy) | |
val m = (dy.toDouble / dx) | |
((x0 until x1) ++ (x1 until x0)).foreach: x => | |
addPixel(surface, x, y0 + (m * (x - x0)).toInt, color) | |
else | |
val m = (dx.toDouble / dy) | |
((y0 until y1) ++ (y1 until y0)).foreach: y => | |
addPixel(surface, x0 + (m * (y - y0)).toInt, y, color) | |
def drawFlow( | |
surface: MutableSurface, | |
x0: Double, | |
y0: Double, | |
color: Color, | |
iters: Int, | |
stepSize: Int | |
): Unit = | |
if (iters <= 0) () | |
else | |
screenToGrid(x0, y0) match | |
case None => () | |
case Some(theta) => | |
val x1 = (x0 + math.cos(theta) * stepSize) | |
val y1 = (y0 + math.sin(theta) * stepSize) | |
drawLine(surface, x0.toInt, y0.toInt, x1.toInt, y1.toInt, color) | |
drawFlow(surface, x1, y1, color, iters - 1, stepSize) | |
def randomColor(): Color = | |
Color(Random.nextInt(32), Random.nextInt(32), Random.nextInt(32)) | |
val startPoints = | |
List.fill(1000)( | |
(Random.nextInt(canvasWidth), Random.nextInt(canvasHeight), randomColor()) | |
) | |
AppLoop | |
.statelessRenderLoop: (canvas: Canvas) => | |
canvas.clear() | |
val buffer = new RamSurface(canvasWidth, canvasHeight, backgroundColor) | |
startPoints.foreach: (x, y, color) => | |
drawFlow(buffer, x, y, color, 100, 5) | |
val postProcessed = Plane | |
.fromSurfaceWithFallback(buffer, backgroundColor) | |
.coflatMap: plane => // Bloom | |
(for | |
x <- -1 to 1 | |
y <- -1 to 1 | |
yield plane(x, y)).foldLeft(plane(0, 0)): | |
case (acc, Color(r, g, b)) => acc + Color(r / 9, g / 9, b / 9) | |
.toSurfaceView(canvasWidth, canvasHeight) | |
canvas.blit(postProcessed)(0, 0) | |
canvas.redraw() | |
.configure( | |
Canvas.Settings( | |
width = canvasWidth, | |
height = canvasHeight, | |
clearColor = backgroundColor, | |
title = "Flow Fields" | |
), | |
LoopFrequency.Never | |
) | |
.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment