local ecs = require 'honey.ecs' local glm = require 'honey.glm' local Vec3 = glm.Vec3 local Quaternion = glm.Quaternion local gl = honey.gl local glfw = honey.glfw local ode = honey.ode 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, priority = 1, 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, } 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, priority=0, update=function(self, dt) local query = self.db:queryComponent("physics") for id, physics in pairs(query) do if not physics._body then physics._body = ode.BodyCreate(self.world) local mass = ode.MassCreate() local class = physics.mass.class if not class then -- configure mass manually elseif class == "sphere" then ode.MassSetSphere( mass, physics.mass.density, physics.mass.radius ) end ode.BodySetMass(physics._body, mass) local m = self.db:getComponent(id, "transform").matrix ode.BodySetPosition( physics._body, m[1][4], m[2][4], m[3][4] ) ode.BodySetRotation( physics._body, m[1][1], m[1][2], m[1][3], m[2][1], m[2][2], m[2][3], m[3][1], m[3][2], m[3][3] ) ode.BodySetLinearVel( physics._body, physics.velocity[1], physics.velocity[2], physics.velocity[3] ) ode.BodySetAngularVel( physics._body, physics.angularVelocity[1], physics.angularVelocity[2], physics.angularVelocity[3] ) end end 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) local transform = self.db:getComponent(id, "transform") local q = Quaternion{a,b,c,d} transform.matrix :identity() :translate(Vec3{x,y,z}) :mul(Quaternion{a,b,c,d}:toMat4()) end end end, } end return module