Last active
August 24, 2022 20:04
-
-
Save exerro/126bf0850a999d7064f53dc417da614b to your computer and use it in GitHub Desktop.
First 3D rendering demo using CCGL3D
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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