This commit is contained in:
2026-05-05 20:38:40 -04:00
parent 76ee7306bb
commit fd4096b98a
2 changed files with 158 additions and 145 deletions

View File

@@ -68,10 +68,10 @@ local function buildSegments()
end
-- Spin physics
local OMEGA_MIN = 4.0 -- rad/s
local OMEGA_MAX = 10.0
local FRICTION = 0.987 -- multiplier per frame
local STOP_OMEGA = 0.04 -- rad/s
local OMEGA_MIN = 8.0 -- rad/s fast launch
local OMEGA_MAX = 16.0
local FRICTION = 0.980 -- per frame — stops cleanly in ~4s
local STOP_OMEGA = 0.10 -- rad/s clean stop threshold, no wiggle
-- Colours
local COL_BG = 0x050505
@@ -136,85 +136,110 @@ local function px_text_centre(str, y, fg, bg, size)
end
----------------------------------------------------------------------
-- Wheel drawing
-- Wheel drawing — single-pass rasteriser
--
-- Instead of drawing each wedge separately (N passes over the full
-- bounding box), we do ONE pass: for every pixel inside the wheel
-- annulus, compute its angle and look up the wedge colour.
-- This is N× faster and eliminates the per-wedge sleep(0) during spin.
----------------------------------------------------------------------
local segments = {}
local function drawWedge(seg, wheelAngle, glowing)
local col = seg.col
if glowing then
local r = math.min(255, math.floor(col / 0x10000) + 80)
local g = math.min(255, math.floor((col % 0x10000) / 0x100) + 80)
local b = math.min(255, col % 0x100 + 80)
col = r * 0x10000 + g * 0x100 + b
-- Build a flat lookup: given a wheel-local angle in [0, TWO_PI),
-- return the segment index. Linear scan over 12 segments is fine.
local function segmentForAngle(a)
for i, seg in ipairs(segments) do
if a >= seg.startA and a < seg.endA then return i end
end
local ri = R_POCKET_IN
local ro = R_OUTER
local a0 = seg.startA + wheelAngle
local arc = seg.endA - seg.startA
local bx0 = math.floor(CX - ro) - 1
local bx1 = math.ceil (CX + ro) + 1
local by0 = math.floor(CY - ro) - 1
local by1 = math.ceil (CY + ro) + 1
for sy = by0, by1 do
local dy = sy - CY
local runStart = nil
for sx = bx0, bx1 do
local dx = sx - CX
local dist = math.sqrt(dx*dx + dy*dy)
if dist >= ri and dist <= ro then
local rel = (math.atan2(dy, dx) - a0) % TWO_PI
if rel <= arc then
if not runStart then runStart = sx end
else
if runStart then
px_rect(runStart, sy, sx - runStart, 1, col)
runStart = nil
end
end
else
if runStart then
px_rect(runStart, sy, sx - runStart, 1, col)
runStart = nil
end
end
end
if runStart then
px_rect(runStart, sy, bx1 - runStart + 1, 1, col)
end
end
-- Separator spoke at startA
local a0w = seg.startA + wheelAngle
for i = 0, math.floor(ro - ri) do
local r = ri + i
local sx = CX + math.cos(a0w) * r
local sy = CY + math.sin(a0w) * r
px_rect(math.floor(sx), math.floor(sy), 2, 1, COL_SEP)
end
-- Label at 72% radius, wedge midpoint
local midA = seg.midA + wheelAngle
local lr = (ri + ro) * 0.72
local lx = CX + math.cos(midA) * lr
local ly = CY + math.sin(midA) * lr
px_text(seg.name, lx - math.floor(#seg.name * 3), ly - 4, COL_WHITE, col, 1)
return #segments -- wrap-around safety
end
local function drawAllWedges(wheelAngle, glowIdx)
-- Draw the full wheel in one raster pass.
-- wheelAngle: current rotation offset (added to every wedge startA/endA).
-- glowIdx: if non-nil, that segment is brightened.
local function drawWheel(wheelAngle, glowIdx)
-- Precompute brightened colours so we don't recompute inside the loop
local cols = {}
for i, seg in ipairs(segments) do
drawWedge(seg, wheelAngle, i == glowIdx)
sleep(0)
if i == glowIdx then
local c = seg.col
local r = math.min(255, math.floor(c / 0x10000) + 80)
local g = math.min(255, math.floor((c % 0x10000) / 0x100) + 80)
local b = math.min(255, c % 0x100 + 80)
cols[i] = r * 0x10000 + g * 0x100 + b
else
cols[i] = seg.col
end
end
local ri = R_POCKET_IN
local ro = R_OUTER
local ri2 = ri * ri
local ro2 = ro * ro
-- Normalise wheelAngle so angles stay in a sane range
local wa = wheelAngle % TWO_PI
for sy = math.floor(CY - ro) - 1, math.ceil(CY + ro) + 1 do
local dy = sy - CY
local dy2 = dy * dy
if dy2 <= ro2 then
local xhalf = math.floor(math.sqrt(ro2 - dy2) + 0.5)
local runStart = nil
local runCol = nil
for sx = CX - xhalf, CX + xhalf do
local dx = sx - CX
local dx2 = dx * dx
local d2 = dx2 + dy2
local col = nil
if d2 >= ri2 and d2 <= ro2 then
-- Inside annulus — find wedge
-- atan2 returns angle in world space; subtract wheelAngle
-- to get wheel-local angle, then mod into [0, TWO_PI)
local worldA = math.atan2(dy, dx)
local localA = (worldA - wa) % TWO_PI
local idx = segmentForAngle(localA)
col = cols[idx]
end
if col == runCol then
-- extend run (including col==nil for outside-annulus gaps)
else
if runCol and runStart then
px_rect(runStart, sy, sx - runStart, 1, runCol)
end
runStart = sx
runCol = col
end
end
-- flush last run
if runCol and runStart then
local xend = CX + xhalf + 1
px_rect(runStart, sy, xend - runStart, 1, runCol)
end
end
end
-- Separator spokes — draw after fill so they appear on top
for i, seg in ipairs(segments) do
local a = seg.startA + wa
for step = 0, math.floor(R_OUTER - R_POCKET_IN) do
local rr = R_POCKET_IN + step
px_rect(math.floor(CX + math.cos(a) * rr),
math.floor(CY + math.sin(a) * rr), 2, 1, COL_SEP)
end
-- Label
local midA = seg.midA + wa
local lr = (R_POCKET_IN + R_OUTER) * 0.68
local lx = CX + math.cos(midA) * lr
local ly = CY + math.sin(midA) * lr
px_text(seg.name, lx - math.floor(#seg.name * 3), ly - 4,
COL_WHITE, cols[i], 1)
end
end
local function drawChrome()
px_annulus(CX, CY, R_OUTER, R_OUTER + 7, COL_RIM)
-- Hub
px_circle(CX, CY, R_POCKET_IN, COL_HUB)
px_annulus(CX, CY, R_POCKET_IN - 4, R_POCKET_IN, COL_HUB_RING)
px_circle(CX, CY, 6, COL_HUB_RING)
@@ -255,7 +280,7 @@ end
local function drawWheelFull(wheelAngle, glowIdx)
px_circle(CX, CY, R_OUTER + 9, COL_BG)
drawAllWedges(wheelAngle, glowIdx)
drawWheel(wheelAngle, glowIdx)
drawChrome()
drawPointer()
end
@@ -281,38 +306,39 @@ end
local function spin()
local omega = OMEGA_MIN + math.random() * (OMEGA_MAX - OMEGA_MIN)
local angle = math.random() * TWO_PI
local elapsed = 0
-- Initial draw
drawWheelFull(angle, nil)
drawHubText({ "SPINNING..." })
while elapsed < 30.0 do
local frameCount = 0
while true do
omega = omega * FRICTION
angle = angle + omega * FRAME_DELAY
if angle > TWO_PI * 100 then angle = angle % TWO_PI end
px_circle(CX, CY, R_OUTER + 9, COL_BG)
drawAllWedges(angle, nil)
-- drawWheel overwrites every annulus pixel — no need to clear first
drawWheel(angle, nil)
drawChrome()
drawPointer()
gpu.sync()
sleep(FRAME_DELAY)
elapsed = elapsed + FRAME_DELAY
-- Yield to OS every 4 frames to avoid CC timeout without a sleep(0) overhead
frameCount = frameCount + 1
if frameCount % 4 == 0 then sleep(0) end
if omega < STOP_OMEGA then break end
end
local winIdx = segmentAtPointer(angle)
-- Flash winning wedge
for flash = 1, 7 do
px_circle(CX, CY, R_OUTER + 9, COL_BG)
drawAllWedges(angle, flash % 2 == 1 and winIdx or nil)
-- Flash winning wedge — 6 flashes at 120ms each
for flash = 1, 6 do
drawWheel(angle, flash % 2 == 1 and winIdx or nil)
drawChrome()
drawPointer()
gpu.sync()
sleep(0.14)
sleep(0.12)
end
return winIdx, angle