Last active
July 7, 2026 04:23
-
-
Save adamico/f36ebae18dc8d994d5cbf6431ec8df35 to your computer and use it in GitHub Desktop.
4x4 Bayer dithering
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
| 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