Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions arts/gen/src/gen/FlowInk.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package gen

import dev.oblac.gart.Gart
import dev.oblac.gart.angle.Degrees
import dev.oblac.gart.flow.Flow2
import dev.oblac.gart.flow.FlowField
import dev.oblac.gart.flow.PointTracer
import dev.oblac.gart.gfx.toPath
import dev.oblac.gart.math.*
import dev.oblac.gart.noise.OpenSimplexNoise
import dev.oblac.gart.noise.poissonDiskSampling
import org.jetbrains.skia.PaintStrokeCap
import kotlin.math.atan2
import kotlin.random.Random

// 16:9 — GART_SIZE sets HEIGHT; width derived. Draft: GART_SIZE=540 -> 960x540.
private val HEIGHT: Int = System.getProperty("GART_SIZE")?.toIntOrNull() ?: 1080
private val WIDTH: Int = HEIGHT * 16 / 9
private val SEED: Long = System.getProperty("GART_SEED")?.toLongOrNull() ?: Random.nextLong()

/** Flow-field streamlines through a simplex-noise vector field, finished with the Ink glow. */
private class Flo(
val tag: String, val spacing: Float, val freq: Double, val angleScale: Float,
val vortex: Float, val steps: Int, val width: Float, val alpha: Int,
)

private val VARIANTS = listOf(
Flo("laminar", 22f, 0.0009, 1.6f, 0.0f, 1300, 2.4f, 50),
Flo("turbulent", 18f, 0.0030, 3.2f, 0.0f, 800, 2.0f, 44),
Flo("vortex", 20f, 0.0014, 1.2f, 0.85f, 1100, 2.2f, 48),
)

fun main() {
println("seed=$SEED")
val master = Random(SEED)
val gart = Gart.of("flowInk", WIDTH, HEIGHT)
val d = gart.d
val cx = d.cx; val cy = d.cy

VARIANTS.forEachIndexed { vi, cfg ->
val rng = Random(master.nextLong())
val simplex = OpenSimplexNoise(rng.nextLong())
val flowField = FlowField.of(d) { x, y ->
val raw = simplex.random2D(x * cfg.freq, y * cfg.freq).toFloat()
val n = raw * 360f * cfg.angleScale
val ang: Float = if (cfg.vortex > 0f) {
val base = Math.toDegrees(atan2((y - cy).toDouble(), (x - cx).toDouble())).toFloat() + 90f
base * cfg.vortex + n * (1f - cfg.vortex)
} else n
Flow2(Degrees.of(ang), 1f)
}
val seeds = poissonDiskSampling(d.rect, cfg.spacing, random = rng)
val buf = gart.gartvas()
seeds.forEachIndexed { i, s ->
val pts = PointTracer(d, flowField).trace(s, cfg.steps)
if (pts.size < 2) return@forEachIndexed
val color = when {
i % 6 == 0 -> Ink.WHITE
i % 2 == 0 -> Ink.TEAL
else -> Ink.PURPLE
}
val a = if (color == Ink.PURPLE) (cfg.alpha * 1.7f).toInt() else cfg.alpha
val paint = Ink.stroke(color, a, cfg.width).apply { strokeCap = PaintStrokeCap.ROUND }
buf.canvas.drawPath(pts.toPath(), paint)
}
val finalv = Ink.finish(gart, buf, 0.09f)
gart.saveImage(finalv, "flowInk${vi + 1}.png")
println(" flowInk${vi + 1}.png (${cfg.tag}, seeds=${seeds.size})")
}
println("done")
}
72 changes: 72 additions & 0 deletions arts/gen/src/gen/GrowInk.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package gen

import dev.oblac.gart.Gart
import dev.oblac.gart.grow.Growth
import org.jetbrains.skia.PaintStrokeCap
import org.jetbrains.skia.PaintStrokeJoin
import kotlin.random.Random

// 16:9 — GART_SIZE sets HEIGHT; width derived.
private val HEIGHT: Int = System.getProperty("GART_SIZE")?.toIntOrNull() ?: 1080
private val WIDTH: Int = HEIGHT * 16 / 9
private val SEED: Long = System.getProperty("GART_SEED")?.toLongOrNull() ?: Random.nextLong()

/**
* Differential growth — a closed polyline wrinkles outward, bounded by maxRadius so it
* stays in frame. Colour bands by growth time (teal core -> white -> purple rim), which
* keeps both fiuto accents spatially separated and visible. NB: Growth uses global RNG
* internally, so it is not byte-for-byte reproducible by GART_SEED.
*/
private class Gro(
val tag: String, val maxEdge: Float, val rejection: Float, val attraction: Float,
val brownian: Float, val seedR: Float, val seedN: Int, val steps: Int, val alpha: Int,
)

private val VARIANTS = listOf(
Gro("coral", 6f, 14f, 0.15f, 0.6f, 30f, 3, 600, 14),
Gro("lobes", 7f, 18f, 0.18f, 0.5f, 40f, 4, 560, 13),
Gro("dense", 6f, 12f, 0.13f, 0.55f, 26f, 3, 520, 12),
)

fun main() {
println("seed=$SEED")
val gart = Gart.of("growInk", WIDTH, HEIGHT)
val d = gart.d
val bound = d.h * 0.46f

VARIANTS.forEachIndexed { vi, cfg ->
val growth = Growth(
maxEdgeLength = cfg.maxEdge,
rejectionRadius = cfg.rejection,
attractionStrength = cfg.attraction,
rejectionStrength = 0.5f,
brownianStrength = cfg.brownian,
centerX = d.cx,
centerY = d.cy,
maxRadius = bound,
)
growth.seedCircle(d.cx, d.cy, cfg.seedR, cfg.seedN)
val buf = gart.gartvas()
val c = buf.canvas
var step = 0
while (step < cfg.steps && !growth.done && growth.size < 60_000) {
growth.step()
val t = step.toFloat() / cfg.steps
val color = when {
t < 0.40f -> Ink.TEAL
t < 0.60f -> Ink.WHITE
else -> Ink.PURPLE
}
val a = if (color == Ink.PURPLE) (cfg.alpha * 1.7f).toInt() else cfg.alpha
c.drawPath(growth.toPath(), Ink.stroke(color, a, 0.8f).apply {
strokeCap = PaintStrokeCap.ROUND
strokeJoin = PaintStrokeJoin.ROUND
})
step++
}
val finalv = Ink.finish(gart, buf, 0.09f)
gart.saveImage(finalv, "growInk${vi + 1}.png")
println(" growInk${vi + 1}.png (${cfg.tag}, nodes=${growth.size}, steps=$step)")
}
println("done")
}
75 changes: 75 additions & 0 deletions arts/gen/src/gen/Ink.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package gen

import dev.oblac.gart.Gart
import dev.oblac.gart.Gartvas
import dev.oblac.gart.color.CssColors
import dev.oblac.gart.gfx.strokeOf
import dev.oblac.gart.shader.createNoiseGrainFilter
import org.jetbrains.skia.BlendMode
import org.jetbrains.skia.FilterTileMode
import org.jetbrains.skia.ImageFilter
import org.jetbrains.skia.Paint

/**
* Shared "light in the dark" finishing pass + fiuto tones, used by every gen ink piece
* so the whole family stays consistent. Tones lifted from the fiuto repo:
* - navy ground #071426 (favicon background)
* - teal accent #00D3BB (logo fill)
* - purple accent #5E49D6 (default slides-theme accent)
*/
object Ink {
val GROUND = 0xFF071426.toInt()
val TEAL = 0xFF00D3BB.toInt()
val PURPLE = 0xFF5E49D6.toInt()
val WHITE = CssColors.white

fun stroke(color: Int, alpha: Int, width: Float = 1f): Paint =
strokeOf(color, width).apply { this.alpha = alpha.coerceIn(0, 255); isAntiAlias = true }

/**
* Composite a *transparent* stroke buffer over the navy ground with SCREEN-blend
* Gaussian-blur bloom (so dense areas glow), then a noise-grain texture pass.
* Returns the final gartvas, ready to saveImage.
*/
fun finish(gart: Gart, buf: Gartvas, grain: Float = 0.10f): Gartvas {
val d = gart.d
val sharp = buf.snapshot()
val out = gart.gartvas()
val oc = out.canvas
oc.clear(GROUND)
val s = d.h / 170f
for (sigma in listOf(s, s / 2.5f)) {
oc.drawImage(sharp, 0f, 0f, Paint().apply {
imageFilter = ImageFilter.makeBlur(sigma, sigma, FilterTileMode.DECAL)
blendMode = BlendMode.SCREEN
})
}
oc.drawImage(sharp, 0f, 0f, Paint().apply { blendMode = BlendMode.SCREEN })
val finalv = gart.gartvas()
finalv.canvas.drawImage(out.snapshot(), 0f, 0f, Paint().apply {
imageFilter = createNoiseGrainFilter(grain, d)
})
return finalv
}

// ---- colour helpers for per-pixel pieces (plasma) ----

private fun lerp(a: Int, b: Int, t: Float): Int {
val ar = (a ushr 16) and 0xFF; val ag = (a ushr 8) and 0xFF; val ab = a and 0xFF
val br = (b ushr 16) and 0xFF; val bg = (b ushr 8) and 0xFF; val bb = b and 0xFF
val r = (ar + (br - ar) * t).toInt()
val g = (ag + (bg - ag) * t).toInt()
val bl = (ab + (bb - ab) * t).toInt()
return (0xFF shl 24) or (r shl 16) or (g shl 8) or bl
}

/** Multi-stop colour ramp; t in [0,1]. */
fun ramp(stops: List<Int>, t: Float): Int {
val tt = t.coerceIn(0f, 1f) * (stops.size - 1)
val i = tt.toInt().coerceAtMost(stops.size - 2)
return lerp(stops[i], stops[i + 1], tt - i)
}

/** navy→purple→teal→white→teal→purple→navy — both fiuto accents, cycles seamlessly. */
val PLASMA_STOPS = listOf(GROUND, PURPLE, TEAL, WHITE, TEAL, PURPLE, GROUND)
}
89 changes: 89 additions & 0 deletions arts/gen/src/gen/LissaInk.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package gen

import dev.oblac.gart.Gart
import dev.oblac.gart.gfx.drawLine
import dev.oblac.gart.math.*
import kotlin.math.PI
import kotlin.math.exp
import kotlin.math.sin
import kotlin.random.Random
import org.jetbrains.skia.Point

// 16:9 — GART_SIZE sets HEIGHT; width derived.
private val HEIGHT: Int = System.getProperty("GART_SIZE")?.toIntOrNull() ?: 1080
private val WIDTH: Int = HEIGHT * 16 / 9
private val SEED: Long = System.getProperty("GART_SEED")?.toLongOrNull() ?: Random.nextLong()

private val HALF_PI = (PI / 2).toFloat()

/**
* Lissajous figures: x = sin(a·t + δ), y = sin(b·t), phase-swept into a glowing bundle.
* Colour is banded across the sweep (early copies teal, late copies purple) so the two
* fiuto accents land at different orientations and both stay visible. Some variants damp
* (exp decay) into spirals. High variance across the 10 via the a:b frequency ratio.
*/
private class Lis(
val tag: String, val a: Float, val b: Float, val phase: Float,
val decay: Float, val copies: Int, val phaseStep: Float, val alpha: Int,
)

private val VARIANTS = listOf(
Lis("1:2", 1f, 2f, HALF_PI, 0.0f, 90, 0.040f, 16),
Lis("2:3", 2f, 3f, HALF_PI, 0.0f, 100, 0.036f, 16),
Lis("3:4", 3f, 4f, 0.4f, 0.0f, 110, 0.034f, 15),
Lis("3:5", 3f, 5f, 0.7f, 0.0f, 110, 0.032f, 15),
Lis("4:5", 4f, 5f, HALF_PI, 0.0f, 120, 0.030f, 15),
Lis("5:6", 5f, 6f, 0.3f, 0.0f, 120, 0.030f, 14),
Lis("5:7", 5f, 7f, 0.9f, 0.0f, 130, 0.028f, 14),
Lis("7:9", 7f, 9f, 0.5f, 0.0f, 130, 0.026f, 13),
Lis("damped-3:2", 3f, 2f, 0.2f, 0.012f, 150, 0.045f, 14),
Lis("damped-4:7", 4f, 7f, 0.6f, 0.015f, 150, 0.045f, 13),
)

fun main() {
println("seed=$SEED")
val master = Random(SEED)
val gart = Gart.of("lissaInk", WIDTH, HEIGHT)
val d = gart.d
val cx = d.cx; val cy = d.cy
val amp = d.h * 0.46f
val iterations = 1400
val tStep = 0.01f

VARIANTS.forEachIndexed { vi, cfg ->
val rng = Random(master.nextLong())
val p0 = rng.nextDouble(-PI, PI).toFloat()
val buf = gart.gartvas()
val c = buf.canvas
// Nested concentric Lissajous: scale grows small->full across copies, so the
// teal (inner) and purple (outer) colour bands separate radially and both read.
repeat(cfg.copies) { k ->
val frac = k.toFloat() / (cfg.copies - 1).coerceAtLeast(1)
val s = amp * (0.22f + 0.78f * frac)
val dp = p0 + k * cfg.phaseStep
val color = when {
frac < 0.42f -> Ink.TEAL
frac < 0.58f -> Ink.WHITE
else -> Ink.PURPLE
}
val al = if (color == Ink.PURPLE) (cfg.alpha * 1.8f).toInt() else cfg.alpha
val paint = Ink.stroke(color, al, 1f)
var prev: Point? = null
var t = 0f
repeat(iterations) {
val e = if (cfg.decay > 0f) exp(-cfg.decay * t) else 1f
val pt = Point(
cx + s * e * sin(cfg.a * t + cfg.phase + dp),
cy + s * e * sin(cfg.b * t),
)
prev?.let { c.drawLine(it, pt, paint) }
prev = pt
t += tStep
}
}
val finalv = Ink.finish(gart, buf, 0.09f)
gart.saveImage(finalv, "lissaInk${vi + 1}.png")
println(" lissaInk${vi + 1}.png (${cfg.tag})")
}
println("done")
}
72 changes: 72 additions & 0 deletions arts/gen/src/gen/PackInk.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package gen

import dev.oblac.gart.Gart
import org.jetbrains.skia.PaintStrokeCap
import kotlin.math.min
import kotlin.math.sqrt
import kotlin.random.Random

// 16:9 — GART_SIZE sets HEIGHT; width derived.
private val HEIGHT: Int = System.getProperty("GART_SIZE")?.toIntOrNull() ?: 1080
private val WIDTH: Int = HEIGHT * 16 / 9
private val SEED: Long = System.getProperty("GART_SEED")?.toLongOrNull() ?: Random.nextLong()

/** Circle packing — largest-fit non-overlapping circles drawn as nested glowing rings. */
private class Pak(
val tag: String, val target: Int, val minR: Float, val maxR: Float,
val rings: Int, val width: Float, val alpha: Int,
)

private val VARIANTS = listOf(
Pak("big", 60, 24f, 240f, 4, 2.4f, 95),
Pak("dense", 340, 7f, 130f, 2, 1.6f, 80),
Pak("nested", 150, 13f, 180f, 6, 1.9f, 78),
)

private class C(val x: Float, val y: Float, val r: Float)

fun main() {
println("seed=$SEED")
val master = Random(SEED)
val gart = Gart.of("packInk", WIDTH, HEIGHT)
val d = gart.d
val w = d.w.toFloat(); val h = d.h.toFloat()
val margin = h * 0.05f

VARIANTS.forEachIndexed { vi, cfg ->
val rng = Random(master.nextLong())
val circles = ArrayList<C>()
var attempts = 0
val maxAttempts = cfg.target * 500
while (circles.size < cfg.target && attempts < maxAttempts) {
attempts++
val x = (margin + rng.nextFloat() * (w - 2 * margin))
val y = (margin + rng.nextFloat() * (h - 2 * margin))
var r = min(min(x - margin, w - margin - x), min(y - margin, h - margin - y))
for (c in circles) {
val gap = sqrt((x - c.x) * (x - c.x) + (y - c.y) * (y - c.y)) - c.r - 2f
if (gap < r) r = gap
if (r < cfg.minR) break
}
if (r >= cfg.minR) circles.add(C(x, y, min(r, cfg.maxR)))
}
val buf = gart.gartvas()
val c = buf.canvas
circles.forEachIndexed { i, circ ->
val color = when {
i % 6 == 0 -> Ink.WHITE
i % 2 == 0 -> Ink.TEAL
else -> Ink.PURPLE
}
val a = if (color == Ink.PURPLE) (cfg.alpha * 1.7f).toInt() else cfg.alpha
val paint = Ink.stroke(color, a, cfg.width).apply { strokeCap = PaintStrokeCap.ROUND }
for (k in 1..cfg.rings) {
c.drawCircle(circ.x, circ.y, circ.r * k / cfg.rings, paint)
}
}
val finalv = Ink.finish(gart, buf, 0.09f)
gart.saveImage(finalv, "packInk${vi + 1}.png")
println(" packInk${vi + 1}.png (${cfg.tag}, n=${circles.size})")
}
println("done")
}
Loading