Skip to content

Instantly share code, notes, and snippets.

@exerro
Last active August 24, 2022 20:04
Show Gist options
  • Save exerro/126bf0850a999d7064f53dc417da614b to your computer and use it in GitHub Desktop.
Save exerro/126bf0850a999d7064f53dc417da614b to your computer and use it in GitHub Desktop.
First 3D rendering demo using CCGL3D
local __localmodules = {}
local __localmodulecache = {}
local __localmodulelines = {}
local __require = require
local function require(module)
local f = __localmodules[module]
if not f then return __require(module) end
local result = __localmodulecache[module]
if result == nil then
__localmodulecache[module] = true
result = f() or true
__localmodulecache[module] = result
end
return result
end
__localmodules['lib.BufferFormat'] = function(...)
local unique = require "lib.unique"
return {
Pos3Idx1 = unique "Pos3Idx1",
Pos3Col1 = unique "Pos3Col1",
Pos3Rgb1 = unique "Pos3Rgb1",
PosIdx3 = unique "PosIdx3",
PosCol3 = unique "PosCol3",
PosRgb3 = unique "PosRgb3",
Pos2Idx1 = unique "Pos2Idx1",
Pos2Col1 = unique "Pos2Idx1",
Pos2Rgb1 = unique "Pos2Rgb1",
PosIdx2 = unique "PosIdx2",
PosCol2 = unique "PosIdx2",
PosRgb2 = unique "PosRgb2",
}
end
__localmodules['lib.TextureFormat'] = function(...)
local unique = require "lib.unique"
return {
Idx1 = unique "Idx1",
Col1 = unique "Col1",
RGB1 = unique "RGB1",
Dpt1 = unique "Dpt1",
BFC1 = unique "BFC1",
}
end
__localmodules['lib.unique'] = function(...)
return function(name)
local unique = {}
return setmetatable(unique, {
__tostring = function() return name end,
__concat = function(a, b)
if a == unique then return name .. b else return a .. name end
end
})
end
end
__localmodules['__main'] = function(...)
local ccgl3d = require "lib.ccgl3d"
print("Using CCGL3D v" .. ccgl3d.version)
local w, h = term.getSize()
local camera = ccgl3d.camera.createPerspective(30)
local dirty_texture = ccgl3d.texture.create(ccgl3d.TextureFormat.BFC1, w, h)
local texture = ccgl3d.texture.createSubpixel(ccgl3d.TextureFormat.Col1, w, h)
local viewport = ccgl3d.viewport.create(texture)
local fallback_table = ccgl3d.fallback_table.create(term)
local buffer = ccgl3d.buffer.create(ccgl3d.BufferFormat.Pos3Col1) {
-- left
-0.5, 0.5, 0.5,
-0.5, 0.5, -0.5,
-0.5, -0.5, -0.5,
colours.lightBlue,
-0.5, 0.5, 0.5,
-0.5, -0.5, -0.5,
-0.5, -0.5, 0.5,
colours.lightBlue,
-- right
0.5, 0.5, 0.5,
0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
colours.red,
0.5, 0.5, 0.5,
0.5, -0.5, 0.5,
0.5, -0.5, -0.5,
colours.red,
-- front
-0.5, 0.5, 0.5,
-0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
colours.blue,
-0.5, 0.5, 0.5,
0.5, -0.5, 0.5,
0.5, 0.5, 0.5,
colours.blue,
-- back
-0.5, 0.5, -0.5,
0.5, -0.5, -0.5,
-0.5, -0.5, -0.5,
colours.yellow,
-0.5, 0.5, -0.5,
0.5, 0.5, -0.5,
0.5, -0.5, -0.5,
colours.yellow,
-- top
0.5, 0.5, -0.5,
-0.5, 0.5, -0.5,
-0.5, 0.5, 0.5,
colours.green,
0.5, 0.5, -0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, 0.5,
colours.green,
-- bottom
0.5, -0.5, -0.5,
-0.5, -0.5, 0.5,
-0.5, -0.5, -0.5,
colours.magenta,
0.5, -0.5, -0.5,
0.5, -0.5, 0.5,
-0.5, -0.5, 0.5,
colours.magenta,
}
-- local bc = #buffer
-- for i = 1, bc do
-- for n = 1, 9 do
-- buffer[bc * n + i] = buffer[i]
-- end
-- end
-- buffer.size = #buffer
-- error(#buffer / 10)
local t0 = os.clock()
local count = 200
while true do
local ft0 = os.clock()
local t = ft0 - t0
local model_draws = 0
ccgl3d.render.clear(texture)
for z = -8, 0 do
for y = -2, 2, 0.5 do
for x = -2, 2, 0.5 do
ccgl3d.render.draw_triangles(buffer, texture, camera, viewport, {
model_transform = ccgl3d.transform
:translate_to(x, y, z - 2)
:rotate_x_to(t + x * 2)
:rotate_z_to(math.sin(t + y * 2))
:rotate_y_to(t * 2 + z * 2)
:scale_to(0.25, 0.25, 0.25),
-- model_transform = ccgl3d.transform:translate_to((i / count * 2) - 1, 0, -1),
-- view_transform = ccgl3d.transform:translate_to(-1, 0, 0),
cull_back_face = true,
})
model_draws = model_draws + 1
end
end
end
local rt = os.clock() - ft0
-- term.setBackgroundColour(colours.black)
-- term.clear()
-- ccgl3d.blit(texture, term, fallback_table)
ccgl3d.blit(texture, term, fallback_table, dirty_texture)
local dt = os.clock() - ft0
local bt = dt - rt
term.setBackgroundColour(colours.black)
term.setTextColour(colours.white)
term.setCursorPos(1, 1)
term.write("total: " .. math.floor(dt * 1000 + 0.5) .. "ms (" .. math.floor(1/dt) .. "fps)")
term.setCursorPos(1, 2)
term.write("blit: " .. math.floor(bt * 1000 + 0.5) .. "ms (" .. math.floor(1/bt) .. "fps)")
term.setCursorPos(1, 3)
term.write("draw: " .. math.floor(rt * 1000 + 0.5) .. "ms (" .. math.floor(1/rt) .. "fps)")
term.setCursorPos(1, 4)
term.write("load: " .. model_draws .. " models, " .. buffer.size / 10 * model_draws .. " triangles")
term.setCursorPos(1, 5)
term.write("size: (" .. w .. ", " .. h .. ") terminal, (" .. texture.width .. ", " .. texture.height .. ") texture")
local e = tostring {}
sleep(0.05)
end
print("~" .. math.floor(count / (os.clock() - t0)) .. "fps")
end
__localmodules['lib.viewport'] = function(...)
local TextureFormat = require "lib.TextureFormat"
local libtexture = require "lib.texture"
local unique = require "lib.unique"
local viewport = {}
viewport.__type = unique "Viewport"
function viewport.create(texture)
assert(type(texture) == "table" and texture.__type == libtexture.__type, "Expected texture")
local vp = {
__type = viewport.__type,
dx = 0,
dy = 0,
di = 0,
width = texture.width,
height = texture.height,
}
return vp
end
return viewport
end
__localmodules['lib.blit'] = function(...)
local TextureFormat = require "lib.TextureFormat"
local libfallbackTable = require "lib.fallback_table"
local libtexture = require "lib.texture"
local math_floor = math.floor
local math_log = math.log
local string_char = string.char
local table_unpack = table.unpack
local SUBPIXEL_WIDTH = 2
local SUBPIXEL_HEIGHT = 3
local SUBPIXEL_SIZE = SUBPIXEL_WIDTH * SUBPIXEL_HEIGHT
local SUBPIXEL_INVERT_THRESHOLD = 2 ^ (SUBPIXEL_SIZE - 1)
local SUBPIXEL_BASE = 128
local colour_lookup = {}
for i = 0, 15 do
colour_lookup[i + 1] = i < 10 and ("0"):byte() + i or ("a"):byte() + i - 10
end
local function blit_idx(texture_data, texture_width, texture_height, term, fallback_table, dirty_texture)
local base_index = 1
local base_index_delta = texture_width * 3
local y = 1
local dirty_index = 1
local rowBg = {}
local rowFg = {}
local rowCh = {}
dirty_texture = dirty_texture or {}
for ty = 1, texture_height, SUBPIXEL_HEIGHT do
local rowIdx = 1
local row_dirty = false
local row_dirty_first_index = 0
local row_dirty_last_index = 0
for txd = 0, texture_width - 1, SUBPIXEL_WIDTH do
local px_index = base_index + txd
local px_index1 = px_index + texture_width
local px_index2 = px_index1 + texture_width
local c0 = texture_data[px_index]
local c1 = texture_data[px_index + 1]
local c2 = texture_data[px_index1]
local c3 = texture_data[px_index1 + 1]
local c4 = texture_data[px_index2]
local c5 = texture_data[px_index2 + 1]
local colours = { c0, c1, c2, c3, c4, c5 }
local totals = { [c0] = 1 }
local max_count0, max_count0_colour = 1, c0
local max_count1, max_count1_colour = 0, max_count0_colour + 1
for i = 2, SUBPIXEL_SIZE do
local c = colours[i]
local total = (totals[c] or 0) + 1
if c == max_count0_colour then
max_count0 = total
elseif c == max_count1_colour then
max_count1 = total
elseif total > max_count1 then
max_count1 = total
max_count1_colour = c
end
if max_count1 > max_count0 then
max_count0, max_count1 = max_count1, max_count0
max_count0_colour, max_count1_colour = max_count1_colour, max_count0_colour
end
totals[c] = total
end
local subpixel_n = SUBPIXEL_BASE
local bg_colour, fg_colour = max_count0_colour, max_count1_colour
for i = 1, SUBPIXEL_SIZE do
local c = colours[i]
if c ~= bg_colour and c ~= fg_colour then
local ftl = fallback_table[c]
colours[i] = bg_colour
for j = 1, 4 do -- check the first 4 fallback colours
if ftl[j] == bg_colour or ftl[j] == fg_colour then
colours[i] = ftl[j]
break
end
end
end
end
if colours[SUBPIXEL_SIZE] == fg_colour then
bg_colour, fg_colour = fg_colour, bg_colour
end
for i = 1, SUBPIXEL_SIZE - 1 do
if colours[i] == fg_colour then
subpixel_n = subpixel_n + 2 ^ (i - 1)
end
end
local bg = colour_lookup[bg_colour + 1]
local fg = colour_lookup[fg_colour + 1]
local this_dirty = dirty_texture == nil or dirty_texture[dirty_index] ~= bg or dirty_texture[dirty_index + 1] ~= fg or dirty_texture[dirty_index + 2] ~= subpixel_n
if not row_dirty and this_dirty then
row_dirty = true
row_dirty_first_index = txd
end
if this_dirty then
row_dirty_last_index = txd
end
if row_dirty then
rowBg[rowIdx] = bg
rowFg[rowIdx] = fg
rowCh[rowIdx] = subpixel_n
if dirty_texture then
dirty_texture[dirty_index] = bg
dirty_texture[dirty_index + 1] = fg
dirty_texture[dirty_index + 2] = subpixel_n
end
end
rowIdx = rowIdx + 1
dirty_index = dirty_index + 3
end
if row_dirty then
local i0 = row_dirty_first_index / SUBPIXEL_WIDTH + 1
local i1 = row_dirty_last_index / SUBPIXEL_WIDTH + 1
local chs = string_char(table_unpack(rowCh, i0, i1))
local fgs = string_char(table_unpack(rowFg, i0, i1))
local bgs = string_char(table_unpack(rowBg, i0, i1))
term.setCursorPos(i0, y)
-- term.blit((" "):rep(#chs), fgs, ("a"):rep(#bgs))
term.blit(chs, fgs, bgs)
end
base_index = base_index + base_index_delta
y = y + 1
end
end
return function(texture, term, fallback_table, dirty_texture)
assert(type(texture) == "table" and texture.__type == libtexture.__type, "Expected Texture")
assert(type(term) == "table", "Expected table term")
assert(type(fallback_table) == "table" and fallback_table.__type == libfallbackTable.__type, "Expected FallbackTable")
assert(dirty_texture == nil or type(dirty_texture) == "table" and dirty_texture.__type == libtexture.__type, "Expected dirty_texture texture")
assert(texture.width % SUBPIXEL_WIDTH == 0, "Texture width is not a multiple of " .. SUBPIXEL_WIDTH)
assert(texture.height % SUBPIXEL_HEIGHT == 0, "Texture height is not a multiple of " .. SUBPIXEL_HEIGHT)
assert(dirty_texture == nil or texture.width == SUBPIXEL_WIDTH * dirty_texture.width, "Texture width is not proportional to dirty texture width")
assert(dirty_texture == nil or texture.height == SUBPIXEL_HEIGHT * dirty_texture.height, "Texture height is not proportional to dirty texture height")
if texture.format == TextureFormat.Idx1 then
return blit_idx(texture, texture.width, texture.height, term, fallback_table, dirty_texture)
elseif texture.format == TextureFormat.Col1 then
local data = {}
for i = 1, texture.size do
data[i] = math_floor(math_log(texture[i], 2) + 0.5)
end
return blit_idx(data, texture.width, texture.height, term, fallback_table, dirty_texture)
else
error("Unsupported texture format " .. tostring(texture.format))
end
end
end
__localmodules['lib.buffer'] = function(...)
local BufferFormat = require "lib.BufferFormat"
local unique = require "lib.unique"
local buffer = {}
local buffer_mt = {}
buffer.__type = unique "Buffer"
function buffer_mt:__call(data)
assert(type(data) == "table", "Expected a data table")
-- TODO: typecheck etc
local original_size = self.size
local data_len = #data
self.size = original_size + data_len
for i = 1, data_len do
self[original_size + i] = data[i]
end
return self
end
function buffer.create(format)
return setmetatable({
__type = buffer.__type,
format = format,
size = 0,
}, buffer_mt)
end
return buffer
end
__localmodules['lib.transform'] = function(...)
local transform_index = {}
local transform_mt = { __index = transform_index }
local math_sin = math.sin
local math_cos = math.cos
-- TODO: scaling
local function createTransform(dx, dy, dz, ry, rx, rz, sx, sy, sz)
local transform = {
dx = dx, dy = dy, dz = dz,
ry = ry, rx = rx, rz = rz,
sx = sx, sy = sy, sz = sz,
has_forward_multipliers = false,
fxx = nil, fxy = nil, fxz = nil, -- to get X
fyx = nil, fyy = nil, fyz = nil, -- to get Y
fzx = nil, fzy = nil, fzz = nil, -- to get Z
has_inverse_multipliers = false,
ixx = nil, ixy = nil, ixz = nil, -- to get X
iyx = nil, iyy = nil, iyz = nil, -- to get Y
izx = nil, izy = nil, izz = nil, -- to get Z
}
return setmetatable(transform, transform_mt)
end
function transform_index:get_multipliers()
if not self.has_forward_multipliers then
local cX, sX = math_cos(self.rx), math_sin(self.rx)
local cY, sY = math_cos(self.ry), math_sin(self.ry)
local cZ, sZ = math_cos(self.rz), math_sin(self.rz)
local scx, scy, scz = self.sx, self.sy, self.sz
self.fxx, self.fxy, self.fxz = (cY*cZ + sX*sY*sZ)*scx, (cZ*sX*sY - cY*sZ)*scy, (cX*sY)*scz
self.fyx, self.fyy, self.fyz = (cX*sZ)*scx, (cX*cZ)*scy, (-sX)*scz
self.fzx, self.fzy, self.fzz = (-cZ*sY + cY*sX*sZ)*scx, (cY*cZ*sX + sY*sZ)*scy, (cX*cY)*scz
self.has_forward_multipliers = true
end
return self.fxx, self.fxy, self.fxz, self.fyx, self.fyy, self.fyz, self.fzx, self.fzy, self.fzz
end
function transform_index:get_inverse_multipliers()
if not self.has_inverse_multipliers then
error "TODO"
self.has_inverse_multipliers = true
end
return self.ixx, self.ixy, self.ixz, self.iyx, self.iyy, self.iyz, self.izx, self.izy, self.izz
end
function transform_index:translate_by(dx, dy, dz)
return createTransform(self.dx + dx, self.dy + dy, self.dz + dz, self.ry, self.rx, self.rz, self.sx, self.sy, self.sz)
end
function transform_index:rotate_y_by(theta)
return createTransform(self.dx, self.dy, self.dz, self.ry + theta, self.rx, self.rz, self.sx, self.sy, self.sz)
end
function transform_index:rotate_x_by(theta)
return createTransform(self.dx, self.dy, self.dz, self.ry, self.rx + theta, self.rz, self.sx, self.sy, self.sz)
end
function transform_index:rotate_z_by(theta)
return createTransform(self.dx, self.dy, self.dz, self.ry, self.rx, self.rz + theta, self.sx, self.sy, self.sz)
end
function transform_index:scale_by(sx, sy, sz)
return createTransform(self.dx, self.dy, self.dz, self.ry, self.rx, self.rz, self.sx * sx, self.sy * sy, self.sz * sz)
end
function transform_index:translate_to(dx, dy, dz)
return createTransform(dx, dy, dz, self.ry, self.rx, self.rz, self.sx, self.sy, self.sz)
end
function transform_index:rotate_y_to(theta)
return createTransform(self.dx, self.dy, self.dz, theta, self.rx, self.rz, self.sx, self.sy, self.sz)
end
function transform_index:rotate_x_to(theta)
return createTransform(self.dx, self.dy, self.dz, self.ry, theta, self.rz, self.sx, self.sy, self.sz)
end
function transform_index:rotate_z_to(theta)
return createTransform(self.dx, self.dy, self.dz, self.ry, self.rx, theta, self.sx, self.sy, self.sz)
end
function transform_index:scale_to(sx, sy, sz)
return createTransform(self.dx, self.dy, self.dz, self.ry, self.rx, self.rz, sx, sy, sz)
end
return createTransform(0, 0, 0, 0, 0, 0, 1, 1, 1)
end
__localmodules['lib.texture'] = function(...)
local TextureFormat = require "lib.TextureFormat"
local unique = require "lib.unique"
local texture = {}
local SUBPIXEL_WIDTH = 2
local SUBPIXEL_HEIGHT = 3
texture.__type = unique "Texture"
function texture.create(format, width, height)
assert(type(width) == "number", "width not a number")
assert(type(height) == "number", "height not a number")
local tx = {
__type = texture.__type,
format = format,
size = width * height,
width = width,
height = height,
pixel_size = 1, -- updated later
}
local pixel_data
if format == TextureFormat.Idx1 then
pixel_data = { 15 }
elseif format == TextureFormat.Col1 then
pixel_data = { 2^15 }
elseif format == TextureFormat.RGB1 then
pixel_data = { 0, 0, 0 }
elseif format == TextureFormat.Dpt1 then
pixel_data = { math.huge }
elseif format == TextureFormat.BFC1 then
pixel_data = { -1, -1, "" }
else
error("Unknown format '" .. tostring(format) .. "'", 2)
end
tx.pixel_size = #pixel_data
for i = 1, width * height * tx.pixel_size do
tx[i] = pixel_data[(i - 1) % tx.pixel_size + 1]
end
return tx
end
function texture.createSubpixel(format, width, height)
assert(type(width) == "number", "width not a number")
assert(type(height) == "number", "height not a number")
return texture.create(format, width * SUBPIXEL_WIDTH, height * SUBPIXEL_HEIGHT)
end
return texture
end
__localmodules['lib.ccgl3d'] = function(...)
local BufferFormat = require "lib.BufferFormat"
local TextureFormat = require "lib.TextureFormat"
local blit = require "lib.blit"
local buffer = require "lib.buffer"
local camera = require "lib.camera"
local fallback_table = require "lib.fallback_table"
local render = require "lib.render"
local texture = require "lib.texture"
local transform = require "lib.transform"
local viewport = require "lib.viewport"
return {
BufferFormat = BufferFormat,
TextureFormat = TextureFormat,
blit = blit,
buffer = buffer,
camera = camera,
fallback_table = fallback_table,
render = render,
texture = texture,
transform = transform,
viewport = viewport,
version = "0.0.1",
}
end
__localmodules['lib.render'] = function(...)
local BufferFormat = require "lib.BufferFormat"
local TextureFormat = require "lib.TextureFormat"
local libtexture = require "lib.texture"
local math_ceil = math.ceil
local math_floor = math.floor
local math_max = math.max
local math_min = math.min
local math_tan = math.tan
local table_unpack = table.unpack
local function clear(texture)
assert(type(texture) == "table" and texture.__type == libtexture.__type, "Expected texture")
local clearValue = 0
if texture.format == TextureFormat.Col1 then
clearValue = 1
end
for i = 1, texture.width * texture.height * texture.pixel_size do
texture[i] = clearValue
end
end
local function draw_triangles(buffer, texture, camera, viewport, options)
local nearClip = -0.01
local tanFOV = math_tan(camera.fov)
local xPerspectiveMult = 1 / (viewport.width / viewport.height) / tanFOV
local yPerspectiveMult = 1 / tanFOV
local modelTransform = options and options.model_transform
local viewTransform = options and options.view_transform
local cullBackFace = options and options.cull_back_face
local mtfxx, mtfxy, mtfxz, mtfyx, mtfyy, mtfyz, mtfzx, mtfzy, mtfzz, mtfdx, mtfdy, mtfdz
local vtixx, vtixy, vtixz, vtiyx, vtiyy, vtiyz, vtizx, vtizy, vtizz, vtidx, vtidy, vtidz
if modelTransform then
mtfxx, mtfxy, mtfxz, mtfyx, mtfyy, mtfyz, mtfzx, mtfzy, mtfzz = modelTransform:get_multipliers()
mtfdx, mtfdy, mtfdz = modelTransform.dx, modelTransform.dy, modelTransform.dz
end
if viewTransform then
vtixx, vtixy, vtixz, vtiyx, vtiyy, vtiyz, vtizx, vtizy, vtizz = viewTransform:get_inverse_multipliers()
mtfdx, mtfdy, mtfdz = viewTransform.dx, viewTransform.dy, viewTransform.dz
end
assert(buffer.format == BufferFormat.Pos3Col1)
local i = 1
local buffer_size = buffer.size
-- pre-fill the buffer with a lot of data in a few instructions to minimise array resize overhead on the JVM side
local raster_edges = { table_unpack(buffer) }
local raster_edge_index = 1
-- raster_edges format is:
-- (yMin, yMax)
-- (xLeft, dxLeft, zLeft, dzLeft)
-- (xRight, dxRight, zRight, dzRight)
-- colour
while i < buffer_size do repeat
local x0, y0, z0 = buffer[i], buffer[i + 1], buffer[i + 2]
local x1, y1, z1 = buffer[i + 3], buffer[i + 4], buffer[i + 5]
local x2, y2, z2 = buffer[i + 6], buffer[i + 7], buffer[i + 8]
local colour = buffer[i + 9]
i = i + 10
-- model transform
if modelTransform then
x0, y0, z0
= mtfxx * x0 + mtfxy * y0 + mtfxz * z0 + mtfdx
, mtfyx * x0 + mtfyy * y0 + mtfyz * z0 + mtfdy
, mtfzx * x0 + mtfzy * y0 + mtfzz * z0 + mtfdz
x1, y1, z1
= mtfxx * x1 + mtfxy * y1 + mtfxz * z1 + mtfdx
, mtfyx * x1 + mtfyy * y1 + mtfyz * z1 + mtfdy
, mtfzx * x1 + mtfzy * y1 + mtfzz * z1 + mtfdz
x2, y2, z2
= mtfxx * x2 + mtfxy * y2 + mtfxz * z2 + mtfdx
, mtfyx * x2 + mtfyy * y2 + mtfyz * z2 + mtfdy
, mtfzx * x2 + mtfzy * y2 + mtfzz * z2 + mtfdz
end
-- view transform
if viewTransform then
x0, y0, z0 = x0 - vtidx, y0 - vtidy, z0 - vtidz
x1, y1, z1 = x1 - vtidx, y1 - vtidy, z1 - vtidz
x2, y2, z2 = x2 - vtidx, y2 - vtidy, z2 - vtidz
x0, y0, z0
= vtixx * x0 + vtixy * y0 + vtixz * z0
, vtiyx * x0 + vtiyy * y0 + vtiyz * z0
, vtizx * x0 + vtizy * y0 + vtizz * z0
x1, y1, z1
= vtixx * x1 + vtixy * y1 + vtixz * z1
, vtiyx * x1 + vtiyy * y1 + vtiyz * z1
, vtizx * x1 + vtizy * y1 + vtizz * z1
x2, y2, z2
= vtixx * x2 + vtixy * y2 + vtixz * z2
, vtiyx * x2 + vtiyy * y2 + vtiyz * z2
, vtizx * x2 + vtizy * y2 + vtizz * z2
end
-- perspective transform
x0 = x0 * xPerspectiveMult; y0 = y0 * yPerspectiveMult
x1 = x1 * xPerspectiveMult; y1 = y1 * yPerspectiveMult
x2 = x2 * xPerspectiveMult; y2 = y2 * yPerspectiveMult
-- compute depth values
local d0 = -z0
local d1 = -z1
local d2 = -z2
-- depth divide for X/Y components
-- Y flip for screen coordinates
x0 = x0 / d0; y0 = -y0 / d0
x1 = x1 / d1; y1 = -y1 / d1
x2 = x2 / d2; y2 = -y2 / d2
-- backface culling
if cullBackFace then
local dx0, dy0 = x1 - x0, y1 - y0
local dx1, dy1 = x2 - x0, y2 - y0
local cz = dx0*dy1 - dy0*dx1 -- cross product z component
if cz >= 0 then -- cull the face
break -- this is actually a continue
end
end
-- do Z clipping to prevent weird artifacts when triangles are behind the camera
if z0 < nearClip and z1 < nearClip and z2 < nearClip then -- all vertices are valid
-- note: we don't care about Z values from here on
-- order vertices so: y0 < y1 < y2
if y0 > y1 then x0, y0, d0, x1, y1, d1 = x1, y1, d1, x0, y0, d0 end
if y1 > y2 then x1, y1, d1, x2, y2, d2 = x2, y2, d2, x1, y1, d1 end
if y0 > y1 then x0, y0, d0, x1, y1, d1 = x1, y1, d1, x0, y0, d0 end
-- TODO: handle y0 = y1 = y2
-- calculate point on P0->P2 edge intersecting with P1y
local dy01 = y1 - y0
local dy01inv = 1 / dy01
local dy12 = y2 - y1
local dy12inv = 1 / dy12
local d = dy01 / (y2 - y0)
local xP = x0 + (x2 - x0) * d
local yP = y0 + (y2 - y0) * d
-- append top section (if non empty)
if y0 ~= y1 then
raster_edges[raster_edge_index] = y0
raster_edges[raster_edge_index + 1] = y1
raster_edges[raster_edge_index + 2] = x0
raster_edges[raster_edge_index + 6] = x0
if x1 < xP then
raster_edges[raster_edge_index + 3] = (x1 - x0) * dy01inv
raster_edges[raster_edge_index + 7] = (xP - x0) * dy01inv
else
raster_edges[raster_edge_index + 3] = (xP - x0) * dy01inv
raster_edges[raster_edge_index + 7] = (x1 - x0) * dy01inv
end
raster_edges[raster_edge_index + 10] = colour
raster_edge_index = raster_edge_index + 11
end
-- append bottom section (if non empty)
if y1 ~= y2 then
raster_edges[raster_edge_index] = y1
raster_edges[raster_edge_index + 1] = y2
if x1 < xP then
raster_edges[raster_edge_index + 2] = x1
raster_edges[raster_edge_index + 3] = (x2 - x1) * dy12inv
raster_edges[raster_edge_index + 6] = xP
raster_edges[raster_edge_index + 7] = (x2 - xP) * dy12inv
else
raster_edges[raster_edge_index + 2] = xP
raster_edges[raster_edge_index + 3] = (x2 - xP) * dy12inv
raster_edges[raster_edge_index + 6] = x1
raster_edges[raster_edge_index + 7] = (x2 - x1) * dy12inv
end
raster_edges[raster_edge_index + 10] = colour
raster_edge_index = raster_edge_index + 11
end
else
-- order vertices so: z0 < z1 < z2
if z0 > z1 then x0, y0, z0, x1, y1, z1 = x1, y1, z1, x0, y0, z0 end
if z1 > z2 then x1, y1, z1, x2, y2, z2 = x2, y2, z2, x1, y1, z1 end
if z0 > z1 then x0, y0, z0, x1, y1, z1 = x1, y1, z1, x0, y0, z0 end
if z1 < nearClip then -- P2 vertex should be clipped
-- note: we don't care about Z values from here on
-- @queue quad p0, p1 quad (split twice at mid Ys)
error "TODO"
elseif z0 < nearClip then -- P1 and P2 vertices should be clipped
-- note: we don't care about Z values from here on
-- @queue p0 sub-triangle (split at mid Y)
error "TODO"
else -- all vertices clipped, triangle ignored
-- do nothing
end
end
until true end
for i = 1, raster_edge_index - 1, 11 do
local yMin, yMax, xLeft, dxLeft, zLeft, dzLeft, xRight, dxRight, zRight, dzRight, colour =
raster_edges[i], raster_edges[i + 1], raster_edges[i + 2], raster_edges[i + 3], raster_edges[i + 4], raster_edges[i + 5], raster_edges[i + 6], raster_edges[i + 7], raster_edges[i + 8], raster_edges[i + 9], raster_edges[i + 10]
local yMinPixel = math_floor((1 + yMin) * (viewport.height - 1) * 0.5 + 0.5)
local yMaxPixel = math_ceil((1 + yMax) * (viewport.height - 1) * 0.5 - 0.5)
local vw1half = (viewport.width - 1) * 0.5
local xAbsoluteMin = math_max(-1, math_min(xLeft, xLeft + (yMax - yMin) * dxLeft))
local xAbsoluteMax = math_min(1, math_max(xRight, xRight + (yMax - yMin) * dxRight))
local xAbsoluteMinPixel = math_floor((1 + xAbsoluteMin) * vw1half + 0.5)
local xAbsoluteMaxPixel = math_ceil((1 + xAbsoluteMax) * vw1half - 0.5)
local dyScale = 2 / (viewport.height - 1)
for yPixel = math_max(0, yMinPixel), math_min(viewport.height, yMaxPixel) do
local baseIndex = yPixel * texture.width + 1
local xRowLeft = xLeft + dxLeft * (yPixel - yMinPixel) * dyScale
local xRowRight = xRight + dxRight * (yPixel - yMinPixel) * dyScale
local xLeftPixel = math_floor((1 + xRowLeft) * vw1half + 0.5)
local xRightPixel = math_ceil((1 + xRowRight) * vw1half - 0.5)
for x = math_max(xLeftPixel, xAbsoluteMinPixel), math_min(xRightPixel, xAbsoluteMaxPixel) do
texture[baseIndex + x] = colour
end
end
end
end
return {
clear = clear,
draw_triangles = draw_triangles,
}
end
__localmodules['lib.camera'] = function(...)
local unique = require "lib.unique"
local camera = {}
camera.__type = unique "Camera"
camera.Perspective = unique "Perspective"
camera.Orthogonal = unique "Orthogonal"
function camera.createPerspective(fov)
assert(type(fov) == "number", "FOV not a number")
return {
__type = camera.__type,
mode = camera.Perspective,
fov = fov * math.pi / 180,
x = 0,
y = 0,
z = 0,
pan = 0,
tilt = 0,
}
end
return camera
end
__localmodules['lib.fallback_table'] = function(...)
local unique = require "lib.unique"
local math_sqrt = math.sqrt
local fallback_table = {}
fallback_table.__type = unique "FallbackTable"
local function color_diff(r0, g0, b0, r1, g1, b1)
local rd = r0 - r1
local gd = g0 - g1
local bd = b0 - b1
return math_sqrt(rd * rd + gd * gd + bd * bd)
end
function fallback_table.create(term)
local ft = {
__type = fallback_table.__type,
}
for i = 0, 15 do
local r0, g0, b0 = term.getPaletteColour(2 ^ i)
local deltas = {}
for j = 0, 15 do
local r1, g1, b1 = term.getPaletteColour(2 ^ j)
if i ~= j then
deltas[#deltas + 1] = { j, color_diff(r0, g0, b0, r1, g1, b1) }
end
end
table.sort(deltas, function(a, b) return a[2] < b[2] end)
ft[i] = {}
for j = 1, #deltas do
ft[i][j] = deltas[j][1]
end
end
return ft
end
return fallback_table
end
__localmodulelines['lib.BufferFormat'] = 17
__localmodulelines['lib.TextureFormat'] = 37
__localmodulelines['lib.unique'] = 50
__localmodulelines['__main'] = 63
__localmodulelines['lib.viewport'] = 201
__localmodulelines['lib.blit'] = 229
__localmodulelines['lib.buffer'] = 406
__localmodulelines['lib.transform'] = 444
__localmodulelines['lib.texture'] = 541
__localmodulelines['lib.ccgl3d'] = 601
__localmodulelines['lib.render'] = 630
__localmodulelines['lib.camera'] = 876
__localmodulelines['lib.fallback_table'] = 903
-- TODO: error mapping!
return __localmodules['__main'](...)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment