From 52c760d39397bb5261981892e2ae2a87e21a2b3f Mon Sep 17 00:00:00 2001 From: sanine Date: Tue, 16 May 2023 16:01:26 -0500 Subject: refactor collision system --- honey/ecs/collision.lua | 187 +++++++++++++++++++++++++++--------------------- honey/std.lua | 1 + main.lua | 2 + 3 files changed, 110 insertions(+), 80 deletions(-) diff --git a/honey/ecs/collision.lua b/honey/ecs/collision.lua index 46028b4..f34c320 100644 --- a/honey/ecs/collision.lua +++ b/honey/ecs/collision.lua @@ -1,112 +1,139 @@ -local mesh = require 'honey.mesh' +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 ode = honey.ode local module = {} setmetatable(module, {__index=_G}) setfenv(1, module) -local function loadTriMesh(space, filename) - local attrib, shapes, _ = honey.tinyobj.parse_obj( - filename, honey.tinyobj.FLAG_TRIANGULATE - ) - local vertices, indices = mesh.loadShape(shapes[1], attrib) +--[[ + collisions are split into two systems: geom updates and transform updates. +]] - trimeshdata = ode.GeomTriMeshDataCreate() - ode.GeomTriMeshDataBuild(trimeshdata, vertices, indices) - return ode.CreateTriMesh(space, trimeshdata) -end - ---===== collision space =====-- - - -local function createGeom(self, id, collision) +local function createGeom(space, id, collision) + local class = collision.class 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 node = self.db:getComponent(id, "node") - local m = node.matrix - local normal = node.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) - elseif collision.class == "ray" then - geom = ode.CreateRay(self.space, collision.length) - elseif collision.class == "trimesh" then - geom = loadTriMesh(self.space, collision.filename) + 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.GeomSetCategoryBits(geom, collision.category or 1) - ode.GeomSetCollideBits(geom, collision.collide or 0xffffffff) ode.GeomSetData(geom, id) - collision._geom = geom collision._gc = honey.util.gc_canary(function() - print("release geom for id"..id) 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, collision) + end + updateGeom(collision) + end +end) + + +--===== update transform & collide =====-- + local function isPlaceable(collision) - if collision.class == "ray" then - return true - elseif collision.class == "trimesh" then - return true + 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 - return false end -system = function(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 - if - not self.db:getComponent(id, "physics") and - isPlaceable(collision) - then - -- no attached physics body, update position & orientation - -- from node transform - local node = self.db:getComponent(id, "node") - local m = node._matrix or node.matrix - local geom = collision._geom - 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 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 + 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 diff --git a/honey/std.lua b/honey/std.lua index 8b70699..0132a58 100644 --- a/honey/std.lua +++ b/honey/std.lua @@ -10,6 +10,7 @@ honey.ecs = require 'honey.ecs.ecs' honey.ecs.node = require 'honey.ecs.node' honey.ecs.render = require 'honey.ecs.render' honey.ecs.script = require 'honey.ecs.script' +honey.ecs.collision = require 'honey.ecs.collision' -- glm is so frequently used that we load it globally, not locally diff --git a/main.lua b/main.lua index 60cc174..b7562e2 100644 --- a/main.lua +++ b/main.lua @@ -8,10 +8,12 @@ honey.init() local db = honey.ecs.EntityDb() local systems = honey.ecs.SystemDb(db) +local space = honey.ode.HashSpaceCreate(honey.ode.Space0) systems:addSystems(honey.ecs.node.system) systems:addSystems(honey.ecs.render.system) systems:addSystems(honey.ecs.script.system) +systems:addSystems(honey.ecs.collision.system, { space=space }) -- camera db:createEntityWithComponents{ -- cgit v1.2.1