Created
January 7, 2020 15:57
-
-
Save liquidev/63537227809e282e88acdb58ba21574a to your computer and use it in GitHub Desktop.
A lava lamp effect in OpenGL, using an alpha threshold shader.
This file contains hidden or 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
import math | |
import os | |
import parseopt | |
import random | |
import strutils | |
import rapid/gfx | |
import rapid/res/textures | |
import rapid/res/images | |
import rapid/gfx/fxsurface | |
#-- | |
# CLI | |
#-- | |
const Help = """ | |
lava_lamp | |
========= | |
usage: lava_lamp [options] | |
options: | |
-h --help this | |
-b --bg|background:<color> background color | |
-f --fg|foreground:<color> foreground color | |
--threshold:<float> alpha threshold | |
--smooth:<float> alpha smoothing | |
-c --spawnChance:<float> blob spawn chance per frame | |
-S --size:<range> blob size | |
-s --speed:<range> blob speed | |
syntax: | |
dec = {'0'..'9'} | |
hex = dec + {'a'..'f', 'A'..'F'} | |
float = dec '.' dec | |
color = hex{6} | |
range = float '..' float | |
""" | |
var | |
colBg = rgb(219, 84, 97) | |
colFg = rgb(229, 115, 105) | |
threshold = 0.4 | |
smooth = 0.025 | |
spawnChance = 0.02 | |
size = 32.0..128.0 | |
speed = 0.5..1.0 | |
proc parseColor(input: string): RColor = | |
result = rgb(parseHexInt(input[0..1]), | |
parseHexInt(input[2..3]), | |
parseHexInt(input[4..5])) | |
proc parseRange(input: string): Slice[float] = | |
let elem = input.split("..") | |
result = elem[0].parseFloat..elem[1].parseFloat | |
for kind, key, val in getopt(commandLineParams()): | |
case kind | |
of cmdShortOption, cmdLongOption: | |
case key | |
of "h", "help": quit(Help, 0) | |
of "b", "bg", "background": colBg = parseColor(val) | |
of "f", "fg", "foreground": colFg = parseColor(val) | |
of "threshold": threshold = parseFloat(val) | |
of "smooth": smooth = parseFloat(val) | |
of "c", "spawnChance": spawnChance = parseFloat(val) | |
of "S", "size": size = parseRange(val) | |
of "s", "speed": speed = parseRange(val) | |
else: quit("invalid option kind") | |
#-- | |
# definitions | |
#-- | |
type | |
Blob* = object | |
pos, vel: Vec2[float] | |
radius: float | |
const | |
AlphaThreshold = """ | |
uniform float threshold; | |
uniform float smooth; | |
vec4 rEffect(vec2 pos) { | |
vec4 pix = rPixel(pos); | |
float a = smoothstep(threshold - smooth, threshold + smooth, pix.r); | |
return vec4(a, a, a, a); | |
} | |
""" | |
#-- | |
# the implementation | |
#-- | |
# generate the blob texture | |
const BlobResolution = 128 | |
var blobData: seq[uint8] | |
func gaussian2d(x, y, a, ox, oy, sx, sy: float): float = | |
## 2D gaussian curve function. | |
a * exp(-((x - ox) ^ 2 / (2 * sx ^ 2) + (y - oy) ^ 2 / (2 * sy ^ 2))) | |
for py in 0..<BlobResolution: | |
for px in 0..<BlobResolution: | |
let | |
x = px / BlobResolution * 2 - 1 | |
y = py / BlobResolution * 2 - 1 | |
alphaf = gaussian2d(x, y, a = 1.0, ox = 0.0, oy = 0.0, sx = 0.3, sy = 0.3) | |
alphai = uint8(alphaf * 255.0) | |
blobData.add([255'u8, 255, 255, alphai]) | |
# allocate all the resources | |
var | |
win = initRWindow() | |
.size(800, 600) | |
.title("lava_lamp") | |
.open() | |
surface = win.openGfx() | |
fx = newRFxSurface(surface.canvas) | |
fxAlphaThreshold = fx.newREffect(AlphaThreshold) | |
texBlob = newRTexture(BlobResolution, BlobResolution, blobData[0].addr) | |
var | |
blobs: seq[Blob] | |
# start blobbin' | |
randomize() | |
surface.loop: | |
init ctx: | |
fxAlphaThreshold.param("threshold", threshold) | |
fxAlphaThreshold.param("smooth", smooth) | |
draw ctx, step: | |
ctx.clear(colBg) | |
fx.begin(ctx) | |
ctx.begin() | |
ctx.texture = texBlob | |
for blob in blobs: | |
ctx.rect(blob.pos.x - blob.radius / 2, blob.pos.y - blob.radius / 2, | |
blob.radius, blob.radius) | |
ctx.draw() | |
ctx.noTexture() | |
fx.effect(fxAlphaThreshold) | |
ctx.color = colFg | |
fx.finish() | |
update step: | |
if rand(1.0) < spawnChance: | |
let | |
radius = rand(size) | |
pos = vec2(rand(0.0..surface.width), surface.height + radius / 2) | |
vel = vec2(0.0, -rand(speed)) | |
blob = Blob(pos: pos, vel: vel, radius: radius) | |
blobs.add(blob) | |
for blob in blobs.mitems: | |
blob.pos += blob.vel | |
var garbage: seq[int] | |
for i, blob in blobs: | |
if blob.pos.y + blob.radius < 0: | |
garbage.add(i) | |
for n in countdown(garbage.len - 1, 0): | |
blobs.delete(garbage[n]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment