MoonlightOwl

Hologram Editor 0.7.1-en

Feb 3rd, 2017
9,690
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 35.51 KB | None | 0 0
  1. --       Hologram Editor v0.7.1
  2. -- 2017 (c) Totoro (aka MoonlightOwl)
  3. --         computercraft.ru
  4.  
  5. local unicode = require('unicode')
  6. local event = require('event')
  7. local term = require('term')
  8. local fs = require('filesystem')
  9. local shell = require('shell')
  10. local com = require('component')
  11. local gpu = com.gpu
  12.  
  13. --     Colors     --
  14. local color = {
  15.   back = 0x000000,
  16.   fore = 0xFFFFFF,
  17.   info = 0x335555,
  18.   error = 0xFF3333,
  19.   help = 0x336600,
  20.   gold = 0xFFCC33,
  21.   gray = 0x080808,
  22.   lightgray = 0x333333,
  23.   lightlightgray = 0x666666
  24. }
  25.  
  26. --     Buttons    --
  27. local keys = {
  28.   BACKSPACE = 14,
  29.   EXIT = 16,
  30.   ENTER = 28,
  31.   ERASER = 41,
  32.   CLEAR = 211
  33. }
  34.  
  35. --  Localization  --
  36. local loc = {
  37.   FILE_REQUEST = 'Enter file name',
  38.   ERROR_CAPTION = 'Error',
  39.   WARNING_CAPTION = 'Warning',
  40.   DONE_CAPTION = 'Done',
  41.   PROJECTOR_UNAVAILABLE_MESSAGE = 'Projector not found!',
  42.   SAVING_MESSAGE = 'Saving...',
  43.   SAVED_MESSAGE = 'The file was saved!',
  44.   LOADING_MESSAGE = 'Loading...',
  45.   LOADED_MESSAGE = 'Loaded successfully!',
  46.   TOO_LOW_RESOLUTION_ERROR = '[ERROR] Your display/GPU does not support 80×25+ resolution.',
  47.   TOO_LOW_SCREEN_TIER_ERROR = '[ERROR] You can use the Tier 2 GPU, but you need Tier 3 display anyway.',
  48.   FORMAT_READING_ERROR = 'Invalid file format!',
  49.   FILE_NOT_FOUND_ERROR = 'File not found!',
  50.   CANNOT_OPEN_ERROR = 'Cannot open the file!',
  51.   CANNOT_SAVE_ERROR = 'Cannot write to the file!',
  52.   PALETTE_FRAME = 'Palette',
  53.   VIEWPORT_FRAME = 'Projection',
  54.   UTILS_FRAME = 'Management',
  55.   LAYER_LABEL = 'Hologram layer:',
  56.   GHOST_LAYER_LABEL = '\'Ghost\' layer:',
  57.   PROGRAMMERS_LABEL = 'Developers:',
  58.   CONTACT_LABEL = 'Contact:',
  59.   EXIT_LABEL = "Quit: 'Q' or ",
  60.   EXIT_BUTTON = 'Exit',
  61.   REFRESH_BUTTON = 'Refresh',
  62.   TOP_BUTTON = 'Top',
  63.   FRONT_BUTTON = 'Front',
  64.   SIDE_BUTTON = 'Side',
  65.   BELOW_BUTTON = 'Below',
  66.   ABOVE_BUTTON = 'Above',
  67.   CLEAR_BUTTON = 'Clear',
  68.   FILL_BUTTON = 'Fill',
  69.   TO_PROJECTOR = 'To Projector',
  70.   SAVE_BUTTON = 'Save',
  71.   LOAD_BUTTON = 'Load',
  72.   NEW_FILE_BUTTON = 'New file'
  73. }
  74. --      ****      --
  75.  
  76.  
  77. -- Try to load a component safely
  78. local function trytofind(name)
  79.   if com.isAvailable(name) then
  80.     return com.getPrimary(name)
  81.   else
  82.     return nil
  83.   end
  84. end
  85.  
  86. -- Constants --
  87. local OLDWIDTH, OLDHEIGHT = gpu.getResolution()
  88. local WIDTH, HEIGHT = gpu.maxResolution()
  89. local FULLSIZE = true
  90. local HOLOW, HOLOH = 48, 32        -- hologram size
  91. local MENUX = HOLOW*2+5            -- right panel offset
  92. local BUTTONW = 12                 -- standart button width
  93. local GRIDX, GRIDY = 3, 2          -- grid offset
  94. local TOP = { width = HOLOW, height = HOLOW, depth = HOLOH }
  95. local FRONT = { width = HOLOW, height = HOLOH, depth = HOLOW }
  96. local SIDE = { width = HOLOW, height = HOLOH, depth = HOLOW }
  97.  
  98. -- Interface variables --
  99. local buttons = {}
  100. local textboxes = {}
  101. local repaint = false
  102.  
  103. -- App state --
  104. local colortable = {}
  105. local hexcolortable = {}
  106. local darkhexcolors = {}
  107. local brush = {color = 1, x = 8, cx = 8, moving = false}
  108. local ghost_layer = 1
  109. local ghost_layer_below = true
  110. local layer = 1
  111. local view = TOP
  112. local running = true
  113.  
  114. -- Auxiliary functions --
  115. local function rgb2hex(r,g,b)
  116.   return r*65536+g*256+b
  117. end
  118. local function setHexColor(n, r, g, b)
  119.   local hexcolor = rgb2hex(r,g,b)
  120.   hexcolortable[n] = hexcolor
  121.   darkhexcolors[n] = bit32.rshift(bit32.band(hexcolor, 0xfefefe), 1)
  122. end
  123.  
  124. local _f = gpu.getForeground()
  125. local function foreground(color)
  126.   if color ~= _f then gpu.setForeground(color); _f = color end
  127. end
  128. local _b = gpu.getBackground()
  129. local function background(color)
  130.   if color ~= _b then gpu.setBackground(color); _b = color end
  131. end
  132.  
  133. -- ========================================= H O L O G R A P H I C S ========================================= --
  134. local holo = {}
  135. local function set(x, y, z, value)
  136.   if holo[x] == nil then holo[x] = {} end
  137.   if holo[x][y] == nil then holo[x][y] = {} end
  138.   holo[x][y][z] = value
  139. end
  140. local function get(x, y, z)
  141.   if holo[x] ~= nil and holo[x][y] ~= nil and holo[x][y][z] ~= nil then
  142.     return holo[x][y][z]
  143.   else
  144.     return 0
  145.   end
  146. end
  147.  
  148. local writer = {}
  149. function writer:init(file)
  150.   self.buffer = {}
  151.   self.file = file
  152. end
  153. function writer:write(sym)
  154.   table.insert(self.buffer, sym)
  155.   if #self.buffer >= 4 then self:finalize() end
  156. end
  157. function writer:finalize()
  158.   if #self.buffer > 0 then
  159.     local byte = 0
  160.     for i=4, 1, -1 do
  161.       local x = self.buffer[i] or 0
  162.       byte = byte * 4 + x
  163.     end
  164.     self.file:write(string.char(byte))
  165.     self.buffer = {}
  166.   end
  167. end
  168.  
  169. local function toBinary(x)
  170.   local data = {}
  171.   while x > 0 do
  172.     table.insert(data, x % 2)
  173.     x = math.floor(x / 2)
  174.   end
  175.   return data
  176. end
  177.  
  178. local function save(filename, compressed)
  179.   -- saving the palette
  180.   local file = io.open(filename, 'wb')
  181.   if file ~= nil then
  182.     for i=1, 3 do
  183.       for c=1, 3 do
  184.         file:write(string.char(colortable[i][c]))
  185.       end
  186.     end
  187.     writer:init(file)
  188.     if compressed then
  189.       local function put(symbol, length)
  190.         if length > 0 then
  191.           writer:write(symbol)
  192.           local l = toBinary(length + 1)
  193.           l[#l] = nil
  194.           l[1] = l[1] + 2
  195.           for i=#l, 1, -1 do writer:write(l[i]) end
  196.         end
  197.       end
  198.       local len = 0
  199.       local sym = -1
  200.       -- saving compressed data
  201.       for x=1, HOLOW do
  202.         for y=1, HOLOH do
  203.           for z=1, HOLOW do
  204.             local a = get(x, y, z)
  205.             if sym == a then  -- next symbol of the sequence
  206.               len = len + 1
  207.             else              -- first symbol of the sequence
  208.               put(sym, len)
  209.               len = 1
  210.               sym = a
  211.             end
  212.           end
  213.         end
  214.       end
  215.       put(sym, len)  -- the last sequence
  216.     else
  217.       -- saving the data without compression
  218.       for x=1, HOLOW do
  219.         for y=1, HOLOH do
  220.           for z=1, HOLOW do
  221.             writer:write(get(x, y, z))
  222.           end
  223.         end
  224.       end
  225.     end
  226.     writer:finalize()
  227.     file:close()
  228.     return true
  229.   else
  230.     return false, filename..": "..loc.CANNOT_SAVE_ERROR
  231.   end
  232. end
  233.  
  234. local reader = {}
  235. function reader:init(file)
  236.   self.buffer = {}
  237.   self.file = file
  238. end
  239. function reader:read()
  240.   if #self.buffer == 0 then
  241.     if not self:fetch() then return nil end
  242.   end
  243.   -- get the last symbol from the buffer
  244.   local sym = self.buffer[#self.buffer]
  245.   self.buffer[#self.buffer] = nil
  246.   return sym
  247. end
  248. function reader:fetch()
  249.   self.buffer = {}
  250.   local char = file:read(1)
  251.   if char == nil then return false
  252.   else
  253.     local byte = string.byte(char)
  254.     for i=0, 3 do
  255.       local a = byte % 4
  256.       byte = math.floor(byte / 4)
  257.       self.buffer[4-i] = a   -- writing bytes in reversed order
  258.     end
  259.     return true
  260.   end
  261. end
  262.  
  263. local function load(filename, compressed)
  264.   local path = shell.resolve(filename, "")
  265.   if path ~= nil then
  266.     file = io.open(filename, 'rb')
  267.     if file ~= nil then
  268.       -- loading the palette
  269.       for i=1, 3 do
  270.         for c=1, 3 do
  271.           colortable[i][c] = string.byte(file:read(1))
  272.         end
  273.         setHexColor(i,colortable[i][1],
  274.                       colortable[i][2],
  275.                       colortable[i][3])
  276.       end
  277.       -- loading the data
  278.       holo = {}
  279.       reader:init(file)
  280.       if compressed then          -- reading compressed data
  281.         local x, y, z = 1, 1, 1
  282.         while true do
  283.           local a = reader:read() -- reading symbol value
  284.           if a == nil then file:close(); return true end
  285.           local len = 1
  286.           while true do           -- reading binary length value
  287.             local b = reader:read()
  288.             if b == nil then
  289.               file:close()
  290.               if a == 0 then return true
  291.               else return false, filename..": "..loc.FORMAT_READING_ERROR end
  292.             end
  293.             local fin = (b > 1)
  294.             if fin then b = b-2 end
  295.             len = bit32.lshift(len, 1)
  296.             len = len + b
  297.             if fin then break end
  298.           end
  299.           len = len - 1
  300.           -- write the sequence
  301.           for i=1, len do
  302.             -- write one voxel
  303.             if a ~= 0 then set(x,y,z, a) end
  304.             -- move the coordinates
  305.             z = z+1
  306.             if z > HOLOW then
  307.               y = y+1
  308.               if y > HOLOH then
  309.                 x = x+1
  310.                 if x > HOLOW then file:close(); return true end
  311.                 y = 1
  312.               end
  313.               z = 1
  314.             end  
  315.           end
  316.         end
  317.       else                        -- reading uncompressed data
  318.         for x=1, HOLOW do
  319.           for y=1, HOLOH do
  320.             for z=1, HOLOW do
  321.               local a = reader:read()
  322.               if a ~= 0 and a ~= nil then
  323.                 set(x,y,z, a)
  324.               end
  325.             end
  326.           end
  327.         end
  328.       end
  329.       file:close()
  330.       return true
  331.     else
  332.       return false, filename..": "..loc.CANNOT_OPEN_ERROR
  333.     end
  334.   else
  335.     return false, filename..": "..loc.FILE_NOT_FOUND_ERROR
  336.   end
  337. end
  338.  
  339.  
  340. -- ============================================== B U T T O N S ============================================== --
  341. local Button = {}
  342. Button.__index = Button
  343. function Button.new(func, x, y, text, fore, back, width, nu)
  344.   self = setmetatable({}, Button)
  345.  
  346.   self.form = '[ '
  347.   if width == nil then width = 0
  348.     else width = (width - unicode.len(text))-4 end
  349.   for i=1, math.floor(width/2) do
  350.     self.form = self.form.. ' '
  351.   end
  352.   self.form = self.form..text
  353.   for i=1, math.ceil(width/2) do
  354.     self.form = self.form.. ' '
  355.   end
  356.   self.form = self.form..' ]'
  357.  
  358.   self.func = func
  359.  
  360.   self.x = math.floor(x); self.y = math.floor(y)
  361.   self.fore = fore
  362.   self.back = back
  363.   self.visible = true
  364.  
  365.   self.notupdate = nu or false
  366.  
  367.   return self
  368. end
  369. function Button:draw(fore, back)
  370.   if self.visible then
  371.     local fore = fore or self.fore
  372.     local back = back or self.back
  373.     foreground(fore)
  374.     background(back)
  375.     gpu.set(self.x, self.y, self.form)
  376.   end
  377. end
  378. function Button:click(x, y)
  379.   if self.visible then
  380.     if y == self.y then
  381.       if x >= self.x and x < self.x+unicode.len(self.form) then
  382.         self:draw(self.back, self.fore)
  383.         local data = self.func()
  384.         if not self.notupdate then self:draw() end
  385.         return true, data
  386.       end
  387.     end
  388.   end
  389.   return false
  390. end
  391.  
  392. local function buttonNew(buttons, func, x, y, text, fore, back, width, notupdate)
  393.   local button = Button.new(func, x, y, text, fore, back, width, notupdate)
  394.   table.insert(buttons, button)
  395.   return button
  396. end
  397. local function buttonsDraw(buttons)
  398.   for i=1, #buttons do
  399.     buttons[i]:draw()
  400.   end
  401. end
  402. local function buttonsClick(buttons, x, y)
  403.   for i=1, #buttons do
  404.     local ok, data = buttons[i]:click(x, y)
  405.     if ok then return data end
  406.   end
  407.   return nil
  408. end
  409.  
  410.  
  411. -- ============================================ T E X T B O X E S ============================================ --
  412. local Textbox = {}
  413. Textbox.__index = Textbox
  414. function Textbox.new(validator, func, x, y, width, value, defValue)
  415.   self = setmetatable({}, Textbox)
  416.   self.x = math.floor(x); self.y = math.floor(y)
  417.   self.width = width or BUTTONW
  418.   self.valueWidth = self.width - 3
  419.   self.form = '>' .. string.rep(' ', self.width - 1)
  420.   self.validator = validator
  421.   self.func = func
  422.   self.value = tostring(value)
  423.   self.defValue = tostring(defValue)
  424.   self.visible = true
  425.   self.active = false
  426.   return self
  427. end
  428. function Textbox:renderForm()
  429.   foreground(color.fore)
  430.   background(self.active and color.info or color.lightgray)
  431.   gpu.set(self.x, self.y, self.form)
  432. end
  433. function Textbox:renderValue(value)
  434.   foreground(color.fore)
  435.   background(self.active and color.info or color.lightgray)
  436.   local value = value or self.value
  437.   if unicode.len(value) > self.valueWidth then
  438.     value = string.sub(value, -self.valueWidth)
  439.   end
  440.   if self.active then value = value .. '_' end
  441.   gpu.set(self.x+2, self.y, tostring(value))
  442. end
  443. function Textbox:draw()
  444.   if self.visible then
  445.     self:renderForm()
  446.     if not self.active and (unicode.len(self.value) == 0) then
  447.       self:renderValue(self.defValue)
  448.     else
  449.       self:renderValue()
  450.     end
  451.   end
  452. end
  453. function Textbox:click(x, y)
  454.   if self.visible then
  455.     if y == self.y then
  456.       if x >= self.x and x < self.x+self.width then
  457.         -- textbox captures the "focus"
  458.         self.active = true
  459.         self:draw()
  460.         local value = self.value
  461.         while true do
  462.           local name, a, char, code = event.pull()
  463.           if name == 'key_down' then
  464.             if char > 30 then
  465.               local letter = unicode.char(char)
  466.               value = value .. letter
  467.               self:renderValue(value)
  468.             elseif code == keys.BACKSPACE then
  469.               if unicode.len(value) > 0 then
  470.                 value = unicode.sub(value, 1, -2)
  471.                 self:renderForm()
  472.                 self:renderValue(value)
  473.               end
  474.             elseif code == keys.ENTER then break end
  475.           elseif name == 'touch' then break end
  476.         end
  477.         -- textbox loses the "focus"
  478.         self.active = false
  479.         if self.validator(value) then
  480.           self.value = value
  481.           self.func(value)
  482.         end
  483.         self:draw()
  484.         return true
  485.       end
  486.     end
  487.   end
  488.   return false
  489. end
  490.  
  491. local function textboxNew(textboxes, validator, func, x, y, width, value, defValue)
  492.   textbox = Textbox.new(validator, func, x, y, width, value, defValue)
  493.   table.insert(textboxes, textbox)
  494.   return textbox
  495. end
  496. local function textboxesDraw(textboxes)
  497.   for i=1, #textboxes do
  498.     textboxes[i]:draw()
  499.   end
  500. end
  501. local function textboxesClick(textboxes, x, y)
  502.   for i=1, #textboxes do
  503.     textboxes[i]:click(x, y)
  504.   end
  505. end
  506.  
  507.  
  508. -- ============================================= G R A P H I C S ============================================= --
  509. local gridLine1, gridLine2, gridLine1s, gridLine2s = nil, nil, nil, nil
  510. local strLine = "+"
  511. local colorCursorY, colorCursorWidth = 8, 8
  512. local function initGraphics()
  513.   -- grid prefabs
  514.   if FULLSIZE then gridLine1 = string.rep("██  ", HOLOW/2)
  515.   else
  516.     gridLine1 = string.rep("▀", HOLOW/2)
  517.     gridLine2 = string.rep("▄", HOLOW/2)
  518.     gridLine1s = string.rep("▀", HOLOH/2)
  519.     gridLine2s = string.rep("▄", HOLOH/2)
  520.   end
  521.   -- lines prefabs
  522.   for i=1, WIDTH do
  523.     strLine = strLine..'-'
  524.   end
  525.   -- palette cursor params
  526.   if not FULLSIZE then
  527.     colorCursorY, colorCursorWidth = 1, 7
  528.   end
  529. end
  530.  
  531. -- draw a line
  532. local function line(x1, x2, y)
  533.   gpu.set(x1,y,string.sub(strLine, 1, x2-x1))
  534.   gpu.set(x2,y,'+')
  535. end
  536.  
  537. -- draw a frame
  538. local function frame(x1, y1, x2, y2, caption, nobottom)
  539.   line(x1, x2, y1)
  540.   if not nobottom then line(x1, x2, y2) end
  541.   if caption ~= nil then
  542.     gpu.set(x1 + math.ceil((x2-x1)/2) - math.ceil(unicode.len(caption)/2), y1, caption)
  543.   end
  544. end
  545.  
  546. -- draw a grid
  547. local function drawGrid(x, y)
  548.   foreground(color.gray)
  549.   background(color.back)
  550.   gpu.fill(0, y, MENUX, HOLOW, ' ')
  551.   if FULLSIZE then
  552.     for i = 0, view.height - 1 do
  553.       gpu.set(x + (i%2)*2, y + i, gridLine1)
  554.     end
  555.     if view.height + y < HEIGHT then
  556.       foreground(color.fore)
  557.       line(1, MENUX-1, y+HOLOH)
  558.     end
  559.   else
  560.     for i = 0, view.height - 1 do
  561.       if view == TOP then
  562.         if i%2 == 0 then gpu.set(x + i, y, gridLine1, true)
  563.         else gpu.set(x+i, y, gridLine2, true) end
  564.       else
  565.         if i%2 == 0 then gpu.set(x + i, y, gridLine1s, true)
  566.         else gpu.set(x+i, y, gridLine2s, true) end
  567.       end
  568.     end
  569.   end
  570. end
  571.  
  572. -- draw a colored rectangle
  573. local function drawRect(x, y, fill)
  574.   foreground(color.fore)
  575.   background(color.gray)
  576.   gpu.set(x, y,   "╓──────╖")
  577.   gpu.set(x, y+1, "║      ║")
  578.   gpu.set(x, y+2, "╙──────╜")
  579.   foreground(fill)
  580.   gpu.set(x+2, y+1, "████")
  581. end
  582. local function drawSmallRect(x, y, fill)
  583.   foreground(color.fore)
  584.   gpu.set(x, y,   "╓─────╖")
  585.   gpu.set(x, y+1, "║     ║")
  586.   gpu.set(x, y+2, "╙─────╜")
  587.   foreground(fill)
  588.   gpu.set(x+2, y+1, "███")
  589. end
  590.  
  591. -- draw palette selection menu
  592. local function drawPaletteFrame()
  593.   foreground(color.fore)
  594.   background(color.back)
  595.   if FULLSIZE then
  596.     frame(MENUX, 3, WIDTH-2, 16, "[ "..loc.PALETTE_FRAME.." ]", true)
  597.     for i=0, 3 do
  598.       drawRect(MENUX+1+i*colorCursorWidth, 5, hexcolortable[i])
  599.     end
  600.     foreground(0xFF0000); gpu.set(MENUX+1, 10, "R:")
  601.     foreground(0x00FF00); gpu.set(MENUX+1, 11, "G:")
  602.     foreground(0x0000FF); gpu.set(MENUX+1, 12, "B:")
  603.   else
  604.     for i=0, 3 do
  605.       drawSmallRect(MENUX+1+i*colorCursorWidth, 2, hexcolortable[i])
  606.     end
  607.     foreground(0xFF0000); gpu.set(MENUX+1, 5, "R:")
  608.     foreground(0x00FF00); gpu.set(MENUX+11, 5, "G:")
  609.     foreground(0x0000FF); gpu.set(MENUX+21, 5, "B:")
  610.   end
  611. end
  612. -- draw and move palette selector
  613. local function drawColorCursor(force)
  614.   if force or brush.moving then
  615.     foreground(color.fore)
  616.     background(color.back)
  617.     if FULLSIZE then gpu.set(MENUX+2+brush.cx, colorCursorY, "      ")
  618.     else gpu.set(MENUX+2+brush.cx, colorCursorY, "-----") end
  619.    
  620.     if brush.moving then
  621.       if brush.x ~= brush.color * colorCursorWidth then brush.x = brush.color*colorCursorWidth end
  622.       if brush.cx < brush.x then brush.cx = brush.cx + 1
  623.       elseif brush.cx > brush.x then brush.cx = brush.cx - 1
  624.       else brush.moving = false end
  625.     end
  626.    
  627.     if FULLSIZE then
  628.       background(color.lightgray)
  629.       gpu.set(MENUX+2+brush.cx, colorCursorY, ":^^^^:")
  630.     else gpu.set(MENUX+2+brush.cx, colorCursorY, ":vvv:") end
  631.   end
  632. end
  633. local function drawLayerFrame()
  634.   foreground(color.fore)
  635.   background(color.back)
  636.   if FULLSIZE then
  637.     frame(MENUX, 16, WIDTH-2, 28, "[ "..loc.VIEWPORT_FRAME.." ]", true)
  638.     gpu.set(MENUX+13, 18, loc.LAYER_LABEL)
  639.     gpu.set(MENUX+1, 23, loc.GHOST_LAYER_LABEL)
  640.   else
  641.     gpu.set(MENUX+1, 8, loc.LAYER_LABEL)
  642.   end
  643. end
  644. local function drawUtilsFrame()
  645.   foreground(color.fore)
  646.   background(color.back)
  647.   frame(MENUX, 28, WIDTH-2, 36, "[ "..loc.UTILS_FRAME.." ]")
  648. end
  649.  
  650. local function mainScreen()
  651.   foreground(color.fore)
  652.   background(color.back)
  653.   term.clear()
  654.   frame(1,1, WIDTH, HEIGHT, "{ Hologram Editor }", not FULLSIZE)
  655.   -- "canvas"
  656.   drawGrid(GRIDX, GRIDY)
  657.  
  658.   drawPaletteFrame()
  659.   drawLayerFrame()
  660.   drawUtilsFrame()
  661.  
  662.   drawColorCursor(true)
  663.   buttonsDraw(buttons)
  664.   textboxesDraw(textboxes)
  665.  
  666.   -- "about"
  667.   foreground(color.info)
  668.   background(color.gray)
  669.   if FULLSIZE then
  670.     gpu.set(MENUX+3, HEIGHT-11, "   Hologram Editor  v0.7.1   ")
  671.     foreground(color.fore)
  672.     gpu.set(MENUX+3, HEIGHT-10, "            * * *            ")
  673.     gpu.set(MENUX+3, HEIGHT-8,  "  Totoro  (aka MoonlightOwl) ")
  674.     gpu.set(MENUX+3, HEIGHT-7,  "            * * *            ")
  675.     gpu.set(MENUX+3, HEIGHT-5,  "       computercraft.ru      ")
  676.     foreground(color.lightlightgray)
  677.     gpu.set(MENUX+3, HEIGHT-9,  " "..loc.PROGRAMMERS_LABEL..string.rep(' ', 28-unicode.len(loc.PROGRAMMERS_LABEL)))
  678.     gpu.set(MENUX+3, HEIGHT-6,  " "..loc.CONTACT_LABEL..string.rep(' ', 28-unicode.len(loc.CONTACT_LABEL)))
  679.     foreground(color.fore)
  680.     background(color.back)
  681.     gpu.set(MENUX+1, HEIGHT-2, loc.EXIT_LABEL)
  682.   else
  683.     gpu.set(MENUX+1, HEIGHT-2,  "  Totoro  computercraft.ru  ")
  684.     foreground(color.fore)
  685.     background(color.back)
  686.     gpu.set(MENUX+1, HEIGHT, loc.EXIT_LABEL)
  687.   end
  688. end
  689.  
  690.  
  691. -- ============================================= M E S S A G E S ============================================= --
  692. local function showMessage(text, caption, textcolor)
  693.   local caption = '[ '..caption..' ]'
  694.   local x = MENUX/2 - unicode.len(text)/2 - 4
  695.   local y = HEIGHT/2 - 2
  696.   foreground(color.fore)
  697.   background(color.back)
  698.   gpu.fill(x, y, unicode.len(text)+9, 5, ' ')
  699.   frame(x, y, x+unicode.len(text)+8, y+4, caption)
  700.   foreground(textcolor)
  701.   gpu.set(x+4,y+2, text)
  702.   -- "canvas" must be rerendered
  703.   repaint = true
  704. end
  705.  
  706.  
  707. -- =============================================== L A Y E R S =============================================== --
  708. local function project(x, y, layer, view)
  709.   if view == TOP then
  710.     return x, layer, y
  711.   elseif view == FRONT then
  712.     return x, HOLOH-y+1, layer
  713.   else
  714.     return layer, HOLOH-y+1, x
  715.   end
  716. end
  717. local function getVoxelColor(x, y, z, grid)
  718.   local voxel = get(x, y, z)
  719.   if voxel ~= 0 then return hexcolortable[voxel]
  720.   elseif grid then return color.gray
  721.   else return color.back end
  722. end
  723. local function drawVoxel(sx, sy, nogrid)
  724.   if FULLSIZE then
  725.     local voxel = get(project(sx, sy, layer, view))
  726.     local dx = (GRIDX-2) + sx*2
  727.     local dy = (GRIDY-1) + sy
  728.     if voxel ~= 0 then
  729.       foreground(hexcolortable[voxel])
  730.       gpu.set(dx, dy, "██")
  731.     else
  732.       local ghost = get(project(sx, sy, ghost_layer, view))
  733.       if ghost ~= 0 then
  734.         foreground(darkhexcolors[ghost])
  735.         gpu.set(dx, dy, "░░")
  736.       elseif not nogrid then
  737.         if (sx+sy)%2 == 0 then foreground(color.gray)
  738.         else foreground(color.back) end
  739.         gpu.set(dx, dy, "██")
  740.       end
  741.     end
  742.   else
  743.     local sxUp, syUp = sx, sy
  744.     if syUp%2 == 0 then syUp = syUp-1 end
  745.     local sxDown, syDown = sxUp, syUp + 1
  746.     local dx, dy = (GRIDX-1) + sxUp, (GRIDY-1) + math.ceil(syUp/2)
  747.     local a, b, c = project(sxUp, syUp, layer, view)
  748.     foreground(getVoxelColor(a, b, c, ((sxUp+syUp)%2 == 0)))
  749.     a, b, c = project(sxDown, syDown, layer, view)
  750.     background(getVoxelColor(a, b, c, ((sxDown+syDown)%2 == 0)))
  751.     gpu.set(dx, dy, "▀")
  752.   end
  753. end
  754.  
  755. function drawLayer()
  756.   drawGrid(GRIDX, GRIDY)
  757.   local step
  758.   if FULLSIZE then step = 1 else step = 2 end
  759.   for x = 1, view.width do
  760.     for y = 1, view.height, step do drawVoxel(x, y, true) end
  761.   end
  762.   -- no need to rerender the screen
  763.   repaint = false
  764. end
  765. local function fillLayer(value)
  766.   local value = value or brush.color
  767.   for x = 1, view.width do
  768.     for y = 1, view.height do
  769.       local vx, vy, vz = project(x, y, layer, view)
  770.       set(vx, vy, vz, value)
  771.     end
  772.   end
  773.   drawLayer()
  774. end
  775. local function clearLayer()
  776.   fillLayer(0)
  777. end
  778.  
  779.  
  780. -- ==================================== G U I   F U N C T I O N A L I T Y ==================================== --
  781. local function exit() running = false end
  782.  
  783. local function nextGhost()
  784.   if ghost_layer_below then
  785.     ghost_layer_below = false
  786.     if ghost_layer < view.depth then
  787.       ghost_layer = layer + 1
  788.     else ghost_layer = view.depth end
  789.     drawLayer()
  790.   else  
  791.     if ghost_layer < view.depth then
  792.       ghost_layer = ghost_layer + 1
  793.       drawLayer()
  794.     end
  795.   end
  796.   tb_ghostlayer.value = '>'; tb_ghostlayer:draw()
  797. end
  798. local function prevGhost()
  799.   if not ghost_layer_below then
  800.     ghost_layer_below = true
  801.     if layer > 1 then
  802.       ghost_layer = layer - 1
  803.     else ghost_layer = 1 end
  804.     drawLayer()
  805.   else
  806.     if ghost_layer > 1 then
  807.       ghost_layer = ghost_layer - 1
  808.       drawLayer()
  809.     end
  810.   end
  811.   tb_ghostlayer.value = '<'; tb_ghostlayer:draw()
  812. end
  813. local function setGhostLayer(value)
  814.   local n = tonumber(value)
  815.   if n == nil or n < 1 or n > view.depth then return false end
  816.   ghost_layer = n
  817.   drawLayer()
  818.   return true
  819. end
  820. local function moveGhost()
  821.   if ghost_layer_below then
  822.     if layer > 1 then ghost_layer = layer - 1
  823.     else ghost_layer = 1 end
  824.   else
  825.     if layer < view.depth then ghost_layer = layer + 1
  826.     else ghost_layer = view.depth end
  827.   end
  828. end
  829.  
  830. local function nextLayer()
  831.   if layer < view.depth then
  832.     layer = layer + 1
  833.     tb_layer.value = layer
  834.     tb_layer:draw()
  835.     moveGhost()
  836.     drawLayer()
  837.   end
  838. end
  839. local function prevLayer()
  840.   if layer > 1 then
  841.     layer = layer - 1
  842.     tb_layer.value = layer
  843.     tb_layer:draw()
  844.     moveGhost()
  845.     drawLayer()
  846.   end
  847. end
  848. local function setLayer(value)
  849.   local n = tonumber(value)
  850.   if n == nil or n < 1 or n > view.depth then return false end
  851.   layer = n
  852.   moveGhost()
  853.   drawLayer()
  854.   tb_layer.value = layer
  855.   tb_layer:draw()
  856.   return true
  857. end
  858.  
  859. local function setFilename(str)
  860.   return str ~= nil and str ~= '' and unicode.len(str) < 30
  861. end
  862.  
  863. local function changeColor(rgb, value)
  864.   if value == nil then return false end
  865.   n = tonumber(value)
  866.   if n == nil or n < 0 or n > 255 then return false end
  867.   -- saving data to the table
  868.   colortable[brush.color][rgb] = n
  869.   setHexColor(brush.color, colortable[brush.color][1],
  870.                            colortable[brush.color][2],
  871.                            colortable[brush.color][3])
  872.   -- refresh colors palette
  873.   drawPaletteFrame()
  874.   return true
  875. end
  876. local function changeRed(value) return changeColor(1, value) end
  877. local function changeGreen(value) return changeColor(2, value) end
  878. local function changeBlue(value) return changeColor(3, value) end
  879.  
  880. local function moveSelector(num)
  881.   if num == 0 and brush.color ~= 0 then
  882.     tb_red.visible = false
  883.     tb_green.visible = false
  884.     tb_blue.visible = false
  885.     background(color.back)
  886.     if FULLSIZE then
  887.       gpu.fill(MENUX+3, 10, 45, 3, ' ')
  888.     else
  889.       gpu.set(MENUX+3, 5, '      ')
  890.       gpu.set(MENUX+13, 5, '      ')
  891.       gpu.set(MENUX+23, 5, '      ')
  892.     end
  893.   elseif num ~= 0 and brush.color == 0 then
  894.     tb_red.visible = true; tb_red:draw()
  895.     tb_green.visible = true; tb_green:draw()
  896.     tb_blue.visible = true; tb_blue:draw()
  897.   end
  898.   brush.color = num
  899.   brush.moving = true
  900.   tb_red.value = colortable[num][1]; tb_red:draw()
  901.   tb_green.value = colortable[num][2]; tb_green:draw()
  902.   tb_blue.value = colortable[num][3]; tb_blue:draw()
  903. end
  904.  
  905. local function setView(value, norefresh)
  906.   view = value
  907.   if layer > view.depth then layer = view.depth end
  908.   if not norefresh then drawLayer() end
  909. end
  910. local function setTopView(norefresh) setView(TOP, norefresh) end
  911. local function setFrontView() setView(FRONT) end
  912. local function setSideView() setView(SIDE) end
  913.  
  914. local function drawHologram()
  915.   -- check for a projector availability
  916.   local projector = trytofind('hologram')
  917.   if projector ~= nil then
  918.     local depth = projector.maxDepth()
  919.     -- clean him up
  920.     projector.clear()
  921.     -- send the palette
  922.     if depth == 2 then
  923.       for i=1, 3 do
  924.         projector.setPaletteColor(i, hexcolortable[i])
  925.       end
  926.     else
  927.       projector.setPaletteColor(1, hexcolortable[1])
  928.     end
  929.     -- send the data
  930.     for x=1, HOLOW do
  931.       for y=1, HOLOH do
  932.         for z=1, HOLOW do
  933.           n = get(x,y,z)
  934.           if n ~= 0 then
  935.             if depth == 2 then
  936.               projector.set(x,y,z,n)
  937.             else
  938.               projector.set(x,y,z,1)
  939.             end
  940.           end
  941.         end
  942.       end      
  943.     end
  944.   else
  945.     showMessage(loc.PROJECTOR_UNAVAILABLE_MESSAGE, loc.ERROR_CAPTION, color.error)
  946.   end
  947. end
  948.  
  949. local function newHologram()
  950.   holo = {}
  951.   drawLayer()
  952. end
  953.  
  954. local function saveHologram()
  955.   local filename = tb_file.value
  956.   if filename ~= loc.FILE_REQUEST then
  957.     -- show a warning
  958.     showMessage(loc.SAVING_MESSAGE, loc.WARNING_CAPTION, color.gold)
  959.     local compressed = true
  960.     -- add our 'brand' file extensions =)
  961.     if string.sub(filename, -3) == '.3d' then compressed = false
  962.     elseif string.sub(filename, -4) ~= '.3dx' then
  963.       filename = filename..'.3dx'
  964.     end
  965.     -- save
  966.     local ok, message = save(filename, compressed)
  967.     if ok then
  968.       showMessage(loc.SAVED_MESSAGE, loc.DONE_CAPTION, color.gold)
  969.     else
  970.       showMessage(message, loc.ERROR_CAPTION, color.error)
  971.     end
  972.   end
  973. end
  974.  
  975. local function loadHologram()
  976.   local filename = tb_file.value
  977.   if filename ~= loc.FILE_REQUEST then
  978.     -- show a warning
  979.     showMessage(loc.LOADING_MESSAGE, loc.WARNING_CAPTION, color.gold)
  980.     local compressed = nil
  981.     -- add our 'brand' file extensions =)
  982.     if string.sub(filename, -3) == '.3d' then compressed = false
  983.     elseif string.sub(filename, -4) == '.3dx' then compressed = true end
  984.     -- load
  985.     local ok, message = nil, nil
  986.     if compressed ~= nil then
  987.       ok, message = load(filename, compressed)
  988.     else
  989.       -- if no file extension vas specified, try both alternately
  990.       ok, message = load(filename..'.3dx', true)
  991.       if not ok then
  992.         ok, message = load(filename..'.3d', false)
  993.       end
  994.     end
  995.     if ok then
  996.       -- refresh textboxes
  997.       tb_red.value = colortable[brush.color][1]; tb_red:draw()
  998.       tb_green.value = colortable[brush.color][2]; tb_green:draw()
  999.       tb_blue.value = colortable[brush.color][3]; tb_blue:draw()
  1000.       -- refresh the palette
  1001.       drawPaletteFrame()
  1002.       -- reset the viewport
  1003.       setTopView(true)
  1004.       setLayer(1)
  1005.     else
  1006.       showMessage(message, loc.ERROR_CAPTION, color.error)
  1007.     end
  1008.   end
  1009. end
  1010.  
  1011.  
  1012. -- =========================================== M A I N   C Y C L E =========================================== --
  1013. -- initialization
  1014. -- check screen resolution; you must have tier 2/3 GPU and tier 3 monitor to work
  1015. if HEIGHT < HOLOW/2 then
  1016.   error(loc.TOO_LOW_RESOLUTION_ERROR)
  1017. elseif HEIGHT < HOLOW+2 then
  1018.   com.screen.setPrecise(true)
  1019.   if not com.screen.isPrecise() then error(loc.TOO_LOW_SCREEN_TIER) end
  1020.   FULLSIZE = false
  1021.   MENUX = HOLOW + 2
  1022.   color.gray = color.lightgray
  1023.   GRIDX = 1
  1024.   GRIDY = 2
  1025.   BUTTONW = 9
  1026. else
  1027.   com.screen.setPrecise(false)
  1028.   WIDTH = HOLOW*2 + 40
  1029.   HEIGHT = HOLOW + 2
  1030. end
  1031. gpu.setResolution(WIDTH, HEIGHT)
  1032. foreground(color.fore)
  1033. background(color.back)
  1034.  
  1035. -- set default palette
  1036. colortable = {{255, 0, 0}, {0, 255, 0}, {0, 102, 255}}
  1037. colortable[0] = {0, 0, 0}  -- eraser
  1038. for i=0, 3 do setHexColor(i, colortable[i][1], colortable[i][2], colortable[i][3]) end
  1039.  
  1040. initGraphics()
  1041.  
  1042. -- generate interface
  1043. if FULLSIZE then
  1044.   buttonNew(buttons, exit, WIDTH-BUTTONW-2, HEIGHT-2, loc.EXIT_BUTTON, color.back, color.error, BUTTONW, true)
  1045.   buttonNew(buttons, drawLayer, MENUX+11, 14, loc.REFRESH_BUTTON, color.back, color.gold, BUTTONW)
  1046.   buttonNew(buttons, prevLayer, MENUX+1, 19, '-', color.fore, color.info, 5)
  1047.   buttonNew(buttons, nextLayer, MENUX+7, 19, '+', color.fore, color.info, 5)
  1048.   buttonNew(buttons, setTopView, MENUX+1, 21, loc.TOP_BUTTON, color.fore, color.info, 10)
  1049.   buttonNew(buttons, setFrontView, MENUX+12, 21, loc.FRONT_BUTTON, color.fore, color.info, 10)
  1050.   buttonNew(buttons, setSideView, MENUX+24, 21, loc.SIDE_BUTTON, color.fore, color.info, 9)
  1051.  
  1052.   buttonNew(buttons, prevGhost, MENUX+1, 24, loc.BELOW_BUTTON, color.fore, color.info, 6)
  1053.   buttonNew(buttons, nextGhost, MENUX+10, 24, loc.ABOVE_BUTTON, color.fore, color.info, 6)
  1054.  
  1055.   buttonNew(buttons, clearLayer, MENUX+1, 26, loc.CLEAR_BUTTON, color.fore, color.info, BUTTONW)
  1056.   buttonNew(buttons, fillLayer, MENUX+2+BUTTONW, 26, loc.FILL_BUTTON, color.fore, color.info, BUTTONW)
  1057.  
  1058.   buttonNew(buttons, drawHologram, MENUX+9, 30, loc.TO_PROJECTOR, color.back, color.gold, 16)
  1059.   buttonNew(buttons, saveHologram, MENUX+1, 33, loc.SAVE_BUTTON, color.fore, color.help, BUTTONW)
  1060.   buttonNew(buttons, loadHologram, MENUX+8+BUTTONW, 33, loc.LOAD_BUTTON, color.fore, color.info, BUTTONW)
  1061.   buttonNew(buttons, newHologram, MENUX+1, 35, loc.NEW_FILE_BUTTON, color.fore, color.info, BUTTONW)
  1062. else
  1063.   buttonNew(buttons, exit, WIDTH-BUTTONW-1, HEIGHT, loc.EXIT_BUTTON, color.back, color.error, BUTTONW, true)
  1064.   buttonNew(buttons, drawLayer, MENUX+9, 6, loc.REFRESH_BUTTON, color.back, color.gold, BUTTONW)
  1065.   buttonNew(buttons, prevLayer, MENUX+1, 9, '-', color.fore, color.info, 5)
  1066.   buttonNew(buttons, nextLayer, MENUX+7, 9, '+', color.fore, color.info, 5)
  1067.   buttonNew(buttons, setTopView, MENUX+1, 11, loc.TOP_BUTTON, color.fore, color.info, 8)
  1068.   buttonNew(buttons, setFrontView, MENUX+10, 12, loc.FRONT_BUTTON, color.fore, color.info, 8)
  1069.   buttonNew(buttons, setSideView, MENUX+20, 13, loc.SIDE_BUTTON, color.fore, color.info, 8)
  1070.  
  1071.   buttonNew(buttons, clearLayer, MENUX+1, 15, loc.CLEAR_BUTTON, color.fore, color.info, BUTTONW)
  1072.   buttonNew(buttons, fillLayer, MENUX+14, 15, loc.FILL_BUTTON, color.fore, color.info, BUTTONW)
  1073.  
  1074.   buttonNew(buttons, drawHologram, MENUX+7, 17, loc.TO_PROJECTOR, color.back, color.gold, 16)
  1075.   buttonNew(buttons, saveHologram, MENUX+1, 20, loc.SAVE_BUTTON, color.fore, color.help, BUTTONW)
  1076.   buttonNew(buttons, loadHologram, MENUX+16, 20, loc.LOAD_BUTTON, color.fore, color.info, BUTTONW)
  1077.   buttonNew(buttons, newHologram, MENUX+1, 21, loc.NEW_FILE_BUTTON, color.fore, color.info, BUTTONW)
  1078. end
  1079.  
  1080. local function isNumber(value)
  1081.   return tonumber(value) ~= nil
  1082. end
  1083. local function correctLayer(value)
  1084.   local n = tonumber(value)
  1085.   return n ~= nil and n > 0 and n <= view.depth
  1086. end
  1087.  
  1088. tb_red, tb_green, tb_blue, tb_layer, tb_ghostlayer, tb_file = nil, nil, nil, nil, nil, nil
  1089. if FULLSIZE then
  1090.   tb_red = textboxNew(textboxes, isNumber, changeRed, MENUX+5, 10, WIDTH-MENUX-7, '255')
  1091.   tb_green = textboxNew(textboxes, isNumber, changeGreen, MENUX+5, 11, WIDTH-MENUX-7, '0')
  1092.   tb_blue = textboxNew(textboxes, isNumber, changeBlue, MENUX+5, 12, WIDTH-MENUX-7, '0')
  1093.   tb_layer = textboxNew(textboxes, correctLayer, setLayer, MENUX+13, 19, WIDTH-MENUX-15, '1')
  1094.   tb_ghostlayer = textboxNew(textboxes, correctLayer, setGhostLayer, MENUX+19, 24, WIDTH-MENUX-21, '')
  1095.   tb_file = textboxNew(textboxes, function() return true end, setFilename, MENUX+1, 32, WIDTH-MENUX-3, '', loc.FILE_REQUEST)
  1096. else
  1097.   tb_red = textboxNew(textboxes, isNumber, changeRed, MENUX+3, 5, 6, '255')
  1098.   tb_green = textboxNew(textboxes, isNumber, changeGreen, MENUX+13, 5, 6, '0')
  1099.   tb_blue = textboxNew(textboxes, isNumber, changeBlue, MENUX+23, 5, 6, '0')
  1100.   tb_layer = textboxNew(textboxes, correctLayer, setLayer, MENUX+13, 9, WIDTH-MENUX-14, '1')
  1101.   tb_file = textboxNew(textboxes, function() return true end, setFilename, MENUX+1, 19, WIDTH-MENUX-2, '', loc.FILE_REQUEST)
  1102. end
  1103.  
  1104. mainScreen()
  1105. moveSelector(1)
  1106.  
  1107. local function delay(active) if active then return 0.02 else return 2.0 end end
  1108.  
  1109. while running do
  1110.   local name, add, x, y, button = event.pull(delay(brush.moving))
  1111.  
  1112.   if name == 'key_down' then
  1113.     -- if 'Q' was pressed - the quit app
  1114.     if y == keys.EXIT then
  1115.       exit()
  1116.     elseif y == keys.ERASER then
  1117.       moveSelector(0)
  1118.     elseif y >= 2 and y <= 4 then
  1119.       moveSelector(y - 1)
  1120.     elseif y == keys.CLEAR then
  1121.       clearLayer()
  1122.     end
  1123.   elseif name == 'touch' or name == 'drag' then
  1124.   -- rerender the screen after message box
  1125.     if repaint then drawLayer()
  1126.     else
  1127.       if name == 'touch' then
  1128.         -- check the GUI
  1129.         buttonsClick(buttons, math.ceil(x), math.ceil(y))
  1130.         textboxesClick(textboxes, math.ceil(x), math.ceil(y))
  1131.         -- select a color
  1132.         if x > MENUX+1 and x < MENUX+37 then
  1133.           if FULLSIZE then
  1134.             if y > 4 and y < 8 then
  1135.               moveSelector(math.floor((x-MENUX-1)/colorCursorWidth))
  1136.             end
  1137.           else
  1138.             if y > 1 and y < 4 and x < WIDTH-2 then
  1139.               moveSelector(math.floor((x-MENUX-1)/colorCursorWidth))
  1140.             end
  1141.           end
  1142.         end
  1143.       end
  1144.      
  1145.       -- "render"
  1146.       local dx, dy
  1147.       if FULLSIZE then
  1148.         if x >= GRIDX and x < GRIDX + view.width*2 then
  1149.           if y >= GRIDY and y < GRIDY + view.height then
  1150.             dx, dy = math.floor((x-GRIDX)/2)+1, math.floor(y-GRIDY+1)
  1151.           end
  1152.         end
  1153.       else
  1154.         if x >= (GRIDX - 1) and x <= GRIDX + view.width then
  1155.           if y >= (GRIDY - 1) and y <= GRIDY + view.height/2 then
  1156.             dx, dy = math.floor(x - GRIDX + 2), math.floor((y - GRIDY + 1) * 2) + 1
  1157.           end
  1158.         end
  1159.       end
  1160.       if dx ~= nil then
  1161.         local a, b, c = project(dx, dy, layer, view)
  1162.         if button == 0 then set(a, b, c, brush.color)
  1163.         else set(a, b, c, 0) end
  1164.         drawVoxel(dx, dy)
  1165.       end
  1166.     end
  1167.   end
  1168.  
  1169.   drawColorCursor()
  1170. end
  1171.  
  1172. -- finalization
  1173. foreground(0xFFFFFF)
  1174. background(0x000000)
  1175. gpu.setResolution(OLDWIDTH, OLDHEIGHT)
  1176. term.clear()
Add Comment
Please, Sign In to add comment