Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local component, computer, event, math = require("component"), require("computer"), require("event"), require("math")
- local gpu, internet = component.gpu, require("internet")
- local sin, cos, floor, min, max, huge, sqrt, rad, abs = math.sin, math.cos, math.floor, math.min, math.max, math.huge, math.sqrt, math.rad, math.abs
- local byte, format = string.byte, string.format
- local W, H = gpu.getResolution()
- local buffer, vertices, faces = {}, {}, {}
- local sinT, cosT = {}, {}
- for i = 0, 360 do local r = rad(i) sinT[i], cosT[i] = sin(r), cos(r) end
- local function downloadOBJ()
- local c = ""
- for chunk in internet.request("https://raw.githubusercontent.com/kevinroast/phoria.js/master/teapot.obj") do c = c .. chunk end
- return c
- end
- local function parseOBJ(c)
- for l in c:gmatch("[^\r\n]+") do
- local p = l:sub(1, 2)
- if p == "v " then
- local x, y, z = l:match("v%s+([%d.-]+)%s+([%d.-]+)%s+([%d.-]+)")
- vertices[#vertices + 1] = {tonumber(x), tonumber(y), tonumber(z)}
- elseif p == "f " then
- local v1, v2, v3 = l:match("f%s+(%d+)/?%S*%s+(%d+)/?%S*%s+(%d+)/?%S*")
- if v1 then faces[#faces + 1] = {tonumber(v1), tonumber(v2), tonumber(v3)} end
- end
- end
- end
- local function calcBoundingBox()
- local minX, minY, minZ, maxX, maxY, maxZ = huge, huge, huge, -huge, -huge, -huge
- for _, v in ipairs(vertices) do
- minX, minY, minZ = min(minX, v[1]), min(minY, v[2]), min(minZ, v[3])
- maxX, maxY, maxZ = max(maxX, v[1]), max(maxY, v[2]), max(maxZ, v[3])
- end
- return minX, minY, minZ, maxX, maxY, maxZ
- end
- local function rotateY(x, y, z, a)
- local c, s = cosT[a], sinT[a]
- return x * c + z * s, y, -x * s + z * c
- end
- local function project(x, y, z, s, oX, oY)
- return floor(x * s + oX), floor(-y * s + oY)
- end
- local function drawLine(x1, y1, x2, y2, char)
- local dx, dy = abs(x2 - x1), abs(y2 - y1)
- local sx, sy = x1 < x2 and 1 or -1, y1 < y2 and 1 or -1
- local err, e2 = dx - dy, 0
- while true do
- buffer[y1 * W + x1] = char
- if x1 == x2 and y1 == y2 then break end
- e2 = 2 * err
- if e2 > -dy then err, x1 = err - dy, x1 + sx end
- if e2 < dx then err, y1 = err + dx, y1 + sy end
- end
- end
- local function clearBuffer() for i = 1, W * H do buffer[i] = ' ' end end
- local function renderBuffer()
- local i, s = 1, {}
- for y = 1, H do
- for x = 1, W do s[x] = buffer[i] or ' ' i = i + 1 end
- gpu.set(1, y, table.concat(s))
- end
- end
- local function normalize(x, y, z)
- local l = sqrt(x*x + y*y + z*z)
- return x/l, y/l, z/l
- end
- local function dot(x1, y1, z1, x2, y2, z2) return x1*x2 + y1*y2 + z1*z2 end
- local function detectGPUTier()
- local maxRes, mem = gpu.maxResolution(), gpu.totalMemory()
- if maxRes >= 160 and mem >= 786432 then return 3
- elseif maxRes >= 80 and mem >= 196608 then return 2
- else return 1 end
- end
- local function main()
- gpu.setBackground(0x000000)
- gpu.setForeground(0xFFFFFF)
- gpu.fill(1, 1, W, H, " ")
- print("Downloading teapot.obj...")
- local objContent = downloadOBJ()
- print("Parsing OBJ file...")
- parseOBJ(objContent)
- local gpuTier = detectGPUTier()
- local faceSkip, rotationSpeed = 4 - gpuTier, gpuTier
- local minX, minY, minZ, maxX, maxY, maxZ = calcBoundingBox()
- local cX, cY, cZ = (minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2
- for i, v in ipairs(vertices) do v[1], v[2], v[3] = v[1] - cX, v[2] - cY, v[3] - cZ end
- local modelSize = max(maxX - minX, maxY - minY, maxZ - minZ)
- local scale = min(W, H) * 0.8 / modelSize
- local oX, oY = W / 2, H / 2
- local angle, lastRender = 0, 0
- local frameCount = 0
- local lastFPSUpdate = computer.uptime()
- local fps = 0
- print("Rendering... Press 'x' to exit, '+' to zoom in, '-' to zoom out")
- os.sleep(1)
- local shadeChars = {[0]='·', ':', '.', '+', '%', '&', '#', '@'}
- local totalMemory = computer.totalMemory()
- while true do
- local currentTime = computer.uptime()
- frameCount = frameCount + 1
- if currentTime - lastFPSUpdate >= 1 then
- fps = frameCount / (currentTime - lastFPSUpdate)
- frameCount = 0
- lastFPSUpdate = currentTime
- end
- if currentTime - lastRender >= 0.05 then
- clearBuffer()
- local la = (angle * 2) % 360
- local lx, ly, lz = sin(rad(la)) * 2, 1, cos(rad(la)) * 2
- lx, ly, lz = normalize(lx, ly, lz)
- for i, face in ipairs(faces) do
- if i % faceSkip == 0 then
- local v1, v2, v3 = vertices[face[1]], vertices[face[2]], vertices[face[3]]
- local ux, uy, uz = v2[1] - v1[1], v2[2] - v1[2], v2[3] - v1[3]
- local vx, vy, vz = v3[1] - v1[1], v3[2] - v1[2], v3[3] - v1[3]
- local nx, ny, nz = uy*vz - uz*vy, uz*vx - ux*vz, ux*vy - uy*vx
- nx, ny, nz = normalize(nx, ny, nz)
- local shade = dot(nx, ny, nz, lx, ly, lz)
- local rx, ry, rz = 2 * shade * nx - lx, 2 * shade * ny - ly, 2 * shade * nz - lz
- local spec = max(0, dot(rx, ry, rz, 0, 0, 1)) ^ 8
- local char = shadeChars[floor(max(shade, spec) * 7)]
- for j = 1, 3 do
- local v1, v2 = vertices[face[j]], vertices[face[j % 3 + 1]]
- local x1, y1, z1 = rotateY(v1[1], v1[2], v1[3], angle)
- local x2, y2, z2 = rotateY(v2[1], v2[2], v2[3], angle)
- local px1, py1 = project(x1, y1, z1, scale, oX, oY)
- local px2, py2 = project(x2, y2, z2, scale, oX, oY)
- drawLine(px1, py1, px2, py2, char)
- end
- end
- end
- renderBuffer()
- local memUsage = (1 - computer.freeMemory() / totalMemory) * 100
- local statsText = format("FPS: %.1f | MEM: %.1f%% | Zoom: %.2f", fps, memUsage, scale)
- gpu.setForeground(0xFFFF00)
- gpu.set(1, 1, statsText)
- gpu.setForeground(0xFFFFFF)
- angle = (angle + rotationSpeed) % 360
- lastRender = currentTime
- end
- local eventType, _, char = event.pull(0.01)
- if eventType == "key_down" then
- if char == byte('x') or char == byte('X') then
- break
- elseif char == byte('+') then
- scale = scale * 1.1
- elseif char == byte('-') then
- scale = scale * 0.9
- end
- end
- end
- gpu.fill(1, 1, W, H, " ")
- print("Exiting...")
- end
- main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement