summaryrefslogtreecommitdiff
path: root/honey/ecs/physics.lua
blob: 9b22bd7fbae89aaefa3e147cce0f4f7b27a33816 (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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
local glm = require 'honey.glm'
local Vec3       = glm.Vec3
local Mat4       = glm.Mat4
local Quaternion = glm.Quaternion
local ode = honey.ode

local module = {}
setmetatable(module, {__index=_G})
setfenv(1, module)


-- create a new mass
local function createMass(tbl)
	local mass = ode.MassCreate()
	local class = tbl.class
	if not class then
		-- configure mass manually
	elseif class == "sphere" then
		ode.MassSetSphere(
			mass, 
			tbl.density, 
			tbl.radius
		)
	elseif class == "capsule" then
		ode.MassSetCapsule(
			mass,
			tbl.density,
			tbl.direction,
			tbl.radius,
			tbl.length
		)
	end
	return mass
end


-- create a new physics body
local function createPhysicsBody(db, world, id, component)
	-- initialize the body with garbage collector
	print("add component body for "..id)
	local body = ode.BodyCreate(world)
	component._gc = honey.util.gc_canary(function() 
		print("releasing component body for " .. id)
		ode.BodyDestroy(body) 
		body = nil
	end)

	-- connect the body with a collision geom
	local collision = db:getComponent(id, "collision")
	if collision then
		print(id, collision.class)
		ode.GeomSetBody(collision._geom, body)
	end

	-- set the mass of the body
	ode.BodySetMass(body, createMass(component.mass))

	-- set the transform of the body
	local m = db:getComponent(id, "node").matrix
	ode.BodySetPosition(
		body,
		m[1][4], m[2][4], m[3][4]
	)
	ode.BodySetRotation(
		body,
		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]
	)

	-- set the linear velocity
	local vel = component.velocity or Vec3{0,0,0}
	ode.BodySetLinearVel(
		body, vel[1], vel[2], vel[3]
	)
	component.velocity = vel

	-- set the angular velocity
	local avel = component.angularVelocity or Vec3{0,0,0}
	ode.BodySetAngularVel(
		body, avel[1], avel[2], avel[3]
	)
	component.angularVelocity = avel

	-- optionally set a maximum angular speed
	if component.maxAngularSpeed then
		ode.BodySetMaxAngularSpeed(body, component.maxAngularSpeed)
	end

	-- put the body into the physics component
	component._body = body
end


local function handleCollision(db, self, other)
	local handler = db:getComponent(self, "onCollision")
	if handler then
		handler(db, self, other)
	end
end


local function collide(self, a, b, collision)
	-- check for collision handlers
	local idA = ode.GeomGetData(a)
	local idB = ode.GeomGetData(b)
	handleCollision(self.db, idA, idB)
	handleCollision(self.db, idB, idA)

	-- set up the joint params
	local contact = ode.CreateContact{ surface={
		mode = ode.ContactBounce + ode.ContactSoftCFM,
		mu = ode.Infinity,
		bounce = 0.90,
		bounce_vel = 0.1,
		soft_cfm = 0.001,
	}}
	ode.ContactSetGeom(contact, collision)
	-- create the joint
	local joint = ode.JointCreateContact(
		self.world,
		self.contactGroup,
		contact
	)
	-- attach the two bodies
	local bodyA = ode.GeomGetBody(a)
	local bodyB = ode.GeomGetBody(b)
	ode.JointAttach(joint, bodyA, bodyB)
end

--===== physics =====--


system = function(params)
	local interval = params.interval or 0.016
	local groupSize = params.groupSize or 20
	local refs = {}
	return {
		db=params.db,
		space=params.space,
		world=params.world,
		contactGroup=ode.JointGroupCreate(groupSize),
		time=interval,

		priority=1,
		update=function(self, dt)
			for i, ref in ipairs(refs) do
				print(i, ref.tbl, ref.physics)
			end
			local query = self.db:queryComponent("physics")

			-- check for new physics entities
			for id, physics in pairs(query) do
				if not physics._body then
					createPhysicsBody(self.db, self.world, id, physics)
				end
			end

			self.time = self.time + dt
			-- only run the update every [interval] seconds
			if self.time > interval then
				self.time = self.time - interval

				-- check for near collisions between geoms
				ode.SpaceCollide(self.space, function(a, b)
					-- check for actual collisions
					local collisions = ode.Collide(a, b, 1)
					if #collisions > 0 then
						collide(self, a, b, collisions[1])
					end
				end)
				-- update the world
				ode.WorldQuickStep(self.world, interval)
				-- remove all contact joints
				ode.JointGroupEmpty(self.contactGroup)

				-- update entity nodes
				for id, physics in pairs(query) do
					local x,y,z = ode.BodyGetPosition(physics._body)
					local d,a,b,c = ode.BodyGetQuaternion(physics._body)
					local node = self.db:getComponent(id, "node")
					local q = Quaternion{a,b,c,d}
					node.matrix
						:identity()
						:translate(Vec3{x,y,z})
						:mul(Quaternion{a,b,c,d}:toMat4())

					local vel = physics.velocity
					vel[1], vel[2], vel[3] = ode.BodyGetLinearVel(physics._body)
					local avel = physics.angularVelocity
					avel[1], avel[2], avel[3] = ode.BodyGetAngularVel(physics._body)
				end
			end
		end,
	}
end



return module