diff options
author | sanine-a <sanine.not@pm.me> | 2023-03-28 16:35:22 -0500 |
---|---|---|
committer | sanine-a <sanine.not@pm.me> | 2023-03-28 16:35:22 -0500 |
commit | 45dbe47d17303050cbea7c2c51e838acfe21c2fb (patch) | |
tree | c2827c7aae6cf29286766209af942f16f91ee4c8 | |
parent | d1c2a881f55b80603f6a7772a2c32394d23e795a (diff) |
add cached mesh loading
-rw-r--r-- | honey.bak/ecs-systems.lua | 76 | ||||
-rw-r--r-- | honey.bak/ecs.lua | 234 | ||||
-rw-r--r-- | honey.bak/ecs.test.lua | 51 | ||||
-rw-r--r-- | honey.bak/glm.lua | 361 | ||||
-rw-r--r-- | honey.bak/init.lua | 48 | ||||
-rw-r--r-- | honey.bak/mesh.lua | 95 | ||||
-rw-r--r-- | honey.bak/shader.lua | 118 | ||||
-rw-r--r-- | honey.bak/std.lua | 17 | ||||
-rw-r--r-- | honey.bak/window.lua | 160 | ||||
-rw-r--r-- | honey/ecs.lua | 93 | ||||
-rw-r--r-- | honey/ecs.test.lua | 48 | ||||
-rw-r--r-- | honey/mesh.lua | 17 | ||||
-rw-r--r-- | honey/std.lua | 2 | ||||
-rw-r--r-- | main.lua | 213 |
14 files changed, 1344 insertions, 189 deletions
diff --git a/honey.bak/ecs-systems.lua b/honey.bak/ecs-systems.lua new file mode 100644 index 0000000..5dec159 --- /dev/null +++ b/honey.bak/ecs-systems.lua @@ -0,0 +1,76 @@ +local ecs = require 'honey.ecs'
+
+
+local module = {}
+setmetatable(module, {__index=_G})
+setfenv(1, module)
+
+
+
+--===== transform cascading =====--
+
+local function recursiveComputeTransform(entity)
+ if entity._transformComputed then
+ return entity._transform
+ end
+ if entity.parent == false then
+ entity._transformComputed = true
+ entity._transform = entity.transform
+ return entity.transform
+ end
+
+ entity._transformComputed = true
+ local parentTransform = recursiveComputeTransform(entity.parent)
+ entity._transform = parentTransform * entity.transform
+ return entity._transform
+end
+
+-- update transforms
+transformCascade = {
+ filter=ecs.Filter.AND{"transform", "parent"},
+ prepareEntity=function(self, entity)
+ entity._transform = nil
+ entity._transformComputed = false
+ end,
+ update=function(self, entity, dt)
+ recursiveComputeTransform(entity)
+ end,
+ priority=98,
+}
+
+
+--===== rendering =====--
+
+function renderCam(camera, priority)
+ local priority = priority or 99
+ return {
+ filter=ecs.Filter.AND{"mesh", "shader", "transform"},
+ update=function(self, entity, dt)
+ entity.shader:use()
+ entity.shader:configure{
+ matrix={
+ model=entity._transform,
+ view=camera.view,
+ projection=camera.projection,
+ },
+ }
+ entity.mesh:drawElements()
+ end,
+ nopause=true,
+ priority=priority,
+ }
+end
+
+--===== update functions =====--
+
+update = {
+ filter=ecs.Filter.AND{"update"},
+ update=function(self, entity, dt)
+ entity.update(entity, dt)
+ end,
+ priority=50,
+}
+
+
+
+return module
diff --git a/honey.bak/ecs.lua b/honey.bak/ecs.lua new file mode 100644 index 0000000..0672efe --- /dev/null +++ b/honey.bak/ecs.lua @@ -0,0 +1,234 @@ +local module = {} +setmetatable(module, {__index=_G}) +setfenv(1, module) + + +--===== filters =====-- + + +Filter = {} + +-- base filter creation +function Filter.new(_, operation, tbl) + local self = {} + self.keys = {} + self.filters = {} + self.op = operation + + for _, v in ipairs(tbl) do + if type(v) == "function" then + table.insert(self.filters, v) + elseif type(v) == "string" then + table.insert(self.keys, v) + end + end + + return function(entity, _self) + local entity = _self or entity -- able to call as . or : + return Filter.check(self, entity) + end +end +setmetatable(Filter, {__call=Filter.new}) + + +-- base filter checking +function Filter.check(self, entity) + local funcname = "check" .. self.op + return Filter[funcname](self, entity) +end + + +-- AND filter (all keys and subfilters must match) +function Filter.AND(tbl) + return Filter("AND", tbl) +end +function Filter.checkAND(self, entity) + for _, subfilter in ipairs(self.filters) do + if not subfilter(entity) then return false end + end + for _, key in ipairs(self.keys) do + if entity[key] == nil then return false end + end + return true +end + + +-- OR filter (at least one key or subfilter must match) +function Filter.OR(tbl) + return Filter("OR", tbl) +end +function Filter.checkOR(self, entity) + for _, subfilter in ipairs(self.filters) do + if subfilter(entity) then return true end + end + for _, key in ipairs(self.keys) do + if entity[key] ~= nil then return true end + end + return false +end + + +-- NAND filter (at least one key or subfilter must NOT match) +function Filter.NAND(tbl) + return Filter("NAND", tbl) +end +function Filter.checkNAND(self, entity) + for _, subfilter in ipairs(self.filters) do + if not subfilter(entity) then return true end + end + for _, key in ipairs(self.keys) do + if entity[key] == nil then return true end + end + return false +end + + +-- NOR filter (no keys or subfilters may match) +function Filter.NOR(tbl) + return Filter("NOR", tbl) +end +function Filter.checkNOR(self, entity) + for _, subfilter in ipairs(self.filters) do + if subfilter(entity) then return false end + end + for _, key in ipairs(self.keys) do + if entity[key] ~= nil then return false end + end + return true +end + + +--===== levels =====-- + +Level = {} +Level.__index = Level + +function Level.new(_) + local self = {} + self.systems = {} + self.entities = {} + self.nextId = 1 + setmetatable(self, Level) + return self +end +setmetatable(Level, {__call=Level.new}) + + +function Level.getEntity(self, id) + return self.entities[id] +end + + +local function systemLt(a, b) + return (a.priority or 50) < (b.priority or 50) +end +function Level.addSystem(self, system) + assert(system.update, "systems must have an 'update' key") + assert(system.filter, "systems must have a 'filter' key") + system.entities = {} + system.level = self + table.insert(self.systems, system) + table.sort(self.systems, systemLt) + if system.setup then + system.setup(system) + end +end + + +local function addEntityToSystem(system, id, entity) + -- check if entity is already present + if system.entities[id] then return end + + if system.onAddEntity then + system:onAddEntity(id, entity) + end + system.entities[id] = true +end + + +local function removeEntityFromSystem(system, id, entity) + -- check if entity is already absent + if not system.entities[id] then return end + + if system.onRemoveEntity then + system:onRemoveEntity(id, entity) + end + system.entities[id] = nil +end + + +function Level.addEntity(self, entity) + local id = self.nextId + self.entities[id] = entity + self.nextId = id + 1 + + for _, system in ipairs(self.systems) do + if system.filter(entity) then + addEntityToSystem(system, id, entity) + end + end + + return id +end + + +function Level.reconfigureEntity(self, id) + local entity = self.entities[id] + + for _, system in ipairs(self.systems) do + if system.filter(entity) then + addEntityToSystem(system, id, entity) + else + removeEntityFromSystem(system, id, entity) + end + end +end + + +function Level.removeEntity(self, id) + local entity = self.entities[id] + if not entity then error("bad id: "..tostring(id)) end + for _, system in ipairs(self.systems) do + removeEntityFromSystem(system, id, entity) + end + self.entities[id] = nil +end + + +function Level.reconfigureAllEntities(self) + for id in ipairs(self.entities) do + self:reconfigureEntity(id) + end +end + + +function Level.update(self, dt, paused) + local paused = paused or false + for _, system in ipairs(self.systems) do + if (not paused) or (paused and system.nopause) then + if system.preUpdate then + system:preUpdate() + end + if system.prepareEntity then + for id in pairs(system.entities) do + local entity = self.entities[id] + if entity then + system:prepareEntity(entity) + end + end + end + for id in pairs(system.entities) do + local entity = self.entities[id] + if entity then + system:update(entity, dt) + end + end + if system.postUpdate then + system:postUpdate() + end + end + end +end + + +return module diff --git a/honey.bak/ecs.test.lua b/honey.bak/ecs.test.lua new file mode 100644 index 0000000..7814097 --- /dev/null +++ b/honey.bak/ecs.test.lua @@ -0,0 +1,51 @@ +local function test(msg, f) + local success, error = pcall(f) + if success then + print(msg .. "\t\t[OK]") + else + print(msg .. "\t\t[FAIL]") + print(error) + end +end + + +local ecs = require 'ecs' +local Filter = ecs.Filter + + +test("Filter.AND correctly matches basic keys", function() + local filter = Filter.AND{"hello", "world"} + + assert(filter{hello=true} == false) + assert(filter{world=true} == false) + assert(filter{hello=true, world=true} == true) + assert(filter{asm=true, hello=true, world=true} == true) +end) +test("Filter.AND correctly matches subfilters", function() + local subfilter = Filter.AND{"hello"} + local filter = Filter.AND{subfilter, "world"} + + assert(filter{hello=true} == false) + assert(filter{world=true} == false) + assert(filter{hello=true, world=true} == true) + assert(filter{asm=true, hello=true, world=true} == true) +end) + + +test("Filter.OR correctly matches basic keys", function() + local filter = Filter.OR{"hello", "world"} + + assert(filter{hello=true} == true) + assert(filter{world=true} == true) + assert(filter{hello=true, world=true} == true) + assert(filter{asm=true} == false) +end) +test("Filter.OR correctly matches subfilters", function() + local subfilter = Filter.OR{"hello"} + local filter = Filter.OR{subfilter, "world"} + + assert(filter{hello=true} == true) + assert(filter{world=true} == true) + assert(filter{hello=true, world=true} == true) + assert(filter{asm=true} == false) +end) diff --git a/honey.bak/glm.lua b/honey.bak/glm.lua new file mode 100644 index 0000000..af8dcb3 --- /dev/null +++ b/honey.bak/glm.lua @@ -0,0 +1,361 @@ +local glm = honey.glm
+
+local module = {}
+setmetatable(module, {__index=_G})
+setfenv(1, module)
+
+
+Vec3 = {}
+Mat4 = {}
+Quaternion = {}
+
+
+--===== Vec3 =====--
+
+
+function Vec3.new(_, values)
+ local self = {}
+ self.data = glm.vec3_create()
+ setmetatable(self, Vec3)
+ if values then
+ self[1] = values[1]
+ self[2] = values[2]
+ self[3] = values[3]
+ end
+ return self
+end
+setmetatable(Vec3, {__call=Vec3.new})
+
+
+function Vec3.__index(self, key)
+ if type(key) == 'number' then
+ return glm.vec3_get(self.data, key-1)
+ else
+ return Vec3[key]
+ end
+end
+
+
+function Vec3.__newindex(self, key, value)
+ glm.vec3_set(self.data, key-1, value)
+end
+
+
+function Vec3.__tostring(self)
+ return string.format("Vec3[%.4f, %.4f, %.4f]", self[1], self[2], self[3])
+end
+
+
+--===== arithmetic =====--
+
+local function swapIfNumber(self, other)
+ if type(self) == "number" and type(other) == "table" then
+ return other, self
+ else
+ return self, other
+ end
+end
+
+
+function Vec3.__add(self, other)
+ local self, other = swapIfNumber(self, other)
+
+ local dest = Vec3()
+ if type(other) == "number" then
+ glm.vec3_adds(self.data, other, dest.data)
+ elseif type(other) == "table" then
+ glm.vec3_add(self.data, other.data, dest.data)
+ else
+ error(string.format("cannot add %s to Vec3", type(other)))
+ end
+ return dest
+end
+
+
+function Vec3.__sub(self, other)
+ local dest = Vec3()
+ if type(other) == "number" then
+ glm.vec3_subs(self.data, other, dest.data)
+ elseif type(other) == "table" then
+ glm.vec3_sub(self.data, other.data, dest.data)
+ else
+ error(string.format("cannot subtract %s from Vec3", type(other)))
+ end
+ return dest
+end
+
+
+function Vec3.__mul(self, other)
+ local self, other = swapIfNumber(self, other)
+ local dest = Vec3()
+ if type(other) == "number" then
+ glm.vec3_scale(self.data, other, dest.data)
+ elseif type(other) == "table" then
+ glm.vec3_mul(self.data, other.data, dest.data)
+ else
+ error(string.format("cannot multiply %s and Vec3", type(other)))
+ end
+ return dest
+end
+
+
+function Vec3.__div(self, other)
+ local dest = Vec3()
+ if type(other) == "number" then
+ glm.vec3_divs(self.data, other, dest.data)
+ elseif type(other) == "table" then
+ glm.vec3_div(self.data, other.data, dest.data)
+ else
+ error(string.format("cannot divide Vec3 by %s", type(other)))
+ end
+ return dest
+end
+
+
+
+
+function Vec3.copyTo(self, dest)
+ glm.vec3_copy(self.data, dest.data)
+end
+
+
+function Vec3.zero(self)
+ glm.vec3_zero(self.data)
+end
+
+
+function Vec3.zero(self)
+ glm.vec3_zero(self.data)
+end
+function Vec3.one(self)
+ glm.vec3_one(self.data)
+end
+
+
+function Vec3.dot(self, other)
+ return glm.vec3_dot(self.data, other.data)
+end
+
+
+function Vec3.crossTo(self, other, dest)
+ glm.vec3_cross(self.data, other.data, dest.data)
+end
+function Vec3.cross(self, other)
+ local dest = Vec3()
+ self:crossTo(other, dest)
+ return dest
+end
+
+
+function Vec3.crossnTo(self, other, dest)
+ glm.vec3_crossn(self.data, other.data, dest.data)
+end
+function Vec3.crossn(self, other)
+ local dest = Vec3()
+ self:crossTo(other, dest)
+ return dest
+end
+
+
+function Vec3.norm2(self)
+ return glm.vec3_norm2(self.data)
+end
+function Vec3.norm(self)
+ return glm.vec3_norm(self.data)
+end
+
+
+function Vec3.normalize(self)
+ glm.vec3_normalize(self.data)
+end
+function Vec3.normalizeTo(self, dest)
+ glm.vec3_normalize_to(self.data, dest.data)
+end
+
+
+----------------------------------------
+
+local RowLookup = {}
+function RowLookup.new(_, row, data)
+ local self = {
+ row=row,
+ data=data,
+ }
+ setmetatable(self, RowLookup)
+ return self
+end
+setmetatable(RowLookup, {__call=RowLookup.new})
+function RowLookup.__index(self, col)
+ return glm.mat4_get(self.data, col-1, self.row-1)
+end
+function RowLookup.__newindex(self, col, value)
+ return glm.mat4_set(self.data, col-1, self.row-1, value)
+end
+
+
+--===== Mat4 =====--
+
+
+function Mat4.new(_, self, values)
+ local self = {}
+ self.type = "mat4"
+ self.data = glm.mat4_create()
+ setmetatable(self, Mat4)
+ if values then
+ self[1][1] = values[1]
+ self[1][2] = values[2]
+ self[1][3] = values[3]
+ self[1][4] = values[4]
+
+ self[2][1] = values[5]
+ self[2][2] = values[6]
+ self[2][3] = values[7]
+ self[2][4] = values[8]
+
+ self[3][1] = values[9]
+ self[3][2] = values[10]
+ self[3][3] = values[11]
+ self[3][4] = values[12]
+
+ self[4][1] = values[13]
+ self[4][2] = values[14]
+ self[4][3] = values[15]
+ self[4][4] = values[16]
+ end
+ return self
+end
+setmetatable(Mat4, {__call=Mat4.new})
+
+
+function Mat4.__index(self, key)
+ if type(key) == "number" then
+ return RowLookup(key, self.data)
+ else
+ return Mat4[key]
+ end
+end
+
+
+function Mat4.__tostring(self)
+ return string.format(
+ "/ %0.4f, %0.4f, %0.4f, %0.4f \\\n" ..
+ "| %0.4f, %0.4f, %0.4f, %0.4f |\n" ..
+ "| %0.4f, %0.4f, %0.4f, %0.4f |\n" ..
+ "\\ %0.4f, %0.4f, %0.4f, %0.4f /",
+ self[1][1], self[1][2], self[1][3], self[1][4],
+ self[2][1], self[2][2], self[2][3], self[2][4],
+ self[3][1], self[3][2], self[3][3], self[3][4],
+ self[4][1], self[4][2], self[4][3], self[4][4]
+ )
+end
+
+
+function Mat4.__mul(self, other)
+ if other.type == "mat4" then
+ local dest = Mat4()
+ glm.mat4_mul(self.data, other.data, dest.data)
+ return dest
+ elseif other.type == "vec4" then
+ -- todo
+ elseif other.type == "vec3" then
+ local dest = Vec3()
+ glm.mat4_mulv3(self.data, other.data, 1.0, dest.data)
+ return dest
+ else
+ error(string.format("cannot multiply Mat4 by %s", type(other)))
+ end
+end
+
+
+function Mat4.copy(self, other)
+ glm.mat4_copy(other.data, self.data)
+ return self
+end
+function Mat4.copyTo(self, dest)
+ glm.mat4_copy(self.data, dest.data)
+ return self
+end
+
+
+function Mat4.identity(self)
+ glm.mat4_identity(self.data)
+ return self
+end
+
+
+function Mat4.zero(self)
+ glm.mat4_zero(self.data)
+ return self
+end
+
+
+function Mat4.mul(self, other)
+ glm.mat4_mul(self.data, other.data, self.data)
+ return self
+end
+
+
+function Mat4.translate(self, vec)
+ glm.translate(self.data, vec.data)
+ return self
+end
+
+
+function Mat4.rotateX(self, angle)
+ glm.rotate_x(self.data, angle, self.data)
+ return self
+end
+function Mat4.rotateY(self, angle)
+ glm.rotate_y(self.data, angle, self.data)
+ return self
+end
+function Mat4.rotateZ(self, angle)
+ glm.rotate_z(self.data, angle, self.data)
+ return self
+end
+
+
+function Mat4.scale(self, vec)
+ glm.scale(self.data, vec.data)
+ return self
+end
+
+
+function Mat4.perspective(self, fovy, aspect, near, far)
+ glm.perspective(fovy, aspect, near, far, self.data)
+ return self
+end
+function Mat4.perspectiveResize(self, aspect)
+ glm.perspective_resize(aspect, self.data)
+ return self
+end
+
+
+--===== Quaternion =====--
+
+
+Quaternion.__index = Quaternion
+
+
+function Quaternion.new(_, tbl)
+ local tbl = tbl or { 0, 0, 0, 0 }
+ local self = {}
+ self.data = glm.quat_create()
+ glm.quat_init(self.data, unpack(tbl))
+ setmetatable(self, Quaternion)
+ return self
+end
+setmetatable(Quaternion, {__call=Quaternion.new})
+
+
+function Quaternion.toMat4(self)
+ local m = Mat4()
+ glm.quat_mat4(self.data, m.data)
+ return m
+end
+
+
+---------------------------
+
+
+return module
diff --git a/honey.bak/init.lua b/honey.bak/init.lua new file mode 100644 index 0000000..6cf8801 --- /dev/null +++ b/honey.bak/init.lua @@ -0,0 +1,48 @@ +local glfw = honey.glfw +local gl = honey.gl +local window = require 'honey.window' + +local module = {} +setmetatable(module, {__index=_G}) +setfenv(1, module) + + +function init(width, height, title) + local width = width or 640 + local height = height or 480 + local title = title or "honey3d" + + glfw.Init() + glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) + glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, 4) + glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, 1) + local window = honey.Window(width, height, title) + glfw.MakeContextCurrent(window.win) + gl.InitGlad() + gl.Enable(gl.DEPTH_TEST) + + honey.ode.InitODE() + + return window +end + + +function loop(window, update) + local prevTime = 0 + while not window:shouldClose() do + local time = glfw.GetTime() + local dt = time - prevTime + prevTime = time + update(dt) + window:swapBuffers() + glfw.PollEvents() + end +end + + +function terminate() + glfw.Terminate() +end + + +return module diff --git a/honey.bak/mesh.lua b/honey.bak/mesh.lua new file mode 100644 index 0000000..430d5c3 --- /dev/null +++ b/honey.bak/mesh.lua @@ -0,0 +1,95 @@ +local mesh = {} +local gl = honey.gl +setmetatable(mesh, {__index=_G}) +setfenv(1, mesh) + + +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 = 3*vertex.vt_idx + for i=1,2 do + table.insert(vertices, attrib.texcoords[tex+i]) + end +end + + +function loadShape(shape, attrib) + 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) + table.insert(indices, #indices) + end + end + + return vertices, indices +end + + +function loadFile(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) + table.insert(meshes, Mesh(vertices, indices)) + end + return meshes +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 + + +return mesh diff --git a/honey.bak/shader.lua b/honey.bak/shader.lua new file mode 100644 index 0000000..083b260 --- /dev/null +++ b/honey.bak/shader.lua @@ -0,0 +1,118 @@ +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 + + 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 + +return module.Shader diff --git a/honey.bak/std.lua b/honey.bak/std.lua new file mode 100644 index 0000000..a08bf35 --- /dev/null +++ b/honey.bak/std.lua @@ -0,0 +1,17 @@ +local init = require 'honey.init' + +honey.init = init.init +honey.loop = init.loop +honey.terminate = init.terminate + +honey.ecs = require 'honey.ecs' +honey.standardSystems = require 'honey.ecs-systems' +honey.mesh = require 'honey.mesh' +honey.Shader = require 'honey.shader' +honey.Window = require 'honey.window' + + +local glm = require 'honey.glm' +honey.Vec3 = glm.Vec3 +honey.Mat4 = glm.Mat4 +honey.Quaternion = glm.Quaternion diff --git a/honey.bak/window.lua b/honey.bak/window.lua new file mode 100644 index 0000000..fa777d0 --- /dev/null +++ b/honey.bak/window.lua @@ -0,0 +1,160 @@ +local module = {} +local glfw = honey.glfw +setmetatable(module, {__index=_G}) +setfenv(1, module) + + +Window = {} +Window.__index = Window + + +function Window.new(_, width, height, title, monitor, share) + local monitor = monitor or glfw.monitor_NULL + local share = share or glfw.window_NULL + + local self = {} + setmetatable(self, Window) + + self.win = glfw.CreateWindow(width, height, title, monitor, share) + self.__gc = honey.util.gc_canary(function() + glfw.DestroyWindow(self.win) + end) + + return self +end +setmetatable(Window, {__call=Window.new}) + + +function Window.shouldClose(self) + return glfw.WindowShouldClose(self.win) == glfw.TRUE +end +function Window.setShouldClose(self, state) + glfw.SetWindowShouldClose(self.win, state) +end + + +function Window.setTitle(self, title) + glfw.SetWindowTitle(self.win, title) +end + + +function Window.getPos(self) + return glfw.GetWindowPos(self.win) +end +function Window.setPos(self, x, y) + glfw.SetWindowPos(self.win, x, y) +end + + +function Window.getSize(self) + return glfw.GetWindowSize(self.win) +end +function Window.setSizeLimits(self, minwidth, minheight, maxwidth, maxheight) + glfw.SetWindowSizeLimits(self.win, minwidth, minheight, maxwidth, maxheight) +end +function Window.setAspectRatio(self, numerator, denominator) + glfw.SetWindowAspectRatio(self.win, numerator, denominator) +end +function Window.setSize(self, width, height) + glfw.SetWindowSize(self.win, width, height) +end + + +function Window.getFramebufferSize(self) + return glfw.GetFramebufferSize(self.win) +end +function Window.getFrameSize(self) + return glfw.GetWindowFrameSize(self.win) +end + +function Window.getContentScale(self) + return glfw.GetWindowContentScale(self.win) +end + + +function Window.getOpacity(self) + return glfw.GetWindowOpacity(self.win) +end +function Window.setOpacity(self, opacity) + return glfw.SetWindowOpacity(self.win, opacity) +end + + +function Window.iconify(self) + return glfw.IconityWindow(self.win) +end +function Window.restore(self) + return glfw.RestoreWindow(self.win) +end +function Window.maximize(self) + return glfw.MaximizeWindow(self.win) +end +function Window.show(self) + return glfw.ShowWindow(self.win) +end +function Window.hide(self) + return glfw.HideWindow(self.win) +end +function Window.focus(self) + return glfw.FocusWindow(self.win) +end +function Window.requestAttention(self) + return glfw.RequestWindowAttention(self.win) +end + + +function Window.getMonitor(self) + return glfw.GetWindowMonitor(self.win) +end +function Window.setMonitor(self, monitor, xpos, ypos, width, height, refreshRate) + return glfw.SetWindowMonitor(self.win, monitor, xpos, ypos, width, height, refreshRate) +end + + +function Window.getAttrib(self, attrib) + return glfw.GetWindowAttrib(self.win, attrib) +end +function Window.setAttrib(self, attrib, value) + return glfw.SetWindowAttrib(self.win, attrib, value) +end + + +function Window.setPositionCallback(self, cb) + return glfw.SetWindowPosCallback(self.win, cb) +end +function Window.setSizeCallback(self, cb) + return glfw.SetWindowSizeCallback(self.win, cb) +end +function Window.setCloseCallback(self, cb) + return glfw.SetWindowCloseCallback(self.win, cb) +end +function Window.setRefreshCallback(self, cb) + return glfw.SetWindowRefreshCallback(self.win, cb) +end +function Window.setFocusCallback(self, cb) + return glfw.SetWindowFocusCallback(self.win, cb) +end +function Window.setIconifyCallback(self, cb) + return glfw.SetWindowIconifyCallback(self.win, cb) +end +function Window.setMaximizeCallback(self, cb) + return glfw.SetWindowIconifyCallback(self.win, cb) +end +function Window.setFramebufferSizeCallback(self, cb) + return glfw.SetFramebufferSizeCallback(self.win, cb) +end +function Window.setContentScaleCallback(self, cb) + return glfw.SetContentScaleCallback(self.win, cb) +end + +function Window.setKeyCallback(self, cb) + return glfw.SetKeyCallback(self.win, cb) +end + + +function Window.swapBuffers(self) + glfw.SwapBuffers(self.win) +end + + +return module.Window diff --git a/honey/ecs.lua b/honey/ecs.lua index 8aa5c5c..72cc41d 100644 --- a/honey/ecs.lua +++ b/honey/ecs.lua @@ -84,6 +84,7 @@ end setmetatable(EntityDb, {__call=EntityDb.new}) +-- check if a given entity id is legitimate function EntityDb.checkIsValid(self, id) if not self.entities[id] then error(string.format("invalid entity id: %s", tostring(id))) @@ -91,22 +92,29 @@ function EntityDb.checkIsValid(self, id) end -local function uid() - local template ='xxxx:xxxx:xxxx' - return string.gsub(template, 'x', function (c) - local v = math.random(0, 0xf) +local random = math.random +local function uuid() + local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' + return string.gsub(template, '[xy]', function (c) + local v = (c == 'x') and random(0, 0xf) or random(8, 0xb) return string.format('%x', v) end) end -function EntityDb.createEntity(self) - local id = uid() + + +-- create a new entity +function EntityDb.createEntity(self, id) + local id = id or uuid() -- allow inserting entities at preset ids for loading self.entities[id] = true return id end +-- add a component to an entity function EntityDb.addComponent(self, id, name, value) self:checkIsValid(id) + + -- create the relevant component table if it doesn't exist if not self.components[name] then self.components[name] = { count=0 } end @@ -124,6 +132,7 @@ local function shallowCopy(tbl) end +-- get all entities with a given component function EntityDb.queryComponent(self, name) local query = shallowCopy(self.components[name]) query.count = nil @@ -131,6 +140,7 @@ function EntityDb.queryComponent(self, name) end +-- get all components associated with an entity function EntityDb.queryEntity(self, id) self:checkIsValid(id) local query = {} @@ -141,6 +151,14 @@ function EntityDb.queryEntity(self, id) end +-- get a specific component from an entity +function EntityDb.getComponent(self, id, name) + self:checkIsValid(id) + return self.components[name][id] +end + + +-- remove a component from an entity function EntityDb.removeComponent(self, id, name) self:checkIsValid(id) local component = self.components[name] @@ -154,6 +172,7 @@ function EntityDb.removeComponent(self, id, name) end +-- remove an entity from the db function EntityDb.deleteEntity(self, id) self:checkIsValid(id) for name in pairs(self.components) do @@ -162,4 +181,66 @@ function EntityDb.deleteEntity(self, id) self.entities[id] = nil end + +--===== SystemDb =====-- + +SystemDb = {} +SystemDb.__index = SystemDb + + +function SystemDb.new(_) + local self = { + systems = {}, + sorted = {}, + } + setmetatable(self, SystemDb) + return self +end +setmetatable(SystemDb, {__call=SystemDb.new}) + + +local function systemId() + local template = "xx:xx:xx" + return string.gsub(template, "x", function(c) + return string.format("%x", random(0, 0xf)) + end) +end + + +function SystemDb.addSystem(self, systemFunc, params) + local system + if type(systemFunc) == "table" then + system = systemFunc + else + system = systemFunc(params) + end + + local id = systemId() + self.systems[id] = system + table.insert(self.sorted, id) + self:sort() + return id +end + + +function SystemDb.sort(self) + table.sort(self.sorted, function(a, b) return (self.systems[a].priority or 100) < (self.systems[b].priority or 100) end) +end + + +function SystemDb.update(self, dt) + for _, system in ipairs(self.sorted) do + self.systems[system]:update(dt) + end +end + + +function SystemDb.removeSystem(self, id) + self.systems[id] = nil + for i=#self.sorted,1,-1 do + if self.sorted[i] == id then table.remove(self.sorted, i) end + end +end + + return module diff --git a/honey/ecs.test.lua b/honey/ecs.test.lua index 0577dcd..74e27dc 100644 --- a/honey/ecs.test.lua +++ b/honey/ecs.test.lua @@ -229,4 +229,52 @@ test("EntityDb.deleteEntity() correctly removes an entity", function() end) +--===== SystemDb tests =====-- + +local SystemDb = ecs.SystemDb + +test("addSystem() correctly sorts systems", function() + local sdb = SystemDb(nil) + local str = "" + sdb:addSystem(function () return { + update=function(self, dt) str = str .. "c" end, + priority = 3, + } end) + sdb:addSystem{ + update=function(self, dt) str = "a" end, + priority = 1, + } + sdb:addSystem{ + update=function(self, dt) str = str .. "b" end, + priority = 2, + } + sdb:update(0) + assert(str == "abc") +end) + + +test("removeSystem() correctly handles things", function() + local sdb = SystemDb(nil) + local str = "" + sdb:addSystem(function () return { + update=function(self, dt) str = str .. "c" end, + priority = 3, + } end) + sdb:addSystem{ + update=function(self, dt) str = "a" end, + priority = 1, + } + local id = sdb:addSystem{ + update=function(self, dt) str = str .. "b" end, + priority = 2, + } + sdb:update(0) + assert(str == "abc") + + sdb:removeSystem(id) + sdb:update(1) + assert(str == "ac") +end) + + print(string.format("ran %d tests, %d failed", testCount, failCount)) diff --git a/honey/mesh.lua b/honey/mesh.lua index 430d5c3..e46c399 100644 --- a/honey/mesh.lua +++ b/honey/mesh.lua @@ -1,7 +1,7 @@ -local mesh = {} +local module = {} local gl = honey.gl -setmetatable(mesh, {__index=_G}) -setfenv(1, mesh) +setmetatable(module, {__index=_G}) +setfenv(1, module) local function insertVertex(vertices, attrib, vertex) @@ -54,6 +54,15 @@ function loadFile(filename) end +local meshCache = {} +function loadMesh(filename, index) + if not meshCache[filename] then + meshCache[filename] = loadFile(filename) + end + return meshCache[filename][index] +end + + Mesh = {} Mesh.__index = Mesh @@ -92,4 +101,4 @@ function Mesh.drawElements(self) end -return mesh +return module diff --git a/honey/std.lua b/honey/std.lua index a08bf35..2a5d2c2 100644 --- a/honey/std.lua +++ b/honey/std.lua @@ -5,7 +5,7 @@ honey.loop = init.loop honey.terminate = init.terminate honey.ecs = require 'honey.ecs' -honey.standardSystems = require 'honey.ecs-systems' +--honey.standardSystems = require 'honey.ecs-systems' honey.mesh = require 'honey.mesh' honey.Shader = require 'honey.shader' honey.Window = require 'honey.window' @@ -1,28 +1,4 @@ require 'honey.std' -local paused = true -local balls = {} - -local function formatize(f) - return function(fmt, ...) - f(string.format(fmt, ...)) - end -end -local printf = formatize(print) -local log = honey.log -log.fatal = formatize(log.fatal) -log.error = formatize(log.error) -log.warn = formatize(log.warn) -log.info = formatize(log.info) -log.debug = formatize(log.debug) -log.trace = formatize(log.trace) - -log.info( - "honey v%d.%d.%d -- %s", - honey.version.major, - honey.version.minor, - honey.version.patch, - honey.version.string -) local glfw = honey.glfw local gl = honey.gl @@ -43,80 +19,14 @@ local vg = nvg.CreateContext() local november = nvg.CreateFont(vg, "November", "assets/november.regular.ttf") local vw, vh = 640, 480 + -- create camera matrices local camera = { view=Mat4():identity():translate(Vec3{0, 0, -30}), projection=Mat4():perspective(math.rad(45), 640/480, 0.1, 100), } --- setup ecs -local level = ecs.Level() -level:addSystem(systems.transformCascade) -level:addSystem(systems.renderCam(camera)) -level:addSystem(systems.update) - --- physics system -local world = ode.WorldCreate() -local space = ode.HashSpaceCreate(ode.Space0) -level:addSystem{ - setup=function(self) - self.time = 0 - self.world=world - self.space=space - ode.WorldSetGravity(self.world, 0, -100, 0) - ode.WorldSetCFM(self.world, 1e-5) - self.contactgroup = ode.JointGroupCreate(200) - self.__gc = honey.util.gc_canary(function() - ode.WorldDestroy(self.world) - ode.SpaceDestroy(self.space) - end) - end, - filter = ecs.Filter.AND{"transform", "collisionShape", "physicsBody"}, - preUpdate = function(self) - self.dt = nil - if self.time > 0.01 then - self.time = self.time - 0.01 - ode.SpaceCollide(self.space, function(o1, o2) - local b1 = ode.GeomGetBody(o1) - local b2 = ode.GeomGetBody(o2) - local contact = ode.CreateContact{ - surface = { - mode = ode.ContactBounce + ode.ContactSoftCFM, - mu = ode.Infinity, - bounce = 0.90, - bounce_vel = 0.1, - soft_cfm = 0.001, - }, - } - local collisions = ode.Collide(o1, o2, 1) - if #collisions > 0 then - local e1 = ode.GeomGetData(o1) - local e2 = ode.GeomGetData(o2) - ode.ContactSetGeom(contact, collisions[1]) - local joint = ode.JointCreateContact(self.world, self.contactgroup, contact) - ode.JointAttach(joint, b1, b2) - end - end) - ode.WorldQuickStep(self.world, 0.01) - ode.JointGroupEmpty(self.contactgroup) - end - end, - update = function(self, entity, dt) - if not self.dt then - self.dt = dt - self.time = self.time + dt - end - local x, y, z = ode.BodyGetPosition(entity.physicsBody) - local d, a, b, c = ode.BodyGetQuaternion(entity.physicsBody) - entity.transform:identity():translate(Vec3{x, y, z}):mul(Quaternion{a, b, c, d}:toMat4()) - end, - onRemoveEntity = function(self, id, entity) - print("remove", id) - ode.GeomDestroy(entity.collisionShape) - ode.BodyDestroy(entity.physicsBody) - end, -} -- create shader local shader = honey.Shader{ @@ -132,97 +42,12 @@ local octa = honey.mesh.loadFile("assets/octahedron.obj")[1] local dodeca = honey.mesh.loadFile("assets/dodecahedron.obj")[1] local icosa = honey.mesh.loadFile("assets/icosahedron.obj")[1] --- update function for each entity -function updateTransform(self, dt) - self.transform:rotateY(0.3 * math.pi * dt) - self.transform:rotateX(0.1 * math.pi * dt) -end - --- create entities -function growLine(prev, depth) - if depth == 0 then return prev end - - local entity = { - transform=Mat4():identity():translate(Vec3{2, 0, 0}), - parent=false, - mesh=octa, - shader=shader, - update=updateTransform, - } - prev.parent = entity - level:addEntity(prev) - return growLine(entity, depth-1) -end - -local leaf = { - transform=Mat4():identity():translate(Vec3{2, 0, 0}), - parent=false, - mesh=tetra, - shader=shader, -} -local root = growLine(leaf, 24) -root.update = function(self, dt) - self.transform:rotateY(0.2 * math.pi * dt) -end -level:addEntity(root) - -local groundPlane = { - transform=Mat4():identity():translate(Vec3{0, -2, 0}):scale(Vec3{10, 10, 10}), - parent=false, - mesh=plane, - shader=shader, -} -level:addEntity(groundPlane) - - -local function createNewBall() - local ball = { - transform=Mat4():identity(), - parent=false, - mesh=icosa, - shader=shader, - collisionShape=ode.CreateSphere(space, 1.0), - physicsBody=ode.BodyCreate(world), - } - - local id = level:addEntity(ball) - ball.id = id - table.insert(balls, id) - if #balls > 200 then - level:removeEntity(table.remove(balls, 1)) - end - - local mass = ode.MassCreate() - ode.MassSetSphere(mass, 1, 0.5) - ode.BodySetMass(ball.physicsBody, mass) - local function tiny() return (6 * math.random()) - 3 end - local x, y, z = tiny(), tiny(), tiny() - ode.GeomSetBody(ball.collisionShape, ball.physicsBody) - ode.BodySetPosition(ball.physicsBody, x, y, z) - ode.BodySetLinearVel(ball.physicsBody, x, y, z) - ode.GeomSetData(ball.collisionShape, ball) -end -createNewBall() - -ode.CreatePlane(space, 0, 1, 0, -2) -- close window on ESCAPE key window:setKeyCallback(function(_, key, scancode, action) if action == glfw.PRESS then if key == glfw.KEY_ESCAPE then window:setShouldClose(true) - elseif key == glfw.KEY_SPACE then - paused = not paused - elseif - key == glfw.KEY_Z or - key == glfw.KEY_X or - key == glfw.KEY_C or - key == glfw.KEY_V or - key == glfw.KEY_B or - key == glfw.KEY_N or - key == glfw.KEY_M - then - createNewBall() end end end) @@ -248,15 +73,47 @@ function averager(memory) end local fpsAverage = averager(200) + +-- setup ecs +local edb = ecs.EntityDb() +local sdb = ecs.SystemDb() + +sdb:addSystem{ + update = function(self, dt) + local entities = self.db:queryComponent("renderMesh") + for entity, tbl in pairs(entities) do + tbl.shader:use() + tbl.shader:configure{matrix={ + view=camera.view, + projection=camera.projection, + model=Mat4():identity(), + }} + local mesh = honey.mesh.loadMesh(tbl.mesh.filename, tbl.mesh.index) + mesh:drawElements() + end + end, + db = edb, + priority = 99, +} + + +local id = edb:createEntity() +edb:addComponent(id, "renderMesh", { + shader = shader, + mesh = { filename="assets/icosahedron.obj", index=1 }, +}) + + + -- main loop honey.loop(window, function(dt) gl.ClearColor(0.2, 0.4, 1.0, 1.0) gl.Clear(gl.COLOR_BUFFER_BIT + gl.DEPTH_BUFFER_BIT + gl.STENCIL_BUFFER_BIT) - gl.UseProgram(0) gl.Enable(gl.DEPTH_TEST) + gl.Disable(gl.CULL_FACE) - level:update(dt, paused) + sdb:update(dt) nvg.BeginFrame(vg, vw, vh, 1.0) nvg.StrokeColor(vg, nvg.RGBf(1, 1, 1)) nvg.FontFace(vg, "November") |