summaryrefslogtreecommitdiff
path: root/honey/ecs/collision.lua
blob: 54598cbe7236a1e6a231d07f41bf929e12f2b236 (plain)
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
		ode.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.GeomGetData(geomA)
			local idB = ode.GeomGetData(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