local ode = honey.ode local ecs = require 'honey.ecs.ecs' local node = require 'honey.ecs.node' local script = require 'honey.ecs.script' local glm = require 'honey.glm' local Vec3 = glm.Vec3 local module = {} setmetatable(module, {__index=_G}) setfenv(1, module) --[[ collisions are split into two systems: geom updates and transform updates. ]] local function createGeom(space, id, collision) local class = collision.class local geom if class == "sphere" then geom = ode.CreateSphere(space, collision.radius) elseif class == "box" then geom = ode.CreateBox(space, collision.lx, collision.ly, collision.lz) elseif class == "plane" then geom = ode.CreatePlane(space, collision.a, collision.b, collision.c, collision.d) elseif class == "capsule" then geom = ode.CreateCapsule(space, collision.radius, collision.length) elseif class == "cylinder" then geom = ode.CreateCylinder(space, collision.radius, collision.length) elseif class == "ray" then geom = ode.CreateRay(space, collision.length) end ode.GeomSetData(geom, id) collision._geom = geom collision._gc = honey.util.gc_canary(function() ode.GeomDestroy(geom) end) end local function updateGeom(collision) local geom = collision._geom ode.GeomSetCategoryBits(geom, collision.category or 0x1) ode.GeomSetCollideBits(geom, collision.collide or 0xffffffff) local class = collision.class if class == "sphere" then ode.GeomSphereSetRadius(geom, collision.radius) elseif class == "box" then ode.GeomBoxSetLengths(geom, collision.lx, collision.ly, collision.lz) elseif class == "plane" then ode.GeomPlaneSetParams(geom, collision.a, collision.b, collision.c, collision.d) elseif class == "capsule" then ode.GeomCapsuleSetParams(geom, collision.radius, collision.length) elseif class == "cylinder" then ode.GeomCylinderSetParams(geom, collision.radius, collision.length) elseif class == "ray" then GeomRaySetLength(geom, collision.length) end end local updateGeom = ecs.System("collisionGeoms", function(db, dt, p) for id, collision in pairs(db:queryComponent("collision")) do if not collision._geom then createGeom(p.space, id, collision) end updateGeom(collision) end end) --===== update transform & collide =====-- local function isPlaceable(collision) if collision.class == "plane" then return false end return true end local function updateGeomTransform(collision, node) local geom = collision._geom local m = node._matrix ode.GeomSetPosition(geom, m[1][4], m[2][4], m[3][4]) ode.GeomSetRotation(geom, 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] ) end local function runCollisionScript(db, id, other, collisions) local handler = db:getComponent(id, "onCollision") if handler then local f = script.getFunction(handler) f(db, id, other, collisions) end end local updateTransform = ecs.System("collisionTransform", function(db, dt, p) local query = db:queryComponent("collision") -- update transforms for id, collision in pairs(query) do if isPlaceable(collision) then local node = db:getComponent(id, "node") if node then updateGeomTransform(collision, node) end end end -- compute and handle collisions ode.SpaceCollide(p.space, function(geomA, geomB) local collisions = ode.Collide(geomA, geomB, p.maxPoints or 1) if #collisions > 0 then -- get entity ids local idA = ode.GetData(geomA) local idB = ode.GetData(geomB) -- run handlers, if any runCollisionScript(db, idA, idB, collisions) runCollisionScript(db, idB, idA, collisions) end end) end) updateTransform:addDependencies(node.system) updateTransform:addDependencies({updateGeom}) system = { updateGeom, updateTransform } return module