Skip to content

Instantly share code, notes, and snippets.

@JD557

JD557/flow.sc Secret

Last active October 3, 2023 21:00
Show Gist options
  • Save JD557/1ed8e0111313cb77a04d8c77719d3bbd to your computer and use it in GitHub Desktop.
Save JD557/1ed8e0111313cb77a04d8c77719d3bbd to your computer and use it in GitHub Desktop.
Flow fields
//> 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