Skip to content

Instantly share code, notes, and snippets.

@adamico
Last active July 7, 2026 04:23
Show Gist options
  • Select an option

  • Save adamico/f36ebae18dc8d994d5cbf6431ec8df35 to your computer and use it in GitHub Desktop.

Select an option

Save adamico/f36ebae18dc8d994d5cbf6431ec8df35 to your computer and use it in GitHub Desktop.
4x4 Bayer dithering
local RADIUS = 88
local GRAV_REACH = math.sqrt(usagi.GAME_H * usagi.GAME_H + usagi.GAME_W * usagi.GAME_W)
local G_MAX = 0.05
local GRAV_BRACKET = (GRAV_REACH - RADIUS)
- ((GRAV_REACH * GRAV_REACH) - (RADIUS * RADIUS)) / (2 * GRAV_REACH)
local DAMAGE_FACTOR = 0.0006
local SUN_DIR = { x = 1, y = 0 }
local MAX_INTEGRITY = 100
local BAYER = {
{ 0, 8, 2, 10 },
{ 12, 4, 14, 6 },
{ 3, 11, 1, 9 },
{ 15, 7, 13, 5 },
}
local DITHER_W = 9
local function lit_dithered_at(px, py, ax, ay)
local s = ((px + 0.5) * SUN_DIR.x) + ((py + 0.5) * SUN_DIR.y)
local f = util.clamp((s + DITHER_W) / (2 * DITHER_W), 0, 1) -- 0 (dark) .. 1 (lit)
local th = (BAYER[(ay % 4) + 1][(ax % 4) + 1] + 0.5) / 16 -- 0..1 threshold from pattern
return f > th
end
local Tsuki = {}
local function die(tsuki)
tsuki.dead = true
tsuki.active = false
end
local function attract_entities(tsuki, entities, sign)
for _, e in ipairs(entities) do
local dx, dy = tsuki.cx - e.cx, tsuki.cy - e.cy
local d = math.sqrt(dx * dx + dy * dy)
if d > RADIUS and d <= tsuki.beam_reach
and not e.latched_to then
local strength = tsuki.strength * math.max(0, 1 - (d / tsuki.beam_reach))
e.force.x += (dx / d) * -sign * strength
e.force.y += (dy / d) * -sign * strength
end
end
end
function Tsuki.is_lit(t, px, py)
return ((px - t.cx) * SUN_DIR.x + (py - t.cy) * SUN_DIR.y) > 0
end
function Tsuki.grav_ceiling(accel)
return math.sqrt(2 * G_MAX * accel * GRAV_BRACKET)
end
function Tsuki.spawn()
local t = {
x = usagi.GAME_W / 2,
y = usagi.GAME_H / 2,
w = RADIUS * 2,
h = RADIUS * 2,
vel = { x = 0, y = 0 },
mass = 1e9,
beam_reach = GRAV_REACH,
strength = G_MAX,
static = true,
max_integrity = MAX_INTEGRITY,
integrity = MAX_INTEGRITY,
dead = false,
active = true,
}
local r = RADIUS
local cx, cy = t.x, t.y
t.color_mask = {}
for py = -r + 1, r - 1 do
for px = -r + 1, r - 1 do
if px * px + py * py <= r * r then
local ax, ay = cx + px, cy + py
t.color_mask[#t.color_mask + 1] = {
px, py, lit_dithered_at(px, py, ax, ay),
}
end
end
end
return t
end
function Tsuki.affect(t, entities)
if not t.active then return end
t.cx, t.cy = t.x, t.y
attract_entities(t, entities, -1)
end
function Tsuki.try_contact(a, b, E)
local t, e
if a.static then
t, e = a, b
elseif b.static then
t, e = b, a
else
return false
end
if not e.enemy then return true end
local hard = E > Collision.IMPACT_THRESHOLD
if Tsuki.is_lit(t, e.cx, e.cy) then
if hard then
Enemy.die(e)
Tsuki.damage(t, DAMAGE_FACTOR * (E - Collision.IMPACT_THRESHOLD))
else
Enemy.latch(e, t)
end
elseif hard then
Enemy.die(e)
end
return true
end
function Tsuki.damage(t, amount)
t.integrity = math.max(0, t.integrity - amount)
if t.integrity <= 0 then
die(t)
end
end
function Tsuki.draw(t)
if not t.active then return end
for _, m in ipairs(t.color_mask) do
local cx, cy = t.cx, t.cy
local px, py = cx + m[1], cy + m[2]
local lit = Colors.YELLOW
local dark = Colors.SHADOW
gfx.px(px, py, m[3] and lit or dark)
end
end
return Tsuki
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment