Advertisement
tothe

hologram editor

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