Skip to content

Instantly share code, notes, and snippets.

@nexustix
Last active August 24, 2018 19:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nexustix/088d3970e8e9c912ef9beab5137a71ab to your computer and use it in GitHub Desktop.
Save nexustix/088d3970e8e9c912ef9beab5137a71ab to your computer and use it in GitHub Desktop.
nxdraw - Simple rendering of geometric primitives for OpenComputers
--[[
name: nxdraw
version: 0.1.1
description: Simple rendering of geometric primitives for OpenComputers
author: nexustix
]]--
local nxdraw = {}
nxdraw.__index = nxdraw
local bLookup = {
[1] = 0x1, [2] = 0x8,
[3] = 0x2, [4] = 0x10,
[5] = 0x4, [6] = 0x20,
[7] = 0x40, [8] = 0x80,
["1:1"] = 0x1, ["2:1"] = 0x8,
["1:2"] = 0x2, ["2:2"] = 0x10,
["1:3"] = 0x4, ["2:3"] = 0x20,
["1:4"] = 0x40, ["2:4"] = 0x80,
}
--Convert celldata to Unicode
local function dataToCell(celldata)
local r = 0x2800
for k, v in pairs(celldata) do
if v == true then
r = r + bLookup[k]
end
end
return r
end
--Create new nxdraw
function nxdraw.new(width, height, default)
local self = setmetatable({}, nxdraw)
self.default = default or false
self.canvasCWidth = width
self.canvasCHeight = height
self.canvasPWidth = width * 2
self.canvasPHeight = height * 4
self.screenData = {}
self.dirty = {} -- all cells that have been changed since the last render
self.touched = {} -- all cells that have ben changed do a non-default state
for w=1, self.canvasCWidth do
for h=1, self.canvasCHeight do
local tmp_data = {
["1:1"] = self.default, ["2:1"] = self.default,
["1:2"] = self.default, ["2:2"] = self.default,
["1:3"] = self.default, ["2:3"] = self.default,
["1:4"] = self.default, ["2:4"] = self.default,}
self:setCell(w, h, tmp_data)
end
end
return self
end
function nxdraw.addDirty(self, theDirty)
self.dirty[#self.dirty+1] = theDirty
end
function nxdraw.addTouched(self, theTouched)
end
--Clear touched screenData
function nxdraw.clear(self)
local t = require("term")
for k,v in pairs(self.touched) do
local tmp_data = {
["1:1"] = self.default, ["2:1"] = self.default,
["1:2"] = self.default, ["2:2"] = self.default,
["1:3"] = self.default, ["2:3"] = self.default,
["1:4"] = self.default, ["2:4"] = self.default,}
local w, h = v[1], v[2]
self:setCell(w, h, tmp_data)
self.dirty[tostring(w)..":"..tostring(h)] = {[1]=w, [2]=h}
end
self.touched = {}
end
--Render only dirty cells
function nxdraw.render(self)
local t = require("term")
for k,v in pairs(self.dirty) do
local w, h = v[1], v[2]
local d = self:getCell(w, h)
local c = dataToCell(d)
t.setCursor(w, h)
t.write(utf8.char(c))
end
self.dirty = {}
end
--Clear all screenData
function nxdraw.clearAll(self)
for w=1, self.canvasCWidth do
for h=1, self.canvasCHeight do
local tmp_data = {
["1:1"] = self.default, ["2:1"] = self.default,
["1:2"] = self.default, ["2:2"] = self.default,
["1:3"] = self.default, ["2:3"] = self.default,
["1:4"] = self.default, ["2:4"] = self.default,}
self:setCell(w, h, tmp_data)
end
end
self.touched = {}
end
--Render all the cells
function nxdraw.renderAll(self)
local t = require("term")
for h=1, self.canvasCHeight do
for w=1, self.canvasCWidth do
local d = self:getCell(w, h)
local c = dataToCell(d)
t.setCursor(w,h)
t.write(utf8.char(c))
end
end
self.dirty = {}
end
--Get contents of cell
function nxdraw.getCell(self, cx, cy)
return(self.screenData[tostring(cx)..":"..tostring(cy)])
end
--Set contents of cell
function nxdraw.setCell(self, cx, cy, value)
local cell_key = tostring(cx)..":"..tostring(cy)
self.screenData[cell_key] = value
end
--Get state of pixel
function nxdraw.getPixel(self, xx, yy)
if not (xx <= 0 or yy <= 0 or xx > self.canvasPWidth or yy > self.canvasPHeight) then
local cx = math.ceil(xx / 2)
local cy = math.ceil(yy / 4)
local lx = (xx - ((cx-1)*2))
local ly = (yy - ((cy-1)*4))
return self.screenData[tostring(cx)..":"..tostring(cy)][tostring(lx)..":"..tostring(ly)]
end
return nil
end
--Set state of pixel
function nxdraw.setPixel(self, xx, yy, isVisible)
if not (xx <= 0 or yy <= 0 or xx > self.canvasPWidth or yy > self.canvasPHeight) then
local cx = math.ceil(xx / 2)
local cy = math.ceil(yy / 4)
local lx = (xx - ((cx-1)*2))
local ly = (yy - ((cy-1)*4))
local cell_key = tostring(cx)..":"..tostring(cy)
self.screenData[cell_key][tostring(lx)..":"..tostring(ly)] = isVisible
--self:addDirty({[1]=cx, [2]=cy})
self.dirty[cell_key] = {[1]=cx, [2]=cy}
self.touched[cell_key] = {[1]=cx, [2]=cy}
end
end
--Draw line using Bresenhams line algorithm
function nxdraw.line(self, x1, y1, x2, y2)
local delta_x = x2 - x1
local ix = delta_x > 0 and 1 or -1
delta_x = 2 * math.abs(delta_x)
local delta_y = y2 - y1
local iy = delta_y > 0 and 1 or -1
delta_y = 2 * math.abs(delta_y)
local error = 0
self:setPixel(x1, y1, true)
if delta_x >= delta_y then
error = delta_y - delta_x / 2
while x1 ~= x2 do
if (error > 0) or ((error == 0) and (ix > 0)) then
error = error - delta_x
y1 = y1 + iy
end
error = error + delta_y
x1 = x1 + ix
self:setPixel(x1, y1, true)
end
else
error = delta_x - delta_y / 2
while y1 ~= y2 do
if (error > 0) or ((error == 0) and (iy > 0)) then
error = error - delta_y
x1 = x1 + ix
end
error = error + delta_x
y1 = y1 + iy
self:setPixel(x1, y1, true)
end
end
end
--Draw circle using Midpoint circle algorithm
function nxdraw.circle(self, x0, y0, radius)
local x = radius-1
local y = 0
local dx = 1
local dy = 1
local err = dx - (radius << 1)
while (x >= y) do
self:setPixel(x0 + x, y0 + y, true)
self:setPixel(x0 + y, y0 + x, true)
self:setPixel(x0 - y, y0 + x, true)
self:setPixel(x0 - x, y0 + y, true)
self:setPixel(x0 - x, y0 - y, true)
self:setPixel(x0 - y, y0 - x, true)
self:setPixel(x0 + y, y0 - x, true)
self:setPixel(x0 + x, y0 - y, true)
if (err <= 0) then
y = y + 1
err = err + dy
dy = dy + 2
end
if (err > 0) then
x = x - 1
dx = dx + 2
err = err + (dx - (radius << 1))
end
end
end
--Draw rectangle
function nxdraw.rectangle(self, x,y,w,h)
self:line(x,y, x+w,y)
self:line(x,y, x,y+h)
self:line(x+w,y+h, x+w,y)
self:line(x+w,y+h, x,y+h)
end
--Flood fill 4-way (OC will hate you if you fill huge areas with this)
function nxdraw.fill(self, x, y, sval)
local sval = sval or true
local dval = self:getPixel(x, y)
if (not (dval == nil)) then
if dval ~= sval then
self:setPixel(x, y, sval)
self:fill(x+1, y, sval)
self:fill(x-1, y, sval)
self:fill(x, y+1, sval)
self:fill(x, y-1, sval)
end
end
end
--Vertical line fill
function nxdraw.vfill(self, x, y, sval)
local sval = sval or true
local dval = self:getPixel(x, y)
if (not (dval == nil)) then
if dval ~= sval then
self:setPixel(x, y, sval)
self:vfill(x, y+1, sval)
self:vfill(x, y-1, sval)
end
end
end
--Horizontal line fill
function nxdraw.hfill(self, x, y, sval)
local sval = sval or true
local dval = self:getPixel(x, y)
if (not (dval == nil)) then
if dval ~= sval then
self:setPixel(x, y, sval)
self:hfill(x+1, y, sval)
self:hfill(x-1, y, sval)
end
end
end
--HACK call after render to put text on screen
function nxdraw.text(self, x, y, text)
local t = require("term")
t.setCursor(x, y)
for i = 1, #text do
local c = text:sub(i,i)
t.write(c)
end
end
--Test the thing
--TODO tests now incomplete
local function test()
local tc = nxdraw.new(160,50, false)
tc:line(10,60, 60,65)
tc:line(10,60, 30,10)
tc:line(60,65, 30,10)
tc:circle(100,100, 15)
tc:rectangle(200, 50, 50, 50)
tc:render()
os.sleep(10)
end
--test()
return nxdraw
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment