Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- function loadOBJ(filePath)
- local vertices = {}
- local faces = {}
- local file = fs.open(filePath, "r")
- if not file then
- error("File not found: " .. filePath)
- end
- while true do
- local line = file.readLine()
- if not line then break end
- local parts = {}
- for word in string.gmatch(line, "%S+") do
- table.insert(parts, word)
- end
- if parts[1] == "v" then
- local x = tonumber(parts[2])
- local y = tonumber(parts[3])
- local z = tonumber(parts[4])
- table.insert(vertices, {x, y, z})
- elseif parts[1] == "f" then
- local v1 = tonumber(string.match(parts[2], "%d+"))
- local v2 = tonumber(string.match(parts[3], "%d+"))
- local v3 = tonumber(string.match(parts[4], "%d+"))
- table.insert(faces, {v1, v2, v3})
- end
- end
- file.close()
- return vertices, faces
- end
- function normalizeVertices(vertices, maxSize)
- local minX, minY, minZ = math.huge, math.huge, math.huge
- local maxX, maxY, maxZ = -math.huge, -math.huge, -math.huge
- for _, vertex in ipairs(vertices) do
- local x, y, z = vertex[1], vertex[2], vertex[3]
- minX, maxX = math.min(minX, x), math.max(maxX, x)
- minY, maxY = math.min(minY, y), math.max(maxY, y)
- minZ, maxZ = math.min(minZ, z), math.max(maxZ, z)
- end
- local scale = maxSize / math.max(maxX - minX, maxY - minY, maxZ - minZ)
- local centerX, centerY, centerZ = (minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2
- local normalizedVertices = {}
- for _, vertex in ipairs(vertices) do
- local x = (vertex[1] - centerX) * scale
- local y = (vertex[2] - centerY) * scale
- local z = (vertex[3] - centerZ) * scale
- table.insert(normalizedVertices, {x, y, z})
- end
- return normalizedVertices
- end
- function rotatePoint(x, y, z, angleX, angleY, angleZ)
- local sinX, cosX = math.sin(angleX), math.cos(angleX)
- local sinY, cosY = math.sin(angleY), math.cos(angleY)
- local sinZ, cosZ = math.sin(angleZ), math.cos(angleZ)
- local y1 = y * cosX - z * sinX
- local z1 = y * sinX + z * cosX
- local x2 = x * cosY + z1 * sinY
- local z2 = -x * sinY + z1 * cosY
- local x3 = x2 * cosZ - y1 * sinZ
- local y3 = x2 * sinZ + y1 * cosZ
- return x3, y3, z2
- end
- function project(x, y, z, screenWidth, screenHeight, fov, viewerDistance)
- local factor = fov / (viewerDistance + z)
- local xProj = math.floor(screenWidth / 2 + x * factor)
- local yProj = math.floor(screenHeight / 2 - y * factor * 0.5)
- return xProj, yProj
- end
- function getASCIICharacter(z)
- local chars = " .:-=+*%@#"
- local index = math.floor((-z + 10) / 20 * (#chars - 1)) + 1
- if index < 1 then index = 1 end
- if index > #chars then index = #chars end
- return chars:sub(index, index)
- end
- local function createBuffer(width, height)
- local buffer = {}
- for y = 1, height do
- buffer[y] = {}
- for x = 1, width do
- buffer[y][x] = " "
- end
- end
- return buffer
- end
- local function drawPixel(buffer, x, y, char)
- if x >= 1 and x <= #buffer[1] and y >= 1 and y <= #buffer then
- buffer[y][x] = char
- end
- end
- local function drawLine(buffer, x0, y0, x1, y1, z)
- local dx, dy = math.abs(x1 - x0), math.abs(y1 - y0)
- local sx, sy = x0 < x1 and 1 or -1, y0 < y1 and 1 or -1
- local err = dx - dy
- while true do
- drawPixel(buffer, x0, y0, getASCIICharacter(z))
- if x0 == x1 and y0 == y1 then break end
- local e2 = err * 2
- if e2 > -dy then
- err = err - dy
- x0 = x0 + sx
- end
- if e2 < dx then
- err = err + dx
- y0 = y0 + sy
- end
- end
- end
- local function renderBuffer(buffer)
- for y = 1, #buffer do
- term.setCursorPos(1, y)
- term.write(table.concat(buffer[y]))
- end
- end
- function sortByDepth(faces)
- table.sort(faces, function(a, b)
- return (a[2] or 0) > (b[2] or 0)
- end)
- return faces
- end
- function drawTeapot(vertices, faces)
- local screenWidth, screenHeight = term.getSize()
- local angleX, angleY, angleZ = 0, 0, 0
- local fov = 60
- local viewerDistance = 20
- while true do
- local buffer = createBuffer(screenWidth, screenHeight)
- local projectedVertices = {}
- for _, vertex in ipairs(vertices) do
- local x, y, z = table.unpack(vertex)
- local xRot, yRot, zRot = rotatePoint(x, y, z, angleX, angleY, angleZ)
- local xProj, yProj = project(xRot, yRot, zRot, screenWidth, screenHeight, fov, viewerDistance)
- table.insert(projectedVertices, {xProj, yProj, zRot})
- end
- local sortedFaces = {}
- for _, face in ipairs(faces) do
- local v1, v2, v3 = face[1], face[2], face[3]
- if projectedVertices[v1] and projectedVertices[v2] and projectedVertices[v3] then
- local avgZ = (projectedVertices[v1][3] + projectedVertices[v2][3] + projectedVertices[v3][3]) / 3
- table.insert(sortedFaces, {face, avgZ})
- end
- end
- sortedFaces = sortByDepth(sortedFaces)
- for _, faceData in ipairs(sortedFaces) do
- local face, avgZ = faceData[1], faceData[2]
- local v1, v2, v3 = face[1], face[2], face[3]
- local x1, y1 = projectedVertices[v1][1], projectedVertices[v1][2]
- local x2, y2 = projectedVertices[v2][1], projectedVertices[v2][2]
- local x3, y3 = projectedVertices[v3][1], projectedVertices[v3][2]
- drawLine(buffer, x1, y1, x2, y2, avgZ)
- drawLine(buffer, x2, y2, x3, y3, avgZ)
- drawLine(buffer, x3, y3, x1, y1, avgZ)
- end
- renderBuffer(buffer)
- angleX = angleX + 0.05
- angleY = angleY + 0.03
- angleZ = angleZ + 0.02
- sleep(0.05)
- end
- end
- shell.run("wget https://raw.githubusercontent.com/alecjacobson/common-3d-test-models/master/data/teapot.obj teapot.obj")
- local vertices, faces = loadOBJ("teapot.obj")
- local maxSize = 15
- vertices = normalizeVertices(vertices, maxSize)
- drawTeapot(vertices, faces)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement