From 3275ae4948fd2c1bb8da780214cbb741dc3178be Mon Sep 17 00:00:00 2001 From: sanine Date: Fri, 12 May 2023 01:16:46 -0500 Subject: begin refactor --- honey/asset/image.lua | 49 ++++++++++++++++ honey/asset/mesh.lua | 150 ++++++++++++++++++++++++++++++++++++++++++++++++ honey/asset/shader.lua | 143 +++++++++++++++++++++++++++++++++++++++++++++ honey/ecs/collision.lua | 17 ++++++ honey/ecs/render.lua | 9 +++ honey/image.lua | 50 ---------------- honey/mesh.lua | 147 ----------------------------------------------- honey/notes.md | 23 ++++++++ honey/ode.lua | 55 ------------------ honey/shader.lua | 136 ------------------------------------------- honey/std.lua | 19 ++---- 11 files changed, 396 insertions(+), 402 deletions(-) create mode 100644 honey/asset/image.lua create mode 100644 honey/asset/mesh.lua create mode 100644 honey/asset/shader.lua delete mode 100644 honey/image.lua delete mode 100644 honey/mesh.lua create mode 100644 honey/notes.md delete mode 100644 honey/ode.lua delete mode 100644 honey/shader.lua (limited to 'honey') diff --git a/honey/asset/image.lua b/honey/asset/image.lua new file mode 100644 index 0000000..c5bd145 --- /dev/null +++ b/honey/asset/image.lua @@ -0,0 +1,49 @@ +local gl = honey.gl + +local module = {} +setmetatable(module, {__index=_G}) +setfenv(1, module) + + +local cache = {} + +-- load an image into a gl texture +local function loadImage(filename) + local data, width, height = honey.image.load(filename, 4) + local texture = gl.GenTextures() + gl.BindTexture(gl.TEXTURE_2D, texture) + gl.TexImage2D( + gl.TEXTURE_2D, 0, + gl.RGBA, width, height, + gl.RGBA, gl.UNSIGNED_BYTE, data + ) + gl.GenerateMipmap(gl.TEXTURE_2D) + honey.image.destroy(data) + return texture +end + +-- cached get a texture +get = function(filename) + if not cache[filename] then + cache[filename] = loadImage(filename) + end + return cache[filename] +end + +-- remove a texture from the cache +forget = function(filename) + local texture = cache[filename] + if texture then + gl.DeleteTextures(texture) + end + cache[filename] = nil +end + +-- remove all textures from the cache +clearCache = function(filename) + for key in pairs(cache) do + forget(key) + end +end + +return module diff --git a/honey/asset/mesh.lua b/honey/asset/mesh.lua new file mode 100644 index 0000000..876c5e4 --- /dev/null +++ b/honey/asset/mesh.lua @@ -0,0 +1,150 @@ +local gl = honey.gl + +local module = {} +setmetatable(module, {__index=_G}) +setfenv(1, module) + + +-- append the 8 floats per vertex to the vertices table +local function insertVertex(vertices, attrib, vertex) + local pos = 3*vertex.v_idx + for i=1,3 do + table.insert(vertices, attrib.vertices[pos+i]) + end + + local normal = 3*vertex.vn_idx + for i=1,3 do + table.insert(vertices, attrib.normals[normal+i]) + end + + local tex = 2*vertex.vt_idx + for i=1,2 do + table.insert(vertices, attrib.texcoords[tex+i]) + end +end + + +-- creates a table of floats representing vertex data +-- and a table of ints representing face indices within the vertices +local function loadShape(shape, attrib, debug) + local vertices = {} + local indices = {} + + local start = shape.face_offset + local finish = start + shape.length + for i=start,finish-1 do + assert(attrib.face_num_verts[i+1] == 3, "non-triangular face!") + for j=0,2 do + local vertex = attrib.faces[(3*i) + j + 1] + insertVertex(vertices, attrib, vertex, debug) + table.insert(indices, #indices) + end + end + + return vertices, indices +end + + +-- public helper function (used in some other places, like trimesh construction) +-- packages the vertices & indices tables from loadShape into a single array +loadFile = function(filename) + local flags = honey.tinyobj.FLAG_TRIANGULATE + local attrib, shapes, materials = honey.tinyobj.parse_obj(filename, flags) + + local meshes = {} + for _, shape in ipairs(shapes) do + local vertices, indices = loadShape(shape, attrib, debug) + table.insert(meshes, {vertices=vertices, indices=indices}) + end + return meshes +end + + +-- helper function to create a gl vertex array object from (vertices, indices) pair +local function createMesh(vertices, indices) + vao = gl.GenVertexArrays() + vbo = gl.GenBuffers() + ebo = gl.GenBuffers() + + gl.BindVertexArray(vao) + gl.BindBuffer(gl.ARRAY_BUFFER, vbo) + gl.BufferData(gl.ARRAY_BUFFER, gl.FLOAT, vertices, gl.STATIC_DRAW) + + gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo) + gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, gl.UNSIGNED_INT, indices, gl.STATIC_DRAW) + + local stride = 8 + local offset = 0 + + -- position + gl.VertexAttribPointer(0, 3, false, stride, offset) + gl.EnableVertexAttribArray(0) + offset = offset+3 + + -- normal + gl.VertexAttribPointer(1, 3, false, stride, offset) + gl.EnableVertexAttribArray(1) + offset = offset+3 + + -- texture + gl.VertexAttribPointer(2, 2, false, stride, offset) + gl.EnableVertexAttribArray(2) + offset = offset+2 + + return vao, vbo, ebo, #vertices +end + + +local cache = {} + + +-- get a gl vertex array object + vertex count +get = function(filename, index) + if not cache[filename] then + local c = {} + local meshes = loadFile(filename) + for _, m in ipairs(meshes) do + local vao, vbo, ebo, count = createMesh(m.vertices, m.indices) + table.insert(c, { vao=vao, vbo=vbo, ebo=ebo, count=count }) + end + cache[filename] = c + end + local m = cache[filename][index] + return m.vao, m.count +end + + +-- forget a mesh +forget = function(filename) + if not cache[filename] then return end + for _, m in ipairs(cache[filename]) do + gl.DeleteBuffers(m.vbo) + gl.DeleteBuffers(m.ebo) + gl.DeleteVertexArrays(m.vao) + end + cache[filename] = nil +end + + +-- clear the cache +clearCache = function() + for key in pairs(cache) do + forget(key) + end +end + + +--===== builtin meshes =====-- + +function createBuiltins() + cache["builtin.quad"] = {Mesh( + { 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 1, 0, + 0, 1, 0, 0, 1, 0, 0, 1, + 1, 1, 0, 0, 1, 0, 1, 1 }, + { 0, 1, 3, 0, 3, 2 } + )} +end + + +return module diff --git a/honey/asset/shader.lua b/honey/asset/shader.lua new file mode 100644 index 0000000..0e8c93a --- /dev/null +++ b/honey/asset/shader.lua @@ -0,0 +1,143 @@ +local gl = honey.gl + +local module = {} +setmetatable(module, {__index=_G}) +setfenv(1, module) + +-- compile glsl source code +local function compileShader(source, type) + local shader = gl.CreateShader(type) + gl.ShaderSource(shader, source) + gl.CompileShader(shader) + return shader +end + +-- helper function +local builtin = {} +local function readFile(filename) + -- support built-in shaders + if builtin[filename] then return builtin[filename] end + + local f, err = io.open(filename) + if not f then error(err) end + local str = f:read("*a") + f:close() + return str +end + + +-- build a shader program from provided source files +local function buildProgram(sources) + local shaders = {} + + -- load & compile shaders + if sources.vertex then + local shader = compileShader(readFile(sources.vertex), gl.VERTEX_SHADER) + table.insert(shaders, shader) + end + if sources.vertex then + local shader = compileShader(readFile(sources.vertex), gl.VERTEX_SHADER) + table.insert(shaders, shader) + end + + -- link shaders + local program = gl.CreateProgram() + for _, shader in ipairs(shaders) do + gl.AttachShader(program, shader) + end + gl.LinkProgram(program) + + -- clean up + for _, shader in ipairs(shaders) do + gl.DeleteShader(shader) + end + + return program +end + + +-- public uniform setters +function setInt(program, name, value) + local location = gl.GetUniformLocation(program, name) + gl.Uniform1i(location, value) +end +function setFloat(program, name, value) + local location = gl.GetUniformLocation(program, name) + gl.Uniform1f(location, value) +end +function setVec3(program, name, value) + local location = gl.GetUniformLocation(program, name) + gl.Uniform3f(location, value[1], value[2], value[3]) +end +function setVec4(program, name, value) + local location = gl.GetUniformLocation(program, name) + gl.Uniform3f(location, value[1], value[2], value[3], value[4]) +end +function setMatrix(program, name, matrix) + local location = gl.GetUniformLocation(program, name) + gl.UniformMatrix4fv(location, false, matrix.data) +end + + +function configure(program, tbl) + local processKey = function(key, set) + local subtbl = tbl[key] + if subtbl then + for name, value in pairs(subtbl) do + set(program, name, value) + end + end + end + + processKey("int", setInt) + processKey("float", setFloat) + processKey("vec3", setVec3) + processKey("vec4", setVec4) + processKey("matrix", setMatrix) +end + + +--===== public asset cache functions =====-- + +local cache = {} + + +-- generate id string based on shader sources +local function shaderId(sources) + return string.format( + "%s;%s", + sources.vertex or "nil", + sources.fragment or "nil" + ) +end + +-- get a cached shader program +get = function(sources) + local id = shaderId(sources) + if not cache[id] then + cache[id] = buildProgram(sources) + end + return cache[id] +end + + +-- remove a cached shader program +forget = function(sources) + local id = shaderId(sources) + if cache[id] then + gl.DeleteProgram(cache[id]) + cache[id] = nil + end +end + + +-- clear the cache +clearCache = function() + for key, program in pairs(cache) do + gl.DeleteProgram(program) + cache[key] = nil + end +end + + +return module diff --git a/honey/ecs/collision.lua b/honey/ecs/collision.lua index 4c8af5b..46028b4 100644 --- a/honey/ecs/collision.lua +++ b/honey/ecs/collision.lua @@ -1,3 +1,4 @@ +local mesh = require 'honey.mesh' local glm = require 'honey.glm' local Vec3 = glm.Vec3 local ode = honey.ode @@ -7,6 +8,18 @@ setmetatable(module, {__index=_G}) setfenv(1, module) +local function loadTriMesh(space, filename) + local attrib, shapes, _ = honey.tinyobj.parse_obj( + filename, honey.tinyobj.FLAG_TRIANGULATE + ) + local vertices, indices = mesh.loadShape(shapes[1], attrib) + + trimeshdata = ode.GeomTriMeshDataCreate() + ode.GeomTriMeshDataBuild(trimeshdata, vertices, indices) + return ode.CreateTriMesh(space, trimeshdata) +end + + --===== collision space =====-- @@ -27,6 +40,8 @@ local function createGeom(self, id, collision) geom = ode.CreatePlane(self.space, normal[1], normal[2], normal[3], d) elseif collision.class == "ray" then geom = ode.CreateRay(self.space, collision.length) + elseif collision.class == "trimesh" then + geom = loadTriMesh(self.space, collision.filename) end ode.GeomSetCategoryBits(geom, collision.category or 1) @@ -44,6 +59,8 @@ end local function isPlaceable(collision) if collision.class == "ray" then return true + elseif collision.class == "trimesh" then + return true end return false end diff --git a/honey/ecs/render.lua b/honey/ecs/render.lua index abeac04..1e41e7a 100644 --- a/honey/ecs/render.lua +++ b/honey/ecs/render.lua @@ -67,6 +67,15 @@ system = function(params) (node and node._matrix) or Mat4():identity() -- get shader + if not tbl.shader then + print(node) + print(node and node._matrix) + print(node and node.name) + print(node and node.parent) + for k, v in pairs(tbl) do + print(k, v) + end + end local shader = honey.shader.loadShader( tbl.shader.vertex, tbl.shader.fragment ) diff --git a/honey/image.lua b/honey/image.lua deleted file mode 100644 index f074272..0000000 --- a/honey/image.lua +++ /dev/null @@ -1,50 +0,0 @@ -local gl = honey.gl - -local module = {} -setmetatable(module, {__index=_G}) -setfenv(1, module) - -function Image(filename, params) - local params = params or {} - local data, width, height = honey.image.load(filename, 4) - local self = {} - self.width = width - self.height = height - - self.texture = gl.GenTextures() - gl.BindTexture(gl.TEXTURE_2D, self.texture) - for param, value in pairs(params) do - gl.TexParameteri(gl.TEXTURE_2D, gl[param], value) - end - gl.TexImage2D( - gl.TEXTURE_2D, 0, - gl.RGBA, width, height, - gl.RGBA, gl.UNSIGNED_BYTE, data - ) - gl.GenerateMipmap(gl.TEXTURE_2D) - honey.image.destroy(data) - - self.__gc = honey.util.gc_canary(function() - gl.DeleteTextures(self.texture) - end) - - return self -end - - -local cache = {} -function loadImage(filename, params) - if not cache[filename] then - local img = Image(filename, params) - cache[filename] = img - return img - end - - return cache[filename] -end - -function clearImageCache() - cache = {} -end - -return module diff --git a/honey/mesh.lua b/honey/mesh.lua deleted file mode 100644 index 12edea9..0000000 --- a/honey/mesh.lua +++ /dev/null @@ -1,147 +0,0 @@ -local module = {} -local gl = honey.gl -setmetatable(module, {__index=_G}) -setfenv(1, module) - - -local function printVertex(vertices, i) - print(string.format( - "p(%f, %f, %f), n(%f, %f, %f), t(%f, %f)", - vertices[i+0], - vertices[i+1], - vertices[i+2], - - - vertices[i+3], - vertices[i+4], - vertices[i+5], - - - vertices[i+5], - vertices[i+6] - )) -end - - -local function insertVertex(vertices, attrib, vertex, debug) - if debug then print() end - local pos = 3*vertex.v_idx - for i=1,3 do - table.insert(vertices, attrib.vertices[pos+i]) - if debug then print(vertices[#vertices]) end - end - - local normal = 3*vertex.vn_idx - for i=1,3 do - table.insert(vertices, attrib.normals[normal+i]) - if debug then print(vertices[#vertices]) end - end - - local tex = 2*vertex.vt_idx - for i=1,2 do - table.insert(vertices, attrib.texcoords[tex+i]) - if debug then print(vertices[#vertices]) end - end - if debug then - for i=1,#attrib.texcoords do - print(i, attrib.texcoords[i]) - end - end -end - - -function loadShape(shape, attrib, debug) - local vertices = {} - local indices = {} - - local start = shape.face_offset - local finish = start + shape.length - for i=start,finish-1 do - assert(attrib.face_num_verts[i+1] == 3, "non-triangular face!") - for j=0,2 do - local vertex = attrib.faces[(3*i) + j + 1] - insertVertex(vertices, attrib, vertex, debug) - table.insert(indices, #indices) - end - end - - return vertices, indices -end - - -function loadFile(filename, debug) - local flags = honey.tinyobj.FLAG_TRIANGULATE - if debug then print("load file:", filename) end - local attrib, shapes, materials = honey.tinyobj.parse_obj(filename, flags) - - local meshes = {} - for _, shape in ipairs(shapes) do - local vertices, indices = loadShape(shape, attrib, debug) - table.insert(meshes, Mesh(vertices, indices)) - end - if debug then print("finished file:", filename) end - return meshes -end - - -cache = {} -function loadCached(filename, index, debug) - if not cache[filename] then - cache[filename] = loadFile(filename, debug) - end - return cache[filename][index] -end - - -Mesh = {} -Mesh.__index = Mesh - - -function Mesh.new(_, vertices, indices) - local self = {} - setmetatable(self, Mesh) - - self.vertexArray = gl.GenVertexArrays() - self.vertexBuffer = gl.GenBuffers() - self.elementBuffer = gl.GenBuffers() - self.vertexCount = #indices - - gl.BindVertexArray(self.vertexArray) - gl.BindBuffer(gl.ARRAY_BUFFER, self.vertexBuffer) - gl.BufferData(gl.ARRAY_BUFFER, gl.FLOAT, vertices, gl.STATIC_DRAW) - - gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.elementBuffer) - gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, gl.UNSIGNED_INT, indices, gl.STATIC_DRAW) - - gl.VertexAttribPointer(0, 3, false, 8, 0) - gl.EnableVertexAttribArray(0) - gl.VertexAttribPointer(1, 3, false, 8, 3) - gl.EnableVertexAttribArray(1) - gl.VertexAttribPointer(2, 2, false, 8, 6) - gl.EnableVertexAttribArray(2) - - return self -end -setmetatable(Mesh, {__call=Mesh.new}) - - -function Mesh.drawElements(self) - gl.BindVertexArray(self.vertexArray) - gl.DrawElements(gl.TRIANGLES, self.vertexCount, gl.UNSIGNED_INT, 0) -end - - ---===== builtin meshes =====-- - -function createBuiltins() - cache["builtin.quad"] = {Mesh( - { 0, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 1, 0, - 0, 1, 0, 0, 1, 0, 0, 1, - 1, 1, 0, 0, 1, 0, 1, 1 }, - { 0, 1, 3, 0, 3, 2 } - )} -end - - -return module diff --git a/honey/notes.md b/honey/notes.md new file mode 100644 index 0000000..5dd25e7 --- /dev/null +++ b/honey/notes.md @@ -0,0 +1,23 @@ +avoid closures where possible; they lead to weird garbage collector effects! + + +assets +------ + +assets are cached. each asset type has a module (e.g. `mesh`, `image`, `sound`, etc). These have the following functions: + + * `get(filename)` - cached load of the given file. throws error if it cannot be found + * `forget(filename` - remove the cached copy, if any of the given file + * `clearCache()` - clear the full cache for that asset type + +individual assets may have additional functions. + + +systems +------- + +systems are pure functions (NOT closures!!). they can be embedded in tables that indicate dependencies, i.e. which other systems *must* execute before them in order for things to work correctly. They have a signature like + +> `system(db, dt, [params])` + +where params is a table. all parameters, including the params table, are passed in by the systemdb every frame; the system should NEVER close over them. diff --git a/honey/ode.lua b/honey/ode.lua deleted file mode 100644 index 4cd2e54..0000000 --- a/honey/ode.lua +++ /dev/null @@ -1,55 +0,0 @@ -local module = {} -setmetatable(module, {__index=_G} -setfenv(1, module) - - ---===== collision =====-- - -Geom = {} -Geom.__index = Geom - - -function Geom.new(_, type, params) - local self = {} - self.type = type - self.params = params - setmetatable(self, Geom) - return self -end -setmetatable(Geom, {__call=Geom.new}) - - -function Geom.renew(geom) - return ode.GeomGetData(geom) -end - - -local function instantiateSphere(space, params) - local geom = ode.CreateSphere(space, params.radius) - return geom -end - - -local function instantiatePlane(space, params) - local geom = ode.CreatePlane(space, params.a, params.b, params.c, params.d) - return geom -end - - -function Geom.instantiate(self, space) - if self.type == "sphere" then - self.geom = instantiateSphere(space, self.params) - elseif self.type == "plane" then - self.geom = instantiatePlane(space, self.params) - else - error(string.format("bad geom type: %s", self.type)) - end - ode.GeomSetData(self.geom, self) - return self -end - - -function Geom.setBody(self, body) - ode.GeomSetBody(self.geom, body.body) - return self -end diff --git a/honey/shader.lua b/honey/shader.lua deleted file mode 100644 index 6b281f7..0000000 --- a/honey/shader.lua +++ /dev/null @@ -1,136 +0,0 @@ -local gl = honey.gl - -local module = {} -setmetatable(module, {__index=_G}) -setfenv(1, module) - - -Shader = {} -Shader.__index = Shader - -local function compileShader(source, type) - local shader = gl.CreateShader(type) - gl.ShaderSource(shader, source) - gl.CompileShader(shader) - return shader -end - -local function readFile(filename) - local f, err = io.open(filename) - if not f then error(err) end - local str = f:read("*a") - f:close() - return str -end - -function Shader.new(_, sources) - local self = {} - self.locations = {} - self.links = {} - - if sources.vertexFile then - sources.vertex = readFile(sources.vertexFile) - end - if sources.fragmentFile then - sources.fragment = readFile(sources.fragmentFile) - end - - local shaders = {} - if sources.vertex then - table.insert(shaders, compileShader(sources.vertex, gl.VERTEX_SHADER)) - end - if sources.fragment then - table.insert(shaders, compileShader(sources.fragment, gl.FRAGMENT_SHADER)) - end - - self.program = gl.CreateProgram() - for _, shader in ipairs(shaders) do - gl.AttachShader(self.program, shader) - end - gl.LinkProgram(self.program) - for _, shader in ipairs(shaders) do - gl.DeleteShader(shader) - end - - self.__gc = honey.util.gc_canary(function() - gl.DeleteProgram(self.program) - end) - - setmetatable(self, Shader) - return self -end -setmetatable(Shader, {__call=Shader.new}) - - -function Shader.getLocation(self, name) - if self.locations[name] then - return self.locations[name] - end - - local location = gl.GetUniformLocation(self.program, name) - self.locations[name] = location - return location -end - - -function Shader.use(self) - gl.UseProgram(self.program) -end - - -function Shader.setInt(self, name, value) - local location = self:getLocation(name) - gl.Uniform1i(location, value) -end -function Shader.setFloat(self, name, value) - local location = self:getLocation(name) - gl.Uniform1f(location, value) -end - -function Shader.setVec3(self, name, value) - local location = self:getLocation(name) - gl.Uniform3f(location, value[1], value[2], value[3]) -end -function Shader.setVec4(self, name, value) - local location = self:getLocation(name) - gl.Uniform3f(location, value[1], value[2], value[3], value[4]) -end - -function Shader.setMatrix(self, name, matrix) - local location = self:getLocation(name) - gl.UniformMatrix4fv(location, false, matrix.data) -end - - -function Shader.configure(self, tbl) - local processKey = function(key, set) - local subtbl = tbl[key] - if subtbl then - for name, value in pairs(subtbl) do - self[set](self, name, value) - end - end - end - - processKey("int", "setInt") - processKey("float", "setFloat") - processKey("vec3", "setVec3") - processKey("vec4", "setVec4") - processKey("matrix", "setMatrix") -end - - -local shaderCache = {} -function loadShader(vertex, fragment) - local id = vertex .. "+" .. fragment - if not shaderCache[id] then - local shader = Shader{vertexFile=vertex, fragmentFile=fragment} - shaderCache[id] = shader - end - return shaderCache[id] -end -function clearShaderCache() - shaderCache = {} -end - -return module diff --git a/honey/std.lua b/honey/std.lua index d609844..222798c 100644 --- a/honey/std.lua +++ b/honey/std.lua @@ -4,24 +4,15 @@ honey.init = init.init honey.loop = init.loop honey.terminate = init.terminate -nvg = honey.nvg -ode = honey.ode -gl = honey.gl -glfw = honey.glfw +honey.asset = {} +honey.asset.image = require 'honey.asset.image' +honey.asset.mesh = require 'honey.asset.mesh' +honey.asset.shader = require 'honey.asset.shader' honey.ecs = require 'honey.ecs' -honey.mesh = require 'honey.mesh' -honey.shader = require 'honey.shader' -honey.Window = require 'honey.window' - - --- image -local image = require 'honey.image' -for k, v in pairs(image) do - honey.image[k] = v -end +-- glm is so frequently used that we load it globally, not locally local glm = require 'honey.glm' Vec3 = glm.Vec3 Mat4 = glm.Mat4 -- cgit v1.2.1