Skip to content

Instantly share code, notes, and snippets.

@loleg
Last active June 7, 2025 09:47
Show Gist options
  • Save loleg/4774449a0ec323d78f45658ff63bde61 to your computer and use it in GitHub Desktop.
Save loleg/4774449a0ec323d78f45658ff63bde61 to your computer and use it in GitHub Desktop.
Cosin graffiti simulator for TIC-80 https://make.echtzeitkultur.org/project/187
-- title: graffataq
-- author: seism
-- desc: Make drippy murals inspired by graffiti #CoSin
-- site: https://make.echtzeitkultur.org/project/187
-- license: MIT License
-- version: 0.3
-- script: lua
-- References:
-- - Pixel buffer methods and general idea borrowed from PAINT 31 https://tic80.com/play?cart=686
-- - Discussion on simulating brush strokes https://stackoverflow.com/questions/3066820/simulating-brush-strokes-for-painting-application
-- - Discussion on real-time splines https://stackoverflow.com/questions/40948915/spline-with-multiple-durations
-- - Paper with more detail on the above https://people.csail.mit.edu/sarasu/pub/cgim02/cgim02.pdf
-- - Another classic paper on the subject https://www.researchgate.net/publication/220982699_Artistic_brushstroke_representation_and_animation_with_disk_B-spline_curve
-- - WibblyWobbly game code showing how to spline in Lua https://github.com/JettMonstersGoBoom/wibblywobbly/blob/731e4ab30e2c6ca5b4e99cf9ef41f862ecd5bd57/main.lua#L113
-- - A chat log with Qwen3 used to generate a function https://hf.co/chat/r/2nq-EUB?leafId=09892b1b-ca76-46bf-bced-ab48cae3aaf5
local W = 240
local H = 136
local t = 0
local col = 1
local width = 4
local samples_per_segment = 16
local control_points = {}
local needs_help = true
-- The following functions were based on the code in PAINT 31
function draw()
local pal = 0
local ini = 0
for x = 0 + ini, W - 1 do
for y = 0, H - 1 do
local c = mget(x, y)
if c > 0 then
pix(x, y, c - pal)
end
end
end
end
function OVR()
poke(0x03FF8, 15)
cls(0)
-- updpal(2)
spr(256, 0, 0, 1, 1, 1, 0, 16, 16)
spr(256, 128, 0, 1, 1, -1, -1, 16, 16)
-- spr(256, 120 - 64, 0, -1, 1, 0, 0, 16, 16)
-- spr(256, 32, 0, -1, 1, 1, 0, 5, 16)
-- spr(256, 120 + 64, 0, -1, 1, 0, 0, 4, 16)
draw()
-- print(string.format("CP: %d", #control_points), 10, 10, 2)
rect(W - 2, H - 8, 2, 8, col)
if needs_help then
print("left click to paint, r-click for new color ->", 3, 129, 12)
end
end
function SCN()
--updpal(1)
end
--updpal(2)
sync(0, 0, true)
-- This Catmull-Rom algorith was spit out by Qwen
function evaluate_spline(p0, p1, p2, p3, s)
local s2 = s * s
local s3 = s2 * s
local x = 0.5
* (
(-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * s3
+ (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * s2
+ (-p0.x + p2.x) * s
+ 2 * p1.x
)
local y = 0.5
* (
(-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * s3
+ (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * s2
+ (-p0.y + p2.y) * s
+ 2 * p1.y
)
return { x = x, y = y }
end
function round(r)
return math.floor(r + 0.5)
end
function brush(x, y, size)
for i = round(y - size / 2), round(y + size / 2) do
mset(x, i, col)
end
end
function swipe(x, y)
if #control_points == 4 then
table.remove(control_points, 1)
end
table.insert(control_points, { x = x, y = y })
if #control_points == 4 then
-- Get neighboring control points for the current segment
local p0 = control_points[1]
local p1 = control_points[2]
local p2 = control_points[3]
local p3 = control_points[4]
for j = 0, samples_per_segment do
local i = j / samples_per_segment
local position = evaluate_spline(p0, p1, p2, p3, i)
local xx = position.x
local yy = position.y
local ww = width
-- Make drips once in a while
if math.random() < 0.005 then
ww = ww * round(math.random() * 10)
yy = yy + round(ww / 2)
end
brush(xx, yy, ww)
end
end
end
function dibujarlite()
local x, y, l, m, r = mouse()
if l or m or r then
-- First click
needs_help = false
end
if x < W and x > 0 and y < H and y > 0 then
if l then
-- Start swiping
swipe(x, y)
end
if m then
-- Just a dab
brush(x, y, width)
end
end
if r and t % 2 == 0 then
-- Change color
col = col + 1
if col > 14 then
col = 1
end
-- Reset the brush
control_points = {}
end
end
cls(13)
function TIC()
dibujarlite()
t = t + 1
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment