1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
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
|