summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2023-05-16 16:01:26 -0500
committersanine <sanine.not@pm.me>2023-05-16 16:01:26 -0500
commit52c760d39397bb5261981892e2ae2a87e21a2b3f (patch)
tree56fe282969f3c89eb9ce46fd7701b0dc9619f843
parentb62b5ddd0cb781a7f83e489b1d5838a32e4b109b (diff)
refactor collision system
-rw-r--r--honey/ecs/collision.lua187
-rw-r--r--honey/std.lua1
-rw-r--r--main.lua2
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{