Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local BMP_URL = "https://files.catbox.moe/52gj0y.bmp"
- local BMP_FILE = "demo.bmp"
- local PCM_URL = "https://files.catbox.moe/ae17k1.raw"
- local PCM_FILE = "demo.raw"
- local PCM_SAMPLE_RATE = 32000
- local PCM_UNSIGNED = true
- local DEMO_TIME = 8
- local function readBMP(filename)
- local file = fs.open(filename, "rb")
- if not file then return nil, "Failed to open file" end
- local header = file.read(54)
- local width = string.byte(header, 19) + string.byte(header, 20) * 256
- local height = string.byte(header, 23) + string.byte(header, 24) * 256
- local pixels = {}
- for y = height, 1, -1 do
- pixels[y] = {}
- for x = 1, width do
- local b = file.read(1)
- local g = file.read(1)
- local r = file.read(1)
- pixels[y][x] = {r = string.byte(r), g = string.byte(g), b = string.byte(b)}
- end
- if y % 10 == 0 then os.queueEvent("yield") os.pullEvent("yield") end
- end
- file.close()
- return {width = width, height = height, pixels = pixels}
- end
- local function mapColor256(r, g, b)
- local cubeSize = 6
- local rIndex = math.floor(r / (255 / (cubeSize - 1)))
- local gIndex = math.floor(g / (255 / (cubeSize - 1)))
- local bIndex = math.floor(b / (255 / (cubeSize - 1)))
- return 16 + rIndex * cubeSize * cubeSize + gIndex * cubeSize + bIndex
- end
- local function set256Palette()
- local cubeSize = 6
- for i = 16, 231 do
- local index = i - 16
- local rIndex = math.floor(index / (cubeSize * cubeSize))
- local gIndex = math.floor((index % (cubeSize * cubeSize)) / cubeSize)
- local bIndex = index % cubeSize
- local r = math.floor(rIndex * 255 / (cubeSize - 1))
- local g = math.floor(gIndex * 255 / (cubeSize - 1))
- local b = math.floor(bIndex * 255 / (cubeSize - 1))
- term.setPaletteColor(i, r / 255, g / 255, b / 255)
- end
- for i = 232, 255 do
- local gray = math.floor((i - 232) * 255 / 23)
- term.setPaletteColor(i, gray / 255, gray / 255, gray / 255)
- end
- end
- local function savePalette()
- local t = {}
- for i = 0, 255 do t[i] = {term.getPaletteColor(i)} end
- return t
- end
- local function restorePalette(t)
- for i = 0, 255 do term.setPaletteColor(i, table.unpack(t[i])) end
- end
- local function makeBuffer(width, height)
- local buf = {}
- for y = 0, height-1 do
- buf[y] = {}
- for x = 0, width-1 do buf[y][x] = 0 end
- end
- return buf
- end
- local function swapBuffers(a, b) return b, a end
- local function blitBuffer(buf, output)
- for y = 0, #buf do
- for x = 0, #(buf[y]) do
- output.setPixel(x, y, buf[y][x])
- end
- end
- end
- local function downloadContent(url, filename, contentType)
- print("Downloading " .. contentType .. "...")
- local response = http.get(url, nil, true)
- if not response then error("Failed to download " .. contentType .. ": " .. url) end
- local file = fs.open(filename, "wb")
- local content = response.readAll()
- for i = 1, #content, 8192 do
- file.write(content:sub(i, i + 8191))
- os.queueEvent("yield")
- os.pullEvent("yield")
- end
- file.close()
- response.close()
- print("Downloaded " .. contentType .. ".")
- end
- local function playPCM(filename, inputSampleRate, isUnsigned, volume)
- local speaker = peripheral.find("speaker")
- if not speaker then print("No speaker attached!") return end
- local file = fs.open(filename, "rb")
- if not file then print("Could not open file: " .. filename) return end
- local samples = {}
- local sum = 0
- local count = 0
- while true do
- local byte = file.read()
- if not byte then break end
- if isUnsigned then byte = byte - 128 end
- samples[#samples + 1] = byte
- sum = sum + byte
- count = count + 1
- end
- file.close()
- if #samples == 0 then print("File is empty or unreadable.") return end
- local avg = sum / count
- for i = 1, #samples do samples[i] = samples[i] - avg end
- local outputSampleRate = 48000
- if inputSampleRate ~= outputSampleRate then
- local resampled = {}
- local ratio = inputSampleRate / outputSampleRate
- for i = 1, math.floor(#samples / ratio) do
- local src = (i - 1) * ratio + 1
- local idx = math.floor(src)
- local frac = src - idx
- local s1 = samples[idx] or 0
- local s2 = samples[idx + 1] or s1
- resampled[i] = s1 + (s2 - s1) * frac
- end
- samples = resampled
- end
- local chunkSize = 128 * 1024
- local totalSamples = #samples
- local playedSamples = 0
- while playedSamples < totalSamples do
- local chunk = {}
- for i = 1, chunkSize do
- local idx = playedSamples + i
- if idx > totalSamples then break end
- local sample = math.floor(samples[idx] * volume + 0.5)
- if sample > 127 then sample = 127 end
- if sample < -128 then sample = -128 end
- chunk[i] = sample
- end
- if #chunk == 0 then break end
- while not speaker.playAudio(chunk) do
- os.pullEvent("speaker_audio_empty")
- end
- playedSamples = playedSamples + #chunk
- end
- end
- local function waterdropTransition(image, output, duration)
- local screenWidth, screenHeight = output.getSize()
- local pxw, pxh = screenWidth * 6, screenHeight * 9
- local imageWidth, imageHeight = image.width, image.height
- local bufA = makeBuffer(pxw, pxh)
- local bufB = makeBuffer(pxw, pxh)
- local front, back = bufA, bufB
- local cx, cy = pxw/2, pxh/2
- local t0 = os.epoch("utc")
- local tEnd = t0 + duration*1000
- while os.epoch("utc") < tEnd do
- local t = (os.epoch("utc")-t0)/1000
- for y = 0, pxh-1 do
- for x = 0, pxw-1 do
- local dx, dy = x-cx, y-cy
- local dist = math.sqrt(dx*dx+dy*dy)
- local ripple = 8*math.sin(dist/16-t*8)/(dist/16+1)
- local sx = math.floor((x + ripple) * image.width / pxw) + 1
- local sy = math.floor((y + ripple) * image.height / pxh) + 1
- if sx >= 1 and sx <= image.width and sy >= 1 and sy <= image.height then
- local pixel = image.pixels[sy][sx]
- back[y][x] = mapColor256(pixel.r, pixel.g, pixel.b)
- else
- back[y][x] = 0
- end
- end
- end
- front, back = swapBuffers(front, back)
- blitBuffer(front, output)
- os.queueEvent("yield")
- os.pullEvent("yield")
- end
- end
- local function mode7_spinzoom()
- return function(frame, sw, sh, iw, ih)
- local t = frame / 60
- local scale = 0.5 + 0.5 * math.sin(t)
- local angle = t
- local a = math.cos(angle) * scale
- local b = -math.sin(angle) * scale
- local c = math.sin(angle) * scale
- local d = math.cos(angle) * scale
- return {a=a, b=b, c=c, d=d, x0=(sw*6)/2, y0=(sh*9)/2, u0=iw/2, v0=ih/2}
- end
- end
- local function mode7_wave()
- return function(frame, sw, sh, iw, ih)
- local scale = 0.7
- local angle = 0
- return {
- a=math.cos(angle)*scale, b=-math.sin(angle)*scale,
- c=math.sin(angle)*scale, d=math.cos(angle)*scale,
- x0=(sw*6)/2, y0=(sh*9)/2, u0=iw/2, v0=ih/2,
- scanline=function(sy,a,b,c,d,frame)
- local t = frame / 30
- local wave = 0.2 * math.sin((sy / 30) + t)
- local sa = a + wave
- local sd = d + wave
- return sa, b, c, sd
- end
- }
- end
- end
- local function mode7_barrel()
- return function(frame, sw, sh, iw, ih)
- local scale = 0.7
- local t = frame / 40
- return {
- a=math.cos(t)*scale, b=-math.sin(t)*scale,
- c=math.sin(t)*scale, d=math.cos(t)*scale,
- x0=(sw*6)/2, y0=(sh*9)/2, u0=iw/2, v0=ih/2,
- scanline=function(sy,a,b,c,d,frame)
- local t2 = frame / 15
- local roll = 0.3 * math.sin(t2 + sy / 40)
- local sa = a * math.cos(roll) - b * math.sin(roll)
- local sb = a * math.sin(roll) + b * math.cos(roll)
- local sc = c * math.cos(roll) - d * math.sin(roll)
- local sd = c * math.sin(roll) + d * math.cos(roll)
- return sa, sb, sc, sd
- end
- }
- end
- end
- local function mode7_fzero()
- return function(frame, sw, sh, iw, ih)
- local t = frame / 40
- local scale = 0.3 + 0.7 * (1 - (frame % 120) / 120)
- local angle = 0.15 * math.sin(t)
- return {
- a=math.cos(angle)*scale, b=-math.sin(angle)*scale,
- c=math.sin(angle)*scale, d=math.cos(angle)*scale,
- x0=(sw*6)/2, y0=(sh*9)*0.75, u0=iw/2, v0=ih*0.7,
- }
- end
- end
- local function mode7_floor()
- return function(frame, sw, sh, iw, ih)
- local cam_x = iw/2
- local cam_y = ih/2
- local cam_angle = frame/60
- local cam_height = 120
- local horizon = math.floor((sh*9) * 0.45)
- local focal_length = 170
- return {
- a=1, b=0, c=0, d=1, x0=0, y0=0, u0=0, v0=0,
- scanline=function(sy,a,b,c,d,frame) return a,b,c,d end,
- floor=function(sx,sy)
- if sy < horizon then return nil,nil end
- local pxw = sw*6
- local dx = sx - pxw/2
- local dy = sy - horizon
- local angle = cam_angle
- local distance = cam_height * focal_length / (dy+1)
- local world_x = cam_x + math.cos(angle)*distance + math.sin(angle)*dx*distance/focal_length
- local world_y = cam_y + math.sin(angle)*distance - math.cos(angle)*dx*distance/focal_length
- local tx = ((math.floor(world_x)-1) % iw) + 1
- local ty = ((math.floor(world_y)-1) % ih) + 1
- return tx, ty
- end,
- horizon = horizon
- }
- end
- end
- local function mode7_zoom()
- return function(frame, sw, sh, iw, ih)
- local scale = 0.5 + math.sin(frame / 100) * 0.5
- return {
- a = scale, b = 0, c = 0, d = scale,
- x0 = (sw * 6) / 2, y0 = (sh * 9) / 2,
- u0 = iw / 2, v0 = ih / 2
- }
- end
- end
- local function mode7_pan()
- return function(frame, sw, sh, iw, ih)
- local panX = math.sin(frame / 120) * iw / 4
- local panY = math.cos(frame / 150) * ih / 4
- return {
- a = 1, b = 0, c = 0, d = 1,
- x0 = (sw * 6) / 2, y0 = (sh * 9) / 2,
- u0 = iw / 2 + panX, v0 = ih / 2 + panY
- }
- end
- end
- local function mode7_rotate()
- return function(frame, sw, sh, iw, ih)
- local angle = frame / 100
- local a = math.cos(angle)
- local b = -math.sin(angle)
- local c = math.sin(angle)
- local d = math.cos(angle)
- return {
- a = a, b = b, c = c, d = d,
- x0 = (sw * 6) / 2, y0 = (sh * 9) / 2,
- u0 = iw / 2, v0 = ih / 2
- }
- end
- end
- local function mode7_waterreflection()
- return function(frame, sw, sh, iw, ih)
- local cam_x = iw/2
- local cam_y = ih/2
- local cam_angle = frame/60
- local cam_height = 120
- local horizon = math.floor((sh*9) * 0.45)
- local focal_length = 170
- return {
- a=1, b=0, c=0, d=1, x0=0, y0=0, u0=0, v0=0,
- floor=function(sx,sy)
- local pxw = sw*6
- if sy >= horizon then
- local dx = sx - pxw/2
- local dy = sy - horizon
- local angle = cam_angle
- local distance = cam_height * focal_length / (dy+1)
- local world_x = cam_x + math.cos(angle)*distance + math.sin(angle)*dx*distance/focal_length
- local world_y = cam_y + math.sin(angle)*distance - math.cos(angle)*dx*distance/focal_length
- local tx = ((math.floor(world_x)-1) % iw) + 1
- local ty = ((math.floor(world_y)-1) % ih) + 1
- return tx, ty
- else
- local dx = sx - pxw/2
- local dy = horizon - sy
- local angle = cam_angle
- local distance = cam_height * focal_length / (dy+1)
- local world_x = cam_x + math.cos(angle)*distance + math.sin(angle)*dx*distance/focal_length
- local world_y = cam_y + math.sin(angle)*distance - math.cos(angle)*dx*distance/focal_length
- local tx = ((math.floor(world_x)-1) % iw) + 1
- local ty = ((math.floor(world_y)-1) % ih) + 1
- local ripple = math.sin(frame/10 + dx/20) * 4
- ty = math.max(1, math.min(ih, math.floor(ty + ripple)))
- return tx, ty
- end
- end,
- horizon = horizon
- }
- end
- end
- local function mode7_tunnel()
- return function(frame, sw, sh, iw, ih)
- return {
- a=1, b=0, c=0, d=1, x0=0, y0=0, u0=0, v0=0,
- floor=function(sx,sy)
- local pxw, pxh = sw*6, sh*9
- local cx, cy = pxw/2, pxh/2
- local dx = sx - cx
- local dy = sy - cy
- local angle = math.atan2(dy, dx) + frame/60
- local radius = math.sqrt(dx*dx+dy*dy)
- local u = ((angle/(2*math.pi)) % 1) * iw
- local v = ((frame*4 + radius*2) % ih) + 1
- return math.floor(u)+1, math.floor(v)
- end,
- horizon = 0
- }
- end
- end
- local function mode7_plasma()
- return function(frame, sw, sh, iw, ih)
- return {
- a=1, b=0, c=0, d=1, x0=0, y0=0, u0=0, v0=0,
- floor=function(sx,sy)
- local pxw, pxh = sw*6, sh*9
- local u = sx / pxw * iw
- local v = sy / pxh * ih
- local t = frame/20
- local offset = math.sin(v/8 + t)*8 + math.cos(u/16 + t)*8
- u = u + offset
- v = v + offset
- return ((math.floor(u)-1)%iw)+1, ((math.floor(v)-1)%ih)+1
- end,
- horizon = 0
- }
- end
- end
- local function mode7_ripple()
- return function(frame, sw, sh, iw, ih)
- local cx, cy = (sw*6)/2, (sh*9)/2
- return {
- a=1, b=0, c=0, d=1, x0=0, y0=0, u0=0, v0=0,
- floor=function(sx,sy)
- local dx, dy = sx-cx, sy-cy
- local dist = math.sqrt(dx*dx+dy*dy)
- local ripple = math.sin(dist/8-frame/4)*8
- local u = ((sx + ripple) / (sw*6)) * iw
- local v = ((sy + ripple) / (sh*9)) * ih
- return math.floor(u)+1, math.floor(v)+1
- end,
- horizon = 0
- }
- end
- end
- local function mode7_checkerboard()
- return function(frame, sw, sh, iw, ih)
- return {
- a=1, b=0, c=0, d=1, x0=0, y0=0, u0=0, v0=0,
- floor=function(sx,sy)
- local pxw, pxh = sw*6, sh*9
- local u = sx / pxw * 16
- local v = sy / pxh * 16
- local scale = 1 + math.sin(frame/30) * 0.5
- u = u * scale
- v = v * scale
- local check = ((math.floor(u) + math.floor(v)) % 2 == 0)
- if check then
- return 1, 1
- else
- return iw, ih
- end
- end,
- horizon = 0
- }
- end
- end
- local function mode7_infinitezoom()
- return function(frame, sw, sh, iw, ih)
- local zoom = 1 + math.sin(frame/40)*0.8
- return {
- a=zoom, b=0, c=0, d=zoom,
- x0=(sw*6)/2, y0=(sh*9)/2, u0=iw/2, v0=ih/2
- }
- end
- end
- local function mode7_parallax()
- return function(frame, sw, sh, iw, ih)
- return {
- a=1, b=0, c=0, d=1, x0=0, y0=0, u0=0, v0=0,
- multilayer = true,
- floor=function(sx,sy,layer)
- local pxw, pxh = sw*6, sh*9
- local u = sx / pxw * iw
- local v = sy / pxh * ih
- if layer == 1 then
- u = u + math.sin(frame/30)*10
- else
- v = v + math.cos(frame/40)*10
- end
- return ((math.floor(u)-1)%iw)+1, ((math.floor(v)-1)%ih)+1
- end,
- horizon = 0
- }
- end
- end
- local function mode7_road()
- return function(frame, sw, sh, iw, ih)
- local cam_x = iw/2
- local cam_y = ih/2
- local cam_angle = 0.1*math.sin(frame/50)
- local cam_height = 120
- local horizon = math.floor((sh*9) * 0.45)
- local focal_length = 170
- return {
- a=1, b=0, c=0, d=1, x0=0, y0=0, u0=0, v0=0,
- floor=function(sx,sy)
- if sy < horizon then return nil,nil end
- local pxw = sw*6
- local dx = sx - pxw/2
- local dy = sy - horizon
- local curve = math.sin(dy/40 + frame/30)*40
- local angle = cam_angle + curve/1000
- local distance = cam_height * focal_length / (dy+1)
- local world_x = cam_x + math.cos(angle)*distance + math.sin(angle)*dx*distance/focal_length
- local world_y = cam_y + math.sin(angle)*distance - math.cos(angle)*dx*distance/focal_length
- local tx = ((math.floor(world_x)-1) % iw) + 1
- local ty = ((math.floor(world_y)-1) % ih) + 1
- return tx, ty
- end,
- horizon = horizon
- }
- end
- end
- local function mode7_starfield()
- local stars = {}
- for i=1,128 do
- stars[i] = {x=math.random(),y=math.random(),z=math.random()}
- end
- return function(frame, sw, sh, iw, ih)
- local base = mode7_floor()(frame, sw, sh, iw, ih)
- base.starfield = function(buf)
- local pxw, pxh = sw*6, sh*9
- for _,star in ipairs(stars) do
- star.z = star.z - 0.01
- if star.z <= 0 then
- star.x = math.random()
- star.y = math.random()
- star.z = 1
- end
- local sx = math.floor((star.x-0.5)*pxw/star.z + pxw/2)
- local sy = math.floor((star.y-0.5)*pxh/star.z + pxh/2)
- if sx >= 0 and sx < pxw and sy >= 0 and sy < pxh then
- buf[sy][sx] = 231
- end
- end
- end
- return base
- end
- end
- local DEMOS = {
- {name="SpinZoom", param=mode7_spinzoom(), animate=true},
- {name="Wave", param=mode7_wave(), animate=true},
- {name="Barrel", param=mode7_barrel(), animate=true},
- {name="FZero", param=mode7_fzero(), animate=true},
- {name="Floor", param=mode7_floor(), animate=true},
- {name="Zoom In/Out", param=mode7_zoom(), animate=true},
- {name="Panning", param=mode7_pan(), animate=true},
- {name="Rotating", param=mode7_rotate(), animate=true},
- {name="WaterReflection", param=mode7_waterreflection(), animate=true},
- {name="Tunnel", param=mode7_tunnel(), animate=true},
- {name="Plasma", param=mode7_plasma(), animate=true},
- {name="Rippled Pool", param=mode7_ripple(), animate=true},
- {name="Checkerboard", param=mode7_checkerboard(), animate=true},
- {name="Infinite Zoom", param=mode7_infinitezoom(), animate=true},
- {name="Parallax", param=mode7_parallax(), animate=true},
- {name="Curved Road", param=mode7_road(), animate=true},
- {name="Starfield", param=mode7_starfield(), animate=true},
- }
- local function displayBMPMode7Affine(image, output, paramFunc, animate, duration)
- local screenWidth, screenHeight = output.getSize()
- local pxw, pxh = screenWidth * 6, screenHeight * 9
- local imageWidth, imageHeight = image.width, image.height
- local originalPalette = savePalette()
- local originalGraphicsMode = term.getGraphicsMode()
- local originalBackgroundColor = term.getBackgroundColor()
- local originalTextColor = term.getTextColor()
- term.setGraphicsMode(2)
- set256Palette()
- local bufA = makeBuffer(pxw, pxh)
- local bufB = makeBuffer(pxw, pxh)
- local front, back = bufA, bufB
- local numBands = 4
- local bands = {}
- local bandHeight = math.floor(pxh / numBands)
- for i = 1, numBands do
- local y0 = (i-1)*bandHeight
- local y1 = (i==numBands) and (pxh-1) or (i*bandHeight-1)
- bands[i] = {y0=y0, y1=y1}
- end
- local frame = 0
- local t0 = os.epoch("utc")
- local tEnd = t0 + (duration or 8)*1000
- while os.epoch("utc") < tEnd do
- frame = frame + 1
- local params = paramFunc(frame, screenWidth, screenHeight, imageWidth, imageHeight)
- local a, b, c, d = params.a, params.b, params.c, params.d
- local x0, y0 = params.x0, params.y0
- local u0, v0 = params.u0, params.v0
- local floorFunc = params.floor
- local horizon = params.horizon or 0
- for bandIdx = 1, #bands do
- local band = bands[bandIdx]
- for sy = band.y0, band.y1 do
- for sx = 0, pxw-1 do
- local color = 0
- if floorFunc then
- local tx, ty
- if params.multilayer then
- tx, ty = floorFunc(sx, sy, 1)
- else
- tx, ty = floorFunc(sx, sy)
- end
- if tx and ty and tx >= 1 and tx <= imageWidth and ty >= 1 and ty <= imageHeight then
- local pixel = image.pixels[ty][tx]
- color = mapColor256(pixel.r, pixel.g, pixel.b)
- else
- color = 0
- end
- else
- local sa, sb, sc, sd = a, b, c, d
- if params.scanline then
- sa, sb, sc, sd = params.scanline(sy, a, b, c, d, frame)
- end
- local dx = sx - x0
- local dy = sy - y0
- local u = sa * dx + sb * dy + u0
- local v = sc * dx + sd * dy + v0
- local srcX = math.floor(u + 0.5)
- local srcY = math.floor(v + 0.5)
- if srcX >= 1 and srcX <= imageWidth and srcY >= 1 and srcY <= imageHeight then
- local pixel = image.pixels[srcY][srcX]
- color = mapColor256(pixel.r, pixel.g, pixel.b)
- else
- color = 0
- end
- end
- back[sy][sx] = color
- end
- if params.multilayer then
- local tx, ty = params.floor(band.y0, sy, 2)
- if tx and ty and tx >= 1 and tx <= imageWidth and ty >= 1 and ty <= imageHeight then
- for sx = 0, pxw-1 do
- local pixel = image.pixels[ty][tx]
- back[sy][sx] = math.max(back[sy][sx], mapColor256(pixel.r, pixel.g, pixel.b))
- end
- end
- end
- if sy % 10 == 0 then os.queueEvent("yield") os.pullEvent("yield") end
- end
- end
- if params.starfield then params.starfield(back) end
- front, back = swapBuffers(front, back)
- blitBuffer(front, output)
- os.queueEvent("yield")
- os.pullEvent("yield")
- end
- term.setGraphicsMode(originalGraphicsMode)
- restorePalette(originalPalette)
- term.setBackgroundColor(originalBackgroundColor)
- term.setTextColor(originalTextColor)
- term.clear()
- term.setCursorPos(1, 1)
- end
- local function runDemoSequence(image, output)
- while true do
- for i = 1, #DEMOS do
- local demo = DEMOS[i]
- print("Now running: " .. demo.name)
- displayBMPMode7Affine(image, output, demo.param, demo.animate, DEMO_TIME)
- if i < #DEMOS then
- waterdropTransition(image, output, 1.5)
- os.queueEvent("yield")
- os.pullEvent("yield")
- end
- end
- end
- end
- local output = term
- print("The Nono Demo!")
- print("by nonogamer9!")
- print("Wow! Mode 7 In A Minecraft Mod?")
- print("Downloading demo files if missing...")
- if not fs.exists(BMP_FILE) then downloadContent(BMP_URL, BMP_FILE, "demo image") end
- if not fs.exists(PCM_FILE) then downloadContent(PCM_URL, PCM_FILE, "PCM audio") end
- local image, err = readBMP(BMP_FILE)
- if not image then print("Error reading BMP file:", err) return end
- parallel.waitForAny(
- function() playPCM(PCM_FILE, PCM_SAMPLE_RATE, PCM_UNSIGNED, 0.2) end,
- function() runDemoSequence(image, output) end
- )
Add Comment
Please, Sign In to add comment