cc-mek-scada/graphics/elements/pipenet.lua
2023-09-30 11:46:47 -04:00

332 lines
11 KiB
Lua

-- Pipe Graphics Element
local util = require("scada-common.util")
local core = require("graphics.core")
local element = require("graphics.element")
---@class pipenet_args
---@field pipes table pipe list
---@field bg? color background color
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field hidden? boolean true to hide on initial draw
---@class _pipe_map_entry
---@field atr boolean align top right (or bottom left for false)
---@field thin boolean thin pipe or not
---@field fg string foreground blit
---@field bg string background blit
-- new pipe network
---@param args pipenet_args
---@return graphics_element element, element_id id
local function pipenet(args)
element.assert(type(args.pipes) == "table", "pipes is a required field")
args.width = 0
args.height = 0
for i = 1, #args.pipes do
local pipe = args.pipes[i] ---@type pipe
local true_w = pipe.w + math.min(pipe.x1, pipe.x2)
local true_h = pipe.h + math.min(pipe.y1, pipe.y2)
if true_w > args.width then args.width = true_w end
if true_h > args.height then args.height = true_h end
end
args.x = args.x or 1
args.y = args.y or 1
if args.bg ~= nil then
args.fg_bg = core.cpair(args.bg, args.bg)
end
-- create new graphics element base object
local e = element.new(args)
-- determine if there are any thin pipes involved
local any_thin = false
for p = 1, #args.pipes do
any_thin = args.pipes[p].thin
if any_thin then break end
end
-- draw all pipes by drawing out lines
local function vector_draw()
for p = 1, #args.pipes do
local pipe = args.pipes[p] ---@type pipe
local x = 1 + pipe.x1
local y = 1 + pipe.y1
local x_step = util.trinary(pipe.x1 >= pipe.x2, -1, 1)
local y_step = util.trinary(pipe.y1 >= pipe.y2, -1, 1)
if pipe.thin then
x_step = util.trinary(pipe.x1 == pipe.x2, 0, x_step)
y_step = util.trinary(pipe.y1 == pipe.y2, 0, y_step)
end
e.w_set_cur(x, y)
local c = core.cpair(pipe.color, e.fg_bg.bkg)
if pipe.align_tr then
-- cross width then height
for i = 1, pipe.w do
if pipe.thin then
if i == pipe.w then
-- corner
if y_step > 0 then
e.w_blit("\x93", c.blit_bkg, c.blit_fgd)
else
e.w_blit("\x8e", c.blit_fgd, c.blit_bkg)
end
else
e.w_blit("\x8c", c.blit_fgd, c.blit_bkg)
end
else
if i == pipe.w and y_step > 0 then
-- corner
e.w_blit(" ", c.blit_bkg, c.blit_fgd)
else
e.w_blit("\x8f", c.blit_fgd, c.blit_bkg)
end
end
x = x + x_step
e.w_set_cur(x, y)
end
-- back up one
x = x - x_step
for _ = 1, pipe.h - 1 do
y = y + y_step
e.w_set_cur(x, y)
if pipe.thin then
e.w_blit("\x95", c.blit_bkg, c.blit_fgd)
else
e.w_blit(" ", c.blit_bkg, c.blit_fgd)
end
end
else
-- cross height then width
for i = 1, pipe.h do
if pipe.thin then
if i == pipe.h then
-- corner
if y_step < 0 then
e.w_blit("\x97", c.blit_bkg, c.blit_fgd)
elseif y_step > 0 then
e.w_blit("\x8d", c.blit_fgd, c.blit_bkg)
else
e.w_blit("\x8c", c.blit_fgd, c.blit_bkg)
end
else
e.w_blit("\x95", c.blit_fgd, c.blit_bkg)
end
else
if i == pipe.h and y_step < 0 then
-- corner
e.w_blit("\x83", c.blit_bkg, c.blit_fgd)
else
e.w_blit(" ", c.blit_bkg, c.blit_fgd)
end
end
y = y + y_step
e.w_set_cur(x, y)
end
-- back up one
y = y - y_step
for _ = 1, pipe.w - 1 do
x = x + x_step
e.w_set_cur(x, y)
if pipe.thin then
e.w_blit("\x8c", c.blit_fgd, c.blit_bkg)
else
e.w_blit("\x83", c.blit_bkg, c.blit_fgd)
end
end
end
end
end
-- draw a particular map cell
---@param map table 2D cell map
---@param x integer x coord
---@param y integer y coord
local function draw_map_cell(map, x, y)
local entry = map[x][y] ---@type _pipe_map_entry already confirmed not false
local char
local invert = false
local function check(cx, cy)
return (map[cx] ~= nil) and (map[cx][cy] ~= nil) and (map[cx][cy] ~= false) and (map[cx][cy].fg == entry.fg)
end
if entry.thin then
if check(x - 1, y) then -- if left
if check(x, y - 1) then -- if above
if check(x + 1, y) then -- if right
if check(x, y + 1) then -- if below
char = util.trinary(entry.atr, "\x91", "\x9d")
invert = entry.atr
else -- not below
char = util.trinary(entry.atr, "\x8e", "\x8d")
end
else -- not right
if check(x, y + 1) then -- if below
char = util.trinary(entry.atr, "\x91", "\x95")
invert = entry.atr
else -- not below
char = util.trinary(entry.atr, "\x8e", "\x85")
end
end
elseif check(x, y + 1) then-- not above, if below
if check(x + 1, y) then -- if right
char = util.trinary(entry.atr, "\x93", "\x9c")
invert = entry.atr
else -- not right
char = util.trinary(entry.atr, "\x93", "\x94")
invert = entry.atr
end
else -- not above, not below
char = "\x8c"
end
elseif check(x + 1, y) then -- not left, if right
if check(x, y - 1) then -- if above
if check(x, y + 1) then -- if below
char = util.trinary(entry.atr, "\x95", "\x9d")
invert = entry.atr
else -- not below
char = util.trinary(entry.atr, "\x8a", "\x8d")
end
else -- not above
if check(x, y + 1) then -- if below
char = util.trinary(entry.atr, "\x97", "\x9c")
invert = entry.atr
else -- not below
char = "\x8c"
end
end
else -- not left, not right
char = "\x95"
invert = entry.atr
end
else
if check(x, y - 1) then -- above
-- not below and (if left or right)
if (not check(x, y + 1)) and (check(x - 1, y) or check(x + 1, y)) then
char = util.trinary(entry.atr, "\x8f", " ")
invert = not entry.atr
else -- not below w/ sides only
char = " "
invert = true
end
elseif check(x, y + 1) then -- not above, if below
-- if left or right
if (check(x - 1, y) or check(x + 1, y)) then
char = "\x83"
invert = true
else -- not left or right
char = " "
invert = true
end
else -- not above, not below
char = util.trinary(entry.atr, "\x8f", "\x83")
invert = not entry.atr
end
end
e.w_set_cur(x, y)
if invert then
e.w_blit(char, entry.bg, entry.fg)
else
e.w_blit(char, entry.fg, entry.bg)
end
end
-- draw all pipes by assembling and marking up a 2D map<br>
-- this is an easy way to check adjacent blocks, which is required to properly draw thin pipes
local function map_draw()
local map = {}
for x = 1, args.width do
table.insert(map, {})
for _ = 1, args.height do table.insert(map[x], false) end
end
-- build map
for p = 1, #args.pipes do
local pipe = args.pipes[p] ---@type pipe
local x = 1 + pipe.x1
local y = 1 + pipe.y1
local x_step = util.trinary(pipe.x1 >= pipe.x2, -1, 1)
local y_step = util.trinary(pipe.y1 >= pipe.y2, -1, 1)
local entry = { atr = pipe.align_tr, thin = pipe.thin, fg = colors.toBlit(pipe.color), bg = e.fg_bg.blit_bkg }
if pipe.align_tr then
-- cross width then height
for _ = 1, pipe.w do
map[x][y] = entry
x = x + x_step
end
x = x - x_step -- back up one
for _ = 1, pipe.h do
map[x][y] = entry
y = y + y_step
end
else
-- cross height then width
for _ = 1, pipe.h do
map[x][y] = entry
y = y + y_step
end
y = y - y_step -- back up one
for _ = 1, pipe.w do
map[x][y] = entry
x = x + x_step
end
end
end
-- render
for x = 1, args.width do
for y = 1, args.height do
if map[x][y] ~= false then draw_map_cell(map, x, y) end
end
end
end
-- element redraw
function e.redraw()
if any_thin then map_draw() else vector_draw() end
end
-- initial draw
e.redraw()
return e.complete()
end
return pipenet