local ecs = require 'honey.ecs' local glm = require 'honey.glm' local Mat4 = glm.Mat4 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 = 2, 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 draw(model, view, projection, textures, shader, mesh) shader:use() -- bind textures local texOffset = 0 for name, texTbl in pairs(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 shader:configure{ float={ time=glfw.GetTime(), }, matrix={ view=view, projection=projection, model=model, }, } -- draw mesh mesh:drawElements() -- unbind textures for i=0,texOffset-1 do gl.BindTexture(gl.TEXTURE_2D + i, 0) end end function renderCamera(params) return { db = params.db, priority = params.priority or 99, update = function(self, dt) for id, camera in pairs(self.db:queryComponent("camera")) do local projection = camera.projection local cameraTransform = self.db:getComponent(id, "transform") local view = Mat4() if cameraTransform then honey.glm.mat4_inv(cameraTransform._matrix.data, view.data) else view:identity() end local entities = self.db:queryComponent("renderMesh") for entity, tbl in pairs(entities) do -- get model local transform = self.db:getComponent(entity, "transform") local model = (transform and transform._matrix) or Mat4():identity() -- get shader local shader = honey.shader.loadShader( tbl.shader.vertex, tbl.shader.fragment ) -- get mesh local mesh = honey.mesh.loadCached( tbl.mesh.filename, tbl.mesh.index ) draw(model, view, projection, tbl.textures, shader, mesh) end entities = self.db:queryComponent("renderQuad") local quadmesh = honey.mesh.loadCached("builtin.quad", 1) for entity, tbl in pairs(entities) do -- get model local model = Mat4():identity() -- get shader local shader = honey.shader.loadShader( tbl.shader.vertex, tbl.shader.fragment ) draw(model, view, projection, tbl.textures, shader, quadmesh) 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.016 local groupSize = params.groupSize or 20 local refs = {} return { db=params.db, space=params.space, world=params.world, contactGroup=ode.JointGroupCreate(groupSize), time=interval, priority=1, update=function(self, dt) for i, ref in ipairs(refs) do print(i, ref.tbl, ref.physics) end local query = self.db:queryComponent("physics") for id, physics in pairs(query) do if not physics._body then print("add physics body for "..id) local body = ode.BodyCreate(self.world) physics._gc = honey.util.gc_canary(function() print("releasing physics body for " .. id) ode.BodyDestroy(body) body = nil end) local collision = self.db:getComponent(id, "collision") if collision then print(id, collision.class) ode.GeomSetBody(collision._geom, body) end 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 ) elseif class == "capsule" then ode.MassSetCapsule( mass, physics.mass.density, physics.mass.direction, physics.mass.radius, physics.mass.length ) end ode.BodySetMass(body, mass) local m = self.db:getComponent(id, "transform").matrix ode.BodySetPosition( body, m[1][4], m[2][4], m[3][4] ) ode.BodySetRotation( 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] ) local vel = physics.velocity or Vec3{0,0,0} ode.BodySetLinearVel( body, vel[1], vel[2], vel[3] ) physics.velocity = vel local avel = physics.angularVelocity or Vec3{0,0,0} ode.BodySetAngularVel( body, avel[1], avel[2], avel[3] ) physics.angularVelocity = avel if physics.maxAngularSpeed then ode.BodySetMaxAngularSpeed(body, physics.maxAngularSpeed) end physics._body = body 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.GeomGetBody(a) local bodyB = ode.GeomGetBody(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()) local vel = physics.velocity vel[1], vel[2], vel[3] = ode.BodyGetLinearVel(physics._body) local avel = physics.angularVelocity avel[1], avel[2], avel[3] = ode.BodyGetAngularVel(physics._body) end end end, } end --===== collision space =====-- local function createGeom(self, id, collision) local geom if collision.class == "sphere" then geom = ode.CreateSphere(self.space, collision.radius) elseif collision.class == "capsule" then geom = ode.CreateCapsule(self.space, collision.radius, collision.length) elseif collision.class == "plane" then local transform = self.db:getComponent(id, "transform") local m = transform.matrix local normal = transform.matrix:mulv3(Vec3{0,1,0}):normalize() local position = Vec3{m[1][4], m[2][4], m[3][4]} print(position) local d = normal:dot(position) print(normal, d) geom = ode.CreatePlane(self.space, normal[1], normal[2], normal[3], d) end collision._geom = geom collision._gc = honey.util.gc_canary(function() print("release geom for id"..id) ode.GeomDestroy(geom) end) end function collision(params) local db = params.db local space = params.space return { db=db, space=space, priority=0, update = function(self, dt) local query = self.db:queryComponent("collision") for id, collision in pairs(query) do if not collision._geom then createGeom(self, id, collision) print(id, collision._geom) end end end } end return module