Advertisement
BombBloke

print3d (ComputerCraft)

Jul 17th, 2018
1,003
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 11.23 KB | None | 0 0
  1. -- +--------------------------------------------------------+
  2. -- |                                                        |
  3. -- |                        print3d                         |
  4. -- |                                                        |
  5. -- +--------------------------------------------------------+
  6.  
  7. local version = "Version 1.1.0"
  8.  
  9. -- By Jeffrey Alexander, aka Bomb Bloke.
  10. -- Command Computer script for the purpose of mimicking an OpenComputers 3D per.
  11. -- http://www.computercraft.info/forums2/index.php?/topic/29653-opencomputers-p3d
  12. -- https://ocdoc.cil.li/block:3d_per
  13.  
  14. ---------------------------------------------
  15. ------------      Functions      ------------
  16. ---------------------------------------------
  17.  
  18. -- Returns true/false depending on whether "val" falls into the specified numeric range.
  19. local function inRange(val, low, high)
  20.     if type(val) ~= "number" then return false end
  21.     return val >= low and val <= high
  22. end
  23.  
  24. -- Where "inData" is an unserialised .3dm file. Returns a string representing the NBT data of the object.
  25. local function _makeNBT(inData, noisy)
  26.     local json, jCount = {}, 1
  27.  
  28.     local function w(...) if noisy then return write(...) end end
  29.     local function p(...) if noisy then return print(...) end end
  30.  
  31.     local function reg(val)
  32.         json[jCount] = tostring(val)
  33.         jCount = jCount + 1
  34.     end
  35.  
  36.     local function regShape(shape, last)
  37.         reg("{texture:\"" .. shape.texture .. "\",")
  38.         if shape.tint then reg("tint:" .. shape.tint .. ",") end
  39.  
  40.         reg("bounds:[B;" .. shape[1] .. "B," .. shape[2] .. "B," .. (16 - shape[6]) .. "B,")
  41.         reg(shape[4] .. "B," .. shape[5] .. "B," .. (16 - shape[3]) .. "B]}")
  42.  
  43.         if not last then reg(",") end
  44.     end
  45.  
  46.     reg("{label:\"")
  47.     w("Configuring...\nLabel: ")
  48.     if type(inData.label) == "string" then
  49.         reg(inData.label .. "\",")
  50.         p("'".. inData.label .. "'")
  51.     else
  52.         reg("3D Print\",")
  53.         p("'not set'")
  54.     end
  55.  
  56.     w("Tooltip: ")
  57.     if type(inData.tooltip) == "string" then
  58.         reg("tooltip:\"" .. inData.tooltip .. "\",")
  59.         p("'".. inData.tooltip .. "'")
  60.     else
  61.         p("'not set'")
  62.     end
  63.  
  64.     reg("lightLevel:")
  65.     w("Light level: ")
  66.     if inRange(inData.lightLevel, 0, 15) then
  67.         reg(math.floor(inData.lightLevel) .. ",")
  68.         p(math.floor(inData.lightLevel))
  69.     else
  70.         reg("0b,")
  71.         p(0)
  72.     end
  73.  
  74.     reg("redstoneLevel:")
  75.     w("Redstone level: ")
  76.     if inRange(inData.emitRedstone, 0, 15) then
  77.         local str = math.floor(inData.emitRedstone)
  78.         reg(str .. ",")
  79.         p(str)
  80.     elseif inData.emitRedstone == true then
  81.         reg(15 .. ",")
  82.         p(15)
  83.     else
  84.         reg(0 .. ",")
  85.         p(0)
  86.     end
  87.  
  88.     reg("isButtonMode:")
  89.     w("Button mode: ")
  90.     if inData.buttonMode == true then
  91.         reg("1b,")
  92.         p("true")
  93.     else
  94.         reg("0b,")
  95.         p("false")
  96.     end
  97.  
  98.     w("Collidable: ")
  99.     if type(inData.collidable) == "table" then
  100.         if inData.collidable[1] == true then
  101.             reg("noclipOff:0b,")
  102.             w("true")
  103.         else
  104.             reg("noclipOff:1b,")
  105.             w("false")
  106.         end
  107.  
  108.         w("/")
  109.  
  110.         if inData.collidable[2] == true then
  111.             reg("noclipOn:0b,")
  112.             p("true")
  113.         else
  114.             reg("noclipOn:1b,")
  115.             p("false")
  116.         end
  117.     else
  118.         reg("noclipOff:0b,noclipOn:0b,")
  119.         p("true/true")
  120.     end
  121.  
  122.     local inactive, active = {}, {}
  123.  
  124.     if type(inData.shapes) == "table" then
  125.         for i = 1, table.maxn(inData.shapes) do
  126.             local shape, nShape = inData.shapes[i], {}
  127.            
  128.             if shape then
  129.                 for j = 1, 6 do
  130.                     local val = tonumber(shape[j])
  131.                     if not inRange(val, 0, 16) then error("Failed parsing data: invalid coord within shape #" .. i, 0) end
  132.                     nShape[j] = math.floor(val)
  133.                 end
  134.  
  135.                 --Apparently this doesn't matter and I can do it anyway:  for j = 1, 3 do if nShape[j] == nShape[j + 3] then error("Failed parsing data: shape #" .. i .. " is flat", 0) end end
  136.  
  137.                 if type(shape.texture) ~= "string" then error("Failed parsing data: invalid texture within shape #" .. i, 0) end
  138.                 nShape.texture = shape.texture
  139.  
  140.                 if inRange(shape.tint, 0, 0xFFFFFF) then nShape.tint = math.floor(shape.tint) end
  141.  
  142.                 if shape.state == true then
  143.                     active[#active + 1] = nShape
  144.                 else
  145.                     inactive[#inactive + 1] = nShape
  146.                 end
  147.             end
  148.         end
  149.     else
  150.         error("Failed parsing data: no shapes", 0)
  151.     end
  152.  
  153.     if inactive == 0 then error("Failed parsing data: no inactive shapes", 0) end
  154.  
  155.     reg("stateOff:[")
  156.     for i = 1, #inactive do regShape(inactive[i], i == #inactive) end
  157.     reg("],stateOn:[")
  158.     for i = 1, #active do regShape(active[i], i == #active) end
  159.     reg("]}")
  160.  
  161.     p("Shapes: " .. #inactive .. " inactive, " .. #active .. " active\nJob successfully committed!")
  162.  
  163.     return table.concat(json)
  164. end
  165.  
  166. -- Returns a numerically indexed table of NBT data strings for all objects in a model made by initModel().
  167. local function parseObject(obj)
  168.     local result = {}
  169.    
  170.     for z = 1, obj.len[3] do for y = 1, obj.len[2] do for x = 1, obj.len[1] do
  171.         result[#result + 1] = _makeNBT(obj[z][y][x])
  172.     end end end
  173.    
  174.     return result
  175. end
  176.  
  177. -- Helper function for setPixel(). Combines nearby pixels into larger polys.
  178. local function insertPixel(obj, sPos, shape, xDir, yDir)
  179.     obj = obj[sPos[3]][sPos[2]][sPos[1]]
  180.     local shapes, merge = obj.shapes, obj.merge
  181.    
  182.     local xO, yO
  183.     if xDir[1] + yDir[1] == 0 then
  184.         xO, yO = 6, 5
  185.     elseif xDir[2] + yDir[2] == 0 then
  186.         xO, yO = 4, 6
  187.     elseif xDir[3] + yDir[3] == 0 then
  188.         xO, yO = 4, 5
  189.     end
  190.     local x, y = shape[xO], shape[yO]
  191.    
  192.     if merge[y][x] then error("Can't draw to same pixel twice, sorry.") end
  193.     merge[y][x] = {#shapes + 1, 1, 1, x, y, shape.texture, shape.tint}
  194.     shapes[#shapes + 1] = shape
  195.    
  196.     repeat
  197.         local found = false
  198.        
  199.         for y = 1, 16 do for x = 1, 16 do
  200.             local m, mX, mY = merge[y][x]
  201.             if x < 16 then mX = merge[y][x + 1] end
  202.             if y < 16 then mY = merge[y + 1][x] end
  203.  
  204.             if m then
  205.                 if mX and m ~= mX and m[3] == mX[3] and m[5] == mX[5] and m[6] == mX[6] and m[7] == mX[7] then
  206.                     m[2], found, shapes[m[1]][xO], shapes[mX[1]] = m[2] + mX[2], true, shapes[m[1]][xO] + mX[2]
  207.                     for yy = mX[5], mX[5] + mX[3] - 1 do for xx = mX[4], mX[4] + mX[2] - 1 do merge[yy][xx] = m end end
  208.                 end
  209.  
  210.                 if mY and m ~= mY and m[2] == mY[2] and m[4] == mY[4] and m[6] == mY[6] and m[7] == mY[7] then
  211.                     m[3], found, shapes[m[1]][yO], shapes[mY[1]] = m[3] + mY[3], true, shapes[m[1]][yO] + mY[3]
  212.                     for yy = mY[5], mY[5] + mY[3] - 1 do for xx = mY[4], mY[4] + mY[2] - 1 do merge[yy][xx] = m end end
  213.                 end
  214.             end
  215.         end end
  216.     until not found
  217. end
  218.  
  219. -- Sets a colour within a 2D plane returned by obj.get2DPlane().
  220. local function setPixel(obj, mirror, start, xDir, yDir, x, y, texture, tint)
  221.     local pos, shape = {}, {["texture"] = texture, ["tint"] = tint}
  222.  
  223.     for i = 1, 3 do
  224.         pos[i] = {}
  225.         pos[i][1] = start[i] + (x - 1) * xDir[i] + (y - 1) * yDir[i]
  226.         pos[i][2] = start[i] + x * xDir[i] + y * yDir[i]
  227.        
  228.         table.sort(pos[i])
  229.  
  230.         shape[i] = pos[i][1] % 16
  231.         shape[i + 3] = pos[i][2] % 16
  232.         if shape[i + 3] == 0 and xDir[i] + yDir[i] ~= 0 then shape[i + 3] = 16 end
  233.     end
  234.  
  235.     local sPos = {math.floor(pos[1][1] / 16) + 1, math.floor(pos[2][1] / 16) + 1, math.floor(pos[3][1] / 16) + 1}
  236.  
  237.     for i = 1, 3 do if sPos[i] > obj.len[i] then
  238.         sPos[i] = sPos[i] - 1
  239.         shape[i], shape[i + 3] = 16, 16
  240.     end end
  241.  
  242.     insertPixel(obj, sPos, shape, xDir, yDir)
  243.  
  244.     if mirror then
  245.         local extraShape = {["texture"] = texture, ["tint"] = tint}
  246.         for j = 1, 6 do extraShape[j] = shape[j] end
  247.         extraShape[mirror], extraShape[mirror + 3], sPos[mirror] = 16, 16, sPos[mirror] - 1
  248.         insertPixel(obj, sPos, extraShape, xDir, yDir)
  249.     end
  250. end
  251.  
  252. -- Helper function for makeMergeTables().
  253. local function makeMergeTable()
  254.     local result = {}
  255.     for i = 1, 16 do result[i] = {} end
  256.     return result
  257. end
  258.  
  259. -- Helper function for initModel(). These tables are used to combine single pixels into larger polys.
  260. local function makeMergeTables(obj, sPos, xDir, yDir)
  261.     if xDir[1] + yDir[1] == 0 then
  262.         for z = 1, obj.len[3] do
  263.             for y = 1, obj.len[2] do
  264.                 obj[z][y][sPos[1]].merge = makeMergeTable()
  265.             end
  266.         end
  267.     elseif xDir[2] + yDir[2] == 0 then
  268.         for z = 1, obj.len[3] do
  269.             for x = 1, obj.len[1] do
  270.                 obj[z][sPos[2]][x].merge = makeMergeTable()
  271.             end
  272.         end
  273.     elseif xDir[3] + yDir[3] == 0 then
  274.         for y = 1, obj.len[2] do
  275.             for x = 1, obj.len[1] do
  276.                 obj[sPos[3]][y][x].merge = makeMergeTable()
  277.             end
  278.         end
  279.     end
  280. end
  281.  
  282. -- Generates a new 3D object, which can span multiple blocks.
  283. local function _initModel(label, xLen, yLen, zLen)
  284.     local obj = {["label"] = label, ["len"] = {math.ceil(xLen / 16), math.ceil(yLen / 16), math.ceil(zLen / 16)}}
  285.  
  286.     for z = 1, obj.len[3] do
  287.         local zLayer = {}
  288.  
  289.         for y = 1, obj.len[2] do
  290.             local yLayer = {}
  291.  
  292.             for x = 1, obj.len[1] do
  293.                 yLayer[x] = {["label"] = label, ["tooltip"] = x .."/" .. y .. "/" .. z, shapes = {}}
  294.             end
  295.  
  296.             zLayer[y] = yLayer
  297.         end
  298.  
  299.         obj[z] = zLayer
  300.     end
  301.    
  302.     local invertZ = obj.len[3] * 16
  303.  
  304.     -- Produces a function for drawing pixels across a 2D plane within the 3D model.
  305.     function obj.get2DPlane(start, xDir, yDir)
  306.         -- Re-invert z axis:
  307.         start[3], xDir[3], yDir[3] = invertZ - start[3], -xDir[3], -yDir[3]
  308.        
  309.         local sPos = {}
  310.         for i = 1, 3 do
  311.             sPos[i] = (xDir[i] + yDir[i] < 0) and math.ceil(start[i] / 16) or (math.floor(start[i] / 16) + 1)
  312.             if sPos[i] > obj.len[i] then sPos[i] = obj.len[i] end
  313.         end
  314.        
  315.         for z = 1, obj.len[3] do for y = 1, obj.len[2] do for x = 1, obj.len[1] do obj[z][y][x].merge = nil end end end
  316.        
  317.         makeMergeTables(obj, sPos, xDir, yDir)
  318.        
  319.         local mirror = false
  320.        
  321.         for i = 1, 3 do if start[i] % 16 == 0 and xDir[i] + yDir[i] == 0 and sPos[i] > 1 and start[i] < obj.len[i] * 16 then
  322.             mirror, sPos[i] = i, sPos[i] - 1
  323.             makeMergeTables(obj, sPos, xDir, yDir)
  324.             break
  325.         end end
  326.        
  327.         return function(x, y, texture, tint)
  328.             setPixel(obj, mirror, start, xDir, yDir, x, y, texture, tint)
  329.         end
  330.     end
  331.    
  332.     -- Returns a numerically indexed table of NBT data strings for all objects in the model.
  333.     function obj.parse()
  334.         return parseObject(obj)
  335.     end
  336.  
  337.     return obj
  338. end
  339.  
  340. ---------------------------------------------
  341. ------------     Load as API     ------------
  342. ---------------------------------------------
  343.  
  344. if not shell then
  345.     makeNBT = _makeNBT
  346.     initModel = _initModel
  347.  
  348. ---------------------------------------------
  349. ------------     Script Code     ------------
  350. ---------------------------------------------
  351.  
  352. else
  353.     if not commands then error("This script requires a Command Computer to execute.", 0) end
  354.    
  355.     local filename, num = ...
  356.  
  357.     if not filename then
  358.         print("Usage: print3d FILE [count]")
  359.         error()
  360.     end
  361.    
  362.     num = tonumber(num)
  363.     if not inRange(num, 1, 64) then num = 1 end
  364.    
  365.     if filename:lower():sub(-4) ~= ".3dm" and not fs.exists(filename) then filename = filename .. ".3dm" end
  366.     if not fs.exists(filename) then error("Failed opening file: file not found", 0) end
  367.  
  368.     local inFile = fs.open(filename, "r")
  369.     if not inFile then error("Failed opening file: possibly locked?", 0) end
  370.     local inData = textutils.unserialise(inFile.readAll())
  371.     inFile.close()
  372.  
  373.     if not inData then error("Failed unserialising file: invalid table data", 0) end
  374.    
  375.     local cmd = "give @p opencomputers:print " .. num .. " 0 " .. _makeNBT(inData, true)
  376.  
  377.     local ok, result = commands.exec(cmd)
  378.     if not ok then error("Print failed: ".. textutils.serialise(result), 0) end
  379.    
  380.     if filename:lower():sub(-4) == ".3dm" then filename = filename:sub(1, -5) end
  381.    
  382.     local outFile = fs.open(filename .. ".txt", "w")
  383.     outFile.write(cmd)
  384.     outFile.close()
  385. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement