local ecs = require 'honey.ecs' local gl = honey.gl local glfw = honey.glfw local module = {} setmetatable(module, {__index=_G}) setfenv(1, module) -- helper function for retrieving script functions local function getFunction(script) local f = require(script.script) if script.func then return f[script.func] else return f end end --===== dispatch messages to handlers =====-- dispatch = function(entities, msg, data) local query = entities:queryComponent(msg) for id, handler in pairs(query) do local f = getFunction(handler) f(entities, id, data) end end --===== transform cascading =====-- transform = function(params) return { db = params.db, update = function(self, dt) local entities = self.db:queryComponent("transform") -- prepare transforms for id, transform in pairs(entities) do transform._visited = false end -- helper function local function recursiveTransform(transform) if transform._visited then return transform._matrix end if not transform.parent then transform._matrix = transform.matrix else local parentTransform = self.db:getComponent(transform.parent, "transform") local parentMatrix = recursiveTransform(parentTransform) transform._matrix = parentMatrix * transform.matrix end transform._visited = true return transform._matrix end -- compute transforms for id, transform in pairs(entities) do recursiveTransform(transform) end end, priority = 0, } end --===== rendering =====-- function renderCamera(params) return { camera = params.camera, db = params.db, priority = params.priority or 99, update = function(self, dt) local cameraParams = self.db:getComponent(self.camera, "camera") local cameraTransform = self.db:getComponent(self.camera, "transform") local view if cameraTransform then view = cameraTransform._matrix else view = Mat4():identity() end local entities = self.db:queryComponent("renderMesh") for entity, tbl in pairs(entities) do -- get shader local shader = honey.shader.loadShader(tbl.shader.vertex, tbl.shader.fragment) shader:use() -- bind textures local texOffset = 0 for name, texTbl in pairs(tbl.textures or {}) do local texture = honey.image.loadImage(texTbl.filename, texTbl.params) gl.BindTexture(gl.TEXTURE_2D + texOffset, texture.texture) shader:setInt(name, texOffset) texOffset = texOffset + 1 end -- configure default uniforms local query = self.db:getComponent(entity, "transform") local model = (query and query._matrix) or Mat4():identity() shader:configure{ float={ time=glfw.GetTime(), }, matrix={ view=view, projection=cameraParams.projection, model=model, }, } -- draw mesh local mesh = honey.mesh.loadMesh(tbl.mesh.filename, tbl.mesh.index) mesh:drawElements() -- unbind textures for i=0,texOffset-1 do gl.BindTexture(gl.TEXTURE_2D + i, 0) end end end, } end --===== script system =====-- script = function(params) return { db=params.db, update=function(self, dt) local entities = self.db:queryComponent("script") for id, script in pairs(entities) do local f = getFunction(script) f(self.db, id, dt) end end } end --===== physics =====-- physics = function(params) local interval = params.interval or 0.01 local groupSize = params.groupSize or 20 return { db=params.db, space=params.space, world=params.world, contactGroup=ode.JointGroupCreate(groupSize), time=interval, update=function(self, dt) local query = self.db:queryComponent("physics") -- TODO: create physics bodies self.time = self.time + dt -- only run the update every [interval] seconds if self.time > interval then self.time = self.time - interval -- check for near collisions between geoms ode.SpaceCollide(self.space, function(a, b) -- check for actual collisions local collisions = ode.Collide(a, b, 1) if #collisions > 0 then -- set up the joint params local contact = ode.CreateContact{ surface={ mode = ode.ContactBounce + ode.ContactSoftCFM, mu = ode.Infinity, bounce = 0.90, bounce_vel = 0.1, soft_cfm = 0.001, }} ode.ContactSetGeom(contact, collisions[1]) -- create the joint local joint = ode.JointCreateContact( self.world, self.contactgroup, contact ) -- attach the two bodies local bodyA = ode.GeomGetData(a) local bodyB = ode.GeomGetData(b) ode.JointAttach(joint, bodyA, bodyB) end end) -- update the world ode.WorldQuickStep(self.world, interval) -- remove all contact joints ode.JointGroupEmpty(self.contactGroup) -- update entity transforms for id, physics in pairs(query) do local x,y,z = ode.BodyGetPosition(physics._body) local d,a,b,c = ode.BodyGetQuaternion(physics._body) entity.transform :identity() :translate(Vec3{x,y,z}) :mul(Quaternion{a,b,c,d}:toMat4()) end end end, } end return module