Guest User

render

a guest
Jun 28th, 2013
1,238
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 15.10 KB | None | 0 0
  1. os.loadAPI(shell.resolve(".").."/matrix")
  2. os.loadAPI(shell.resolve(".").."/graphics")
  3. os.loadAPI(shell.resolve(".").."/vertex")
  4. local w,h = term.getSize()
  5.  
  6. --[[
  7. 3D Render
  8. Display objects (and eventually textures) mapped in 3D coordinate space
  9. Makes use of the graphics API for projection and matrix API for transformations
  10.        
  11. By Nitrogen Fingers
  12. Feel free to play with this code as much as you want!
  13. ]]--
  14.  
  15. local verbose = false
  16.  
  17. camera = {
  18.     position = vertex.new(0,0,0);
  19.     rX = 0; rY = 0; rZ = 0;
  20. }
  21.  
  22. tetrahedron = {
  23.     name = "Tetrahedron";
  24.     vertices = {
  25.         [1] = vertex.new(0,-3,0);
  26.         [2] = vertex.new(0,3,4.5);
  27.         [3] = matrix.createRotationY((math.pi*2)/3):apply(vertex.new(0,3,4.5));
  28.         [4] = matrix.createRotationY((math.pi*4)/3):apply(vertex.new(0,3,4.5));
  29.     };
  30.  
  31.     colors = {
  32.         [1] = colors.red;
  33.         [2] = colors.yellow;
  34.         [3] = colors.lime;
  35.         [4] = colors.lightBlue;
  36.     };
  37.  
  38.     indices = {
  39.         1,3,2,  3,1,4,  4,1,2,  2,3,4
  40.     };
  41.     scale = 1;
  42.     translate = vector.new(0,0,5);
  43.     rotateX = 0; rotateY = 0; rotateZ = 0;
  44. }
  45.  
  46. cube = {
  47.     name = "Cube";
  48.     vertices = {
  49.         [1] = vertex.new(3,3,-3),
  50.         [2] = vertex.new(3,-3,-3),
  51.         [3] = vertex.new(-3,-3,-3),
  52.         [4] = vertex.new(-3,3,-3),
  53.         [5] = vertex.new(3,3,3),
  54.         [6] = vertex.new(3,-3,3),
  55.         [7] = vertex.new(-3,-3,3),
  56.         [8] = vertex.new(-3,3,3);
  57.     };
  58.    
  59.     colors = {
  60.         [1] = colors.red;
  61.         [2] = colors.blue;
  62.         [3] = colors.lime;
  63.         [4] = colors.yellow;
  64.         [5] = colors.orange;
  65.         [6] = colors.white;
  66.         [7] = colours.lightBlue;
  67.         [8] = colours.purple;
  68.     };
  69.     --Triangle list: that is each 3 indices make up 1 face (12 total for this model)
  70.     --Must be CLOCKWISE to avoid culling
  71.     indices = {
  72.         1,2,3,  1,3,4,  7,6,8,  8,6,5,  3,7,4,  4,7,8, 
  73.         5,6,2,  2,1,5,  5,1,4,  4,8,5,  2,6,3,  6,7,3
  74.     };
  75.    
  76.     scale = 1;
  77.     translate = vector.new(0,0,5);
  78.     rotateX = 0; rotateY = 0; rotateZ = 0;
  79. }
  80.  
  81. hourglass = {
  82.     name = "Hourglass";
  83.     vertices = {
  84.         --Top
  85.         [1] = vertex.new(3,-3,3);
  86.         [2] = vertex.new(3,-3,-3);
  87.         [3] = vertex.new(-3,-3,-3);
  88.         [4] = vertex.new(-3,-3,3);
  89.         --Centre
  90.         [5] = vertex.new(0,0,0);
  91.         --Bottom
  92.         [6] = vertex.new(3,3,3);
  93.         [7] = vertex.new(3,3,-3);
  94.         [8] = vertex.new(-3,3,-3);
  95.         [9] = vertex.new(-3,3,3);
  96.     };
  97.  
  98.     colors = {
  99.         [1] = colours.red;
  100.         [2] = colours.orange;
  101.         [3] = colours.yellow;
  102.         [4] = colours.white;
  103.         [5] = colours.black;    --should never be used
  104.         [6] = colours.blue;
  105.         [7] = colours.lightGrey;
  106.         [8] = colours.lightBlue;
  107.         [9] = colours.cyan;
  108.     };
  109.  
  110.     indices = {
  111.         --Upper hourglass       --Upper Base
  112.         1,2,5,  2,3,5,  3,4,5,  4,1,5,  1,3,2, 1,4,3,
  113.         --Lower hourglass       --Lower Base
  114.         6,5,7,  7,5,8,  8,5,9,  9,5,6,  6,7,8,  6,8,9
  115.     };
  116.  
  117.     scale = 1;
  118.     translate = vector.new(0,0,5);
  119.     rotateX = 0; rotateY = 0; rotateZ = 0;
  120. }
  121.  
  122. --This thing is a freaking monster.
  123. tiefighter = {
  124.     name = "TIE Fighter";
  125.     vertices = {
  126.         --Left wing- centre then ring around
  127.         [1] = vertex.new(-3.5,0,0);
  128.         [2] = vertex.new(-3.5,-4,-1.5);
  129.         [3] = vertex.new(-3.5,0,-2.5);
  130.         [4] = vertex.new(-3.5,4,-1.5);
  131.         [5] = vertex.new(-3.5,4,1.5);
  132.         [6] = vertex.new(-3.5,0,2.5);
  133.         [7] = vertex.new(-3.5,-4,1.5);
  134.         --Right wing- centre then ring around
  135.         [8] = vertex.new(3.5,0,0);
  136.         [9] = vertex.new(3.5,-4,-1.5);
  137.         [10] = vertex.new(3.5,0,-2.5);
  138.         [11] = vertex.new(3.5,4,-1.5);
  139.         [12] = vertex.new(3.5,4,1.5);
  140.         [13] = vertex.new(3.5,0,2.5);
  141.         [14] = vertex.new(3.5,-4,1.5);
  142.         --Left Connector (what a waste of computation...)
  143.         [15] = vertex.new(-3,-0.25,0);
  144.         [16] = vertex.new(-1.5,-0.25,0);
  145.         [17] = vertex.new(-1.5,0.25,0);
  146.         [18] = vertex.new(-3,0.25,0);
  147.         --Right connector
  148.         [19] = vertex.new(3,-0.25,0);
  149.         [20] = vertex.new(1.5,-0.25,0);
  150.         [21] = vertex.new(1.5,0.25,0);
  151.         [22] = vertex.new(3,0.25,0);
  152.         --Cockpit
  153.         [23] = vertex.new(1,-1,-1);
  154.         [24] = vertex.new(1,-1,1);
  155.         [25] = vertex.new(-1,-1,1);
  156.         [26] = vertex.new(-1,-1,-1);
  157.  
  158.         [27] = vertex.new(1.5,0,-1.5);
  159.         [28] = vertex.new(1.5,0,1.5);
  160.         [29] = vertex.new(-1.5,0,1.5);
  161.         [30] = vertex.new(-1.5,0,-1.5);
  162.        
  163.         [31] = vertex.new(1,1,-1);
  164.         [32] = vertex.new(1,1,1);
  165.         [33] = vertex.new(-1,1,1);
  166.         [34] = vertex.new(-1,1,-1);
  167.     };
  168.  
  169.     --Far fewer colors. We're being sneaky with our indices to make this easier.
  170.     colors = {
  171.         [1] = colours.grey;
  172.         [8] = colours.grey;
  173.  
  174.         [15] = colours.lightGrey;
  175.         [19] = colours.lightGrey;
  176.  
  177.         [23] = colours.lightGrey;
  178.         [24] = colours.grey;
  179.         [25] = colours.lightGrey;
  180.         [26] = colours.grey;
  181.  
  182.         [31] = colours.lightGrey;
  183.         [32] = colours.lightGrey;
  184.         [33] = colours.lightGrey;
  185.         [34] = colours.lightGrey;
  186.     };
  187.  
  188.     --So there are some duplicates, name for the wings and connectors as we need them to display
  189.     --on both sides (making them polygons is just too expensive).
  190.     indices = {
  191.         --Left Wing clockwise          
  192.         1,2,3,  1,3,4,  1,4,5,  1,5,6,  1,6,7,  1,7,2,
  193.         --counter-clockwise
  194.         1,2,7,  1,7,6,  1,6,5,  1,5,4,  1,4,3,  1,3,2,
  195.         --Right Wing clockwise
  196.         8,9,10, 8,10,11, 8,11,12, 8,12,13, 8,13,14, 8,14,9,
  197.         --counter-clockwise
  198.         8,9,14, 8,14,13, 8,13,12, 8,12,11, 8,11,10, 8,10,9,
  199.         --Left connector, clockwise and counter
  200.         15,16,17,  15,17,18,  15,17,16,  15,18,17,
  201.         --Right connector, clockwise and counter
  202.         19,20,21,  19,21,22,  19,21,20,  19,22,21,
  203.         --Cockpit, base and roof
  204.         23,24,25,  23,25,26,  31,33,32,  31,34,33,
  205.         --Cockpit, sides
  206.         23,27,28,  23,24,28,  24,28,29,  24,29,25,  25,29,30,  25,30,26,  26,30,27,  26,27,23,
  207.         31,32,27,  31,28,27,  32,33,28,  32,28,29,  33,34,29,  33,29,30,  34,31,30,  34,30,27
  208.     };
  209.  
  210.     --A little thing I threw in- makes it a bit nicer to look at
  211.     wfcol = colours.lightGrey;
  212.  
  213.     scale = 1;
  214.     translate = vector.new(0,0,5);
  215.     rotateX = 0; rotateY = 0; rotateZ = 0;
  216. }
  217.  
  218. local modelList = {tetrahedron, cube, hourglass, tiefighter}
  219. local msel = 1
  220. local wireframe = false
  221.  
  222. function minPoint(v1,v2)
  223.     if v1.y < v2.y then return v1
  224.     elseif v1.y > v2.y then return v2
  225.     elseif v1.x < v2.x then return v1
  226.     else return v2 end
  227. end
  228. function maxPoint(v1,v2)
  229.     if v1.y < v2.y then return v2
  230.     elseif v1.y > v2.y then return v1
  231.     elseif v1.x < v2.x then return v2
  232.     else return v1 end
  233. end
  234.  
  235. --Ordering has to be precisely as follows
  236.     -- V1 is the topmost and leftmost of the three vertices
  237.     -- V3 is the bottommost and rightmost
  238.     -- V2 is what's left over
  239. --It's very inefficient but it saves computational expense in the longrun.
  240. --NOTE: We now also floor the
  241. function orderPoints(v1,v2,v3)
  242.     local mini = minPoint(minPoint(v1,v2),v3)
  243.     mini.x =  math.floor(mini.x) mini.y = math.floor(mini.y)
  244.     local maxi = maxPoint(maxPoint(v1,v2),v3)
  245.     --This seems to make clipping worse, so I've left it out. True also for midi
  246.     --maxi.x = math.floor(maxi.x) maxi.y = math.floor(maxi.y)
  247.    
  248.     local midi = nil
  249.     if v1 ~= mini and v1 ~= maxi then return mini,v1,maxi
  250.     elseif v2 ~= mini and v2 ~= maxi then return mini,v2,maxi
  251.     else return mini,v3,maxi end
  252. end
  253.  
  254. --The 'illusion of depth' function, really just for debugging and demo work.
  255. function drawPointVert(vert)
  256.     term.setCursorPos(vert.x, vert.y)
  257.     if vert.dist < 5 then
  258.         term.setBackgroundColour(vert.color)
  259.         term.write(" ")
  260.         term.setBackgroundColour(colours.black)
  261.     elseif vert.dist < 10 then
  262.         term.setTextColour(vert.color)
  263.         term.write("0")
  264.     elseif vert.dist < 15 then
  265.         term.setTextColour(vert.color)
  266.         term.write("*")
  267.     else
  268.         term.setTextColour(vert.color)
  269.         term.write(".")
  270.     end
  271. end
  272.  
  273. function clearScreen()
  274.     term.setBackgroundColour(colours.black)
  275.     term.clear()
  276. end
  277.  
  278. --Breshman's Implementation
  279. local function drawLine(x1, y1, x2, y2, colour)
  280.     x1 = math.floor(x1)
  281.     x2 = math.floor(x2)
  282.     y1 = math.floor(y1)
  283.     y2 = math.floor(y2)
  284.     term.setBackgroundColour(colour)
  285.    
  286.     local steep = math.abs(x2 - x1) < math.abs(y2 - y1)
  287.     if steep then
  288.         local cup = x1
  289.         x1 = y1
  290.         y1 = cup
  291.         cup = x2
  292.         x2 = y2
  293.         y2 = cup
  294.     end
  295.    
  296.     local dx,dy = math.abs(x2 - x1), math.abs(y2 - y1)
  297.     local err = dx/2
  298.     local ystep,y = 0,y1
  299.     local inc = 0
  300.     if x2 < x1 then inc = -1 else inc = 1 end
  301.     if y2 < y1 then ystep = -1 else ystep = 1 end
  302.    
  303.     for x = x1, x2, inc do
  304.         if steep then
  305.             term.setCursorPos(y, x)
  306.         else term.setCursorPos(x, y) end
  307.         term.write(" ")
  308.         err = err - dy
  309.         if err < 0 then
  310.             y = y + ystep
  311.             err = err + dx
  312.         end
  313.     end
  314. end
  315.  
  316. --Assuming that V1 is the top of the triangle, and V2 is the left edge of the bottom
  317. function fillBottomTriangle(v1, v2, v3, colour)
  318.     local leftSlope = (v2.x - v1.x) / (v2.y - v1.y)
  319.     local rightSlope = (v3.x - v1.x) / (v3.y - v1.y)
  320.    
  321.     leftx = v1.x
  322.     rightx = v1.x
  323.    
  324.     for y = v1.y, v2.y do
  325.         drawLine(leftx, y, rightx, y, colour)
  326.         leftx = leftx + leftSlope
  327.         rightx = rightx + rightSlope
  328.     end
  329. end
  330.  
  331. --Assuming V3 is bottom of triangle and V1 is left edge of top
  332. function fillTopTriangle(v1, v2, v3, colour)
  333.     local leftSlope = (v1.x - v3.x) / (v1.y - v3.y)
  334.     local rightSlope = (v2.x - v3.x) / (v2.y - v3.y)
  335.    
  336.     leftx = v3.x
  337.     rightx = v3.x
  338.    
  339.     for y = v3.y, v1.y, -1 do
  340.         drawLine(leftx, y, rightx, y, colour)
  341.         leftx = leftx - leftSlope
  342.         rightx = rightx - rightSlope
  343.     end
  344. end
  345.  
  346. --Fills a flat triangle. It uses top and bottom, individually if the triangle is flat on one
  347. --side, otherwise splits the triangle in two and draws both sides.
  348. function fillFlatTriangle(p1, p2, p3, colour)
  349.     local v1,v2,v3 = orderPoints(p1,p2,p3)
  350.     term.setCursorPos(1,h-1)
  351.    
  352.     term.setBackgroundColour(colour)
  353.  
  354.     if v2.y == v3.y then
  355.         fillBottomTriangle(v1,v2,v3, colour)
  356.     elseif v1.y == v2.y then
  357.         fillTopTriangle(v1,v2,v3, colour)
  358.     else
  359.         v4 = { x = v1.x + ((v2.y - v1.y) / (v3.y - v1.y)) * (v3.x - v1.x), y = v2.y }
  360.         fillBottomTriangle(v1, v2, v4, colour)
  361.         fillTopTriangle(v2, v4, v3, colour)
  362.     end
  363. end
  364.  
  365. --Draws our 3D models. Right now it only draws the vertices, not faces or lines.
  366. function renderModel(model)
  367.     world = {}
  368.     --Note: Matrix operations, especially rotations are expensive. Rather than re-creating
  369.     --matrices over and over as done here, it is much safer to initialize them at the start
  370.     --of a program, and only update when necessary. If something is beyond the view frustrum,
  371.     --don't render it!!!
  372.  
  373.     --Creating the world space
  374.     global = matrix.identity()
  375.     global = global * matrix.createScale(model.scale)
  376.     global = global * matrix.createRotationX(model.rotateX)
  377.     global = global * matrix.createRotationY(model.rotateY)
  378.     global = global * matrix.createRotationZ(model.rotateZ)
  379.     global = global * matrix.createTranslation(model.translate)
  380.  
  381.     distances = {}
  382.    
  383.     for i,vertex in ipairs(model.vertices) do
  384.         table.insert(world,global:apply(vertex))
  385.         table.insert(distances, math.sqrt(math.pow(camera.position.x - world[i].x, 2) +
  386.             math.pow(camera.position.y - world[i].y, 2) +
  387.             math.pow(camera.position.z - world[i].z, 2)))
  388.     end
  389.     --Converting from world space to camera space
  390.     global = matrix.identity()
  391.     global = global * matrix.createTranslation(-camera.position)
  392.     global = global * matrix.createRotationX(-camera.rX)
  393.  
  394.     for i,vertex in ipairs(world) do
  395.         --Applying camera space
  396.         world[i] = global:apply(vertex)
  397.     end
  398.  
  399.     local projection = {}
  400.     --Project camera space into screen space
  401.     for i=1,#world do
  402.         local col = world[i].color
  403.         projection[i] = graphics.project(world[i])
  404.         --Here until we sort out depth buffering
  405.         projection[i].color = model.colors[i]
  406.         projection[i].dist = distances[i]
  407.     end
  408.  
  409.     --For culling, we first find the direction the camera is facing.
  410.     local cameraFacing = (matrix.createRotationX(camera.rX)):apply(vector.new(0, 0, -1))
  411.  
  412.     --Preparing our faces to be drawn, depth buffering and so on...
  413.     local faces = {}
  414.     for i=1,#model.indices, 3 do
  415.         --We compute the face normal first (a line perpendicular to that face)
  416.         local v1,v2,v3 = world[model.indices[i]], world[model.indices[i+1]], world[model.indices[i+2]]
  417.         --An average centre (less precise but more reliable than a centroid for right-angles)
  418.         local vc = vector.new((v1.x + v2.x + v3.x)/3, (v1.y + v2.y + v3.y)/3, (v1.z + v2.z + v3.z)/3)
  419.  
  420.         --Only opposite facing triangles are draw- we cull same-side triangles
  421.         --(who knew it would be that easy?)
  422.         local faceNormal = (v1 - v2):cross(v3 - v2)
  423.         --We don't draw faces behind the camera either
  424.         local cameraPF =  matrix.createRotationX(camera.rX):apply(vector.new(0,0,-1)) + camera.position
  425.  
  426.         if faceNormal:dot(cameraFacing) <= 0 and cameraPF:dot(vc) < 0 then
  427.             local face = {
  428.                 p1 = projection[model.indices[i]],
  429.                 p2 = projection[model.indices[i+1]],
  430.                 p3 = projection[model.indices[i+2]],
  431.                 --Placeholder. Will be coloured by a different system soon...
  432.                 colour = model.colors[model.indices[i]],
  433.                 distance = (camera.position - vc):length()
  434.             }
  435.             local prefSide = 1
  436.             for i=1,#faces do
  437.                 if faces[i].distance < face.distance then break
  438.                 else prefSide = prefSide+1 end
  439.             end
  440.             table.insert(faces, prefSide, face)
  441.         end
  442.     end
  443.  
  444.     term.clear()
  445.  
  446.     --Preparing to draw faces
  447.     for _,face in pairs(faces) do
  448.         fillFlatTriangle(face.p1, face.p2, face.p3, face.colour)
  449.         --This isn't very expensive, and it removes artefacts around edges, which is nice.
  450.         local linecol = model.wfcol or face.colour
  451.         drawLine(face.p1.x, face.p1.y, face.p2.x, face.p2.y, linecol)
  452.         drawLine(face.p2.x, face.p2.y, face.p3.x, face.p3.y, linecol)
  453.         drawLine(face.p1.x, face.p1.y, face.p3.x, face.p3.y, linecol)
  454.     end
  455. end
  456.  
  457. --Again, a little demo function to show off the operations
  458. function printHUD()
  459.     term.setBackgroundColour(colours.black)
  460.     term.setTextColour(colours.white)
  461.     term.setCursorPos(1,h-1)
  462.     term.write("Drawing "..modelList[msel].name.."     +/- to switch")
  463.     term.setCursorPos(1,h)
  464.     term.write("Arrows move, WSAD QE rotate, RF scale. Enter quits.")
  465. end
  466.  
  467. --Lets you play with the 3D model a bit.
  468. function run()
  469.     quit = false
  470.     repeat
  471.         clearScreen()
  472.         renderModel(modelList[msel])
  473.         printHUD()
  474.         local _,key = os.pullEvent("key")
  475.    
  476.         --Arrow keys translate
  477.         if key == keys.up then
  478.             modelList[msel].translate.z = modelList[msel].translate.z + 1
  479.         elseif key == keys.down then
  480.             modelList[msel].translate.z = modelList[msel].translate.z - 1
  481.         elseif key == keys.left then
  482.             modelList[msel].translate.x = modelList[msel].translate.x - 1
  483.         elseif key == keys.right then
  484.             modelList[msel].translate.x = cube.translate.x + 1
  485.         --WSAD QE keys rotate
  486.         elseif key == keys.w then
  487.             modelList[msel].rotateX = modelList[msel].rotateX - (math.pi/16)
  488.         elseif key == keys.s then
  489.             modelList[msel].rotateX = modelList[msel].rotateX + (math.pi/16)
  490.         elseif key == keys.q then
  491.             modelList[msel].rotateZ = modelList[msel].rotateZ - (math.pi/16)
  492.         elseif key == keys.e then
  493.             modelList[msel].rotateZ = modelList[msel].rotateZ + (math.pi/16)
  494.         elseif key == keys.a then
  495.             modelList[msel].rotateY = modelList[msel].rotateY + (math.pi/16)
  496.         elseif key == keys.d then
  497.             modelList[msel].rotateY = modelList[msel].rotateY - (math.pi/16)
  498.         --RF scales
  499.         elseif key == keys.r then
  500.             modelList[msel].scale = modelList[msel].scale * 1.1
  501.         elseif key == keys.f then
  502.             modelList[msel].scale = modelList[msel].scale / 1.1
  503.         elseif key == keys.v then
  504.             verbose = not verbose
  505.         -- +/- switches model
  506.         elseif key == keys.minus then
  507.             msel = msel-1 if msel == 0 then msel = #modelList end
  508.         elseif key == keys.equals then
  509.             msel = (msel % #modelList) + 1
  510.         --Enter quits
  511.         elseif key == keys.enter then
  512.             quit = true
  513.         end
  514.     until quit == true
  515. end
  516.  
  517. run()
  518. shell.run("clear")
Advertisement
Add Comment
Please, Sign In to add comment