Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- if not fs.exists("3d") then fs.makeDir("3d") end
- local objList = {
- {n="Teapot",u="https://raw.githubusercontent.com/alecjacobson/common-3d-test-models/master/data/teapot.obj"},
- {n="Suzanne",u="https://raw.githubusercontent.com/alecjacobson/common-3d-test-models/master/data/suzanne.obj"},
- {n="Beetle",u="https://raw.githubusercontent.com/alecjacobson/common-3d-test-models/master/data/beetle.obj"}
- }
- local function centerText(text)
- local w, _ = term.getSize()
- local x = math.floor((w - #text) / 2)
- term.setCursorPos(x, term.getCursorPos())
- print(text)
- end
- local function showMenu()
- term.clear()
- centerText("=== nono's 3D OBJ viewer for ComputerCraft ===")
- print("\nSelect an .obj file:")
- for i,obj in ipairs(objList) do
- print(i..". "..obj.n)
- end
- print("Enter choice (1-"..#objList..") or 0 to exit:")
- local c = tonumber(read())
- if c == 0 then return nil end
- return c and c >= 1 and c <= #objList and objList[c] or nil
- end
- local function downloadObj(obj)
- centerText("Downloading "..obj.n.."...")
- local f = "3d/"..obj.n..".obj"
- if fs.exists(f) then
- print("File already exists. Use cached version? (y/n)")
- if read():lower() == "y" then return true end
- end
- local success = shell.run("wget", obj.u, f)
- return success
- end
- local function parseObj(f)
- local v,faces,vn = {},{},{}
- for l in io.lines(f) do
- local p = {}
- for w in l:gmatch("%S+") do table.insert(p,w) end
- if p[1]=="v" then
- table.insert(v,{x=tonumber(p[2]),y=tonumber(p[3]),z=tonumber(p[4])})
- elseif p[1]=="vn" then
- table.insert(vn,{x=tonumber(p[2]),y=tonumber(p[3]),z=tonumber(p[4])})
- elseif p[1]=="f" then
- local face = {}
- for i=2,#p do
- local indices = {}
- for index in p[i]:gmatch("(%d+)/?") do
- table.insert(indices, tonumber(index))
- end
- table.insert(face, indices)
- end
- table.insert(faces,face)
- end
- end
- if #faces == 0 then
- for l in io.lines(f) do
- local p = {}
- for w in l:gmatch("%S+") do table.insert(p,w) end
- if p[1]=="f" then
- local face = {}
- for i=2,#p do
- table.insert(face, {tonumber(p[i])})
- end
- table.insert(faces,face)
- end
- end
- end
- return v,faces,vn
- end
- local function rotateX(p,a) return {x=p.x,y=p.y*math.cos(a)-p.z*math.sin(a),z=p.y*math.sin(a)+p.z*math.cos(a)} end
- local function rotateY(p,a) return {x=p.x*math.cos(a)+p.z*math.sin(a),y=p.y,z=-p.x*math.sin(a)+p.z*math.cos(a)} end
- local function rotateZ(p,a) return {x=p.x*math.cos(a)-p.y*math.sin(a),y=p.x*math.sin(a)+p.y*math.cos(a),z=p.z} end
- local function translate(p,tx,ty,tz) return {x=p.x+tx, y=p.y+ty, z=p.z+tz} end
- local function project(p,w,h,zoom,offsetX,offsetY)
- local f = math.min(w, h) * zoom
- local x = (p.x*f)/(p.z+10)+w/2+offsetX
- local y = (p.y*f)/(p.z+10)+h/2+offsetY
- return math.floor(x),math.floor(y)
- end
- local function getAsciiChar(intensity)
- local chars = " .'`^\",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$"
- return chars:sub(math.floor(intensity * #chars) + 1, math.floor(intensity * #chars) + 1)
- end
- local function showControlGuide()
- term.clear()
- centerText("=== Control Guide ===")
- print("\nWASD: Rotate model")
- print("IJKL: Move model")
- print("UO: Move model forward/backward")
- print("Arrow Up/Down: Zoom in/out")
- print("X: Exit viewer")
- print("\nPress any key to continue...")
- os.pullEvent("key")
- end
- local function render(v,faces,vn)
- local w,h = term.getSize()
- local ax,ay,az,zoom = 0,0,0,0.5
- local tx,ty,tz = 0,0,0
- local running = true
- local buffer = {}
- local lastTime = os.clock()
- local frameCount = 0
- local fps = 0
- for y=1,h do
- buffer[y] = {}
- for x=1,w do
- buffer[y][x] = {char=" ", depth=math.huge}
- end
- end
- while running do
- local startTime = os.clock()
- for y=1,h do
- for x=1,w do
- buffer[y][x] = {char=" ", depth=math.huge}
- end
- end
- local p2d = {}
- for i,vert in ipairs(v) do
- local rotated = rotateZ(rotateY(rotateX(vert,ax),ay),az)
- local translated = translate(rotated,tx,ty,tz)
- p2d[i] = {project(translated,w,h,zoom,0,0)}
- end
- for _,face in ipairs(faces) do
- local v1,v2,v3 = v[face[1][1]], v[face[2][1]], v[face[3][1]]
- local normal = {
- x=(v2.y-v1.y)*(v3.z-v1.z)-(v2.z-v1.z)*(v3.y-v1.y),
- y=(v2.z-v1.z)*(v3.x-v1.x)-(v2.x-v1.x)*(v3.z-v1.z),
- z=(v2.x-v1.x)*(v3.y-v1.y)-(v2.y-v1.y)*(v3.x-v1.x)
- }
- local mag = math.sqrt(normal.x^2 + normal.y^2 + normal.z^2)
- if mag > 0 then
- normal.x, normal.y, normal.z = normal.x/mag, normal.y/mag, normal.z/mag
- else
- normal.x, normal.y, normal.z = 0, 0, 1
- end
- local intensity = math.max(0.1, normal.x*0.5 + normal.y*0.5 + normal.z*0.5)
- for i=1,#face do
- local x1,y1 = p2d[face[i][1]][1],p2d[face[i][1]][2]
- local x2,y2 = p2d[face[i%#face+1][1]][1],p2d[face[i%#face+1][1]][2]
- local depth = (v[face[i][1]].z + v[face[i%#face+1][1]].z) / 2
- local dx,dy = math.abs(x2-x1), math.abs(y2-y1)
- local sx,sy = x1 < x2 and 1 or -1, y1 < y2 and 1 or -1
- local err = dx-dy
- while true do
- if x1 > 0 and x1 <= w and y1 > 0 and y1 <= h then
- if depth < buffer[y1][x1].depth then
- buffer[y1][x1] = {char=getAsciiChar(intensity), depth=depth}
- end
- end
- if x1 == x2 and y1 == y2 then break end
- local e2 = 2*err
- if e2 > -dy then err = err - dy; x1 = x1 + sx end
- if e2 < dx then err = err + dx; y1 = y1 + sy end
- end
- end
- end
- term.clear()
- for y=1,h do
- for x=1,w do
- term.setCursorPos(x,y)
- term.write(buffer[y][x].char)
- end
- end
- frameCount = frameCount + 1
- if os.clock() - lastTime >= 1 then
- fps = frameCount
- frameCount = 0
- lastTime = os.clock()
- end
- term.setCursorPos(1,1)
- term.write("FPS: " .. fps .. " | Zoom: "..string.format("%.2f", zoom).." | Pos: "..string.format("%.2f", tx)..", "..string.format("%.2f", ty)..", "..string.format("%.2f", tz))
- term.setCursorPos(1,2)
- term.write("Rot: "..string.format("%.2f", ax)..", "..string.format("%.2f", ay)..", "..string.format("%.2f", az))
- term.setCursorPos(1,3)
- term.write("WASD: rotate, IJKL: move, UO: forward/back, Arrows: zoom, X: exit")
- local event, key = os.pullEvent("key")
- if key == keys.w then ax = ax + 0.1
- elseif key == keys.s then ax = ax - 0.1
- elseif key == keys.a then ay = ay - 0.1
- elseif key == keys.d then ay = ay + 0.1
- elseif key == keys.q then az = az + 0.1
- elseif key == keys.e then az = az - 0.1
- elseif key == keys.i then ty = ty + 0.1
- elseif key == keys.k then ty = ty - 0.1
- elseif key == keys.j then tx = tx - 0.1
- elseif key == keys.l then tx = tx + 0.1
- elseif key == keys.u then tz = tz + 0.1
- elseif key == keys.o then tz = tz - 0.1
- elseif key == keys.up then zoom = zoom * 1.1
- elseif key == keys.down then zoom = zoom / 1.1
- elseif key == keys.x then running = false
- end
- local endTime = os.clock()
- local frameTime = endTime - startTime
- if frameTime < 0.05 then
- os.sleep(0.05 - frameTime)
- end
- end
- end
- while true do
- showControlGuide()
- local obj = showMenu()
- if obj then
- if downloadObj(obj) then
- local path = "3d/"..obj.n..".obj"
- local v,f,vn = parseObj(path)
- if v and f then
- print("Rendering " .. obj.n .. " model...")
- os.sleep(1)
- render(v,f,vn)
- else
- print("Failed to parse .obj file.")
- end
- end
- else
- print("Exiting...")
- break
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement