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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
|
local glm = require 'honey.glm'
local Vec3 = glm.Vec3
local Mat4 = glm.Mat4
local Quaternion = glm.Quaternion
local ode = honey.ode
local script = require 'honey.ecs.script'
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
if tbl.mass then
ode.MassSetCapsuleTotal(
mass,
tbl.mass,
tbl.direction,
tbl.radius,
tbl.length
)
else
ode.MassSetCapsule(
mass,
tbl.density,
tbl.direction,
tbl.radius,
tbl.length
)
end
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, collision)
local handler = db:getComponent(self, "onCollision")
if handler then
h = script.getFunction(handler)
h(db, self, other, collision)
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, collision)
handleCollision(self.db, idB, idA, collision)
local physicsA = self.db:getComponent(idA, "physics")
local physicsB = self.db:getComponent(idB, "physics")
local surface = (physicsA and physicsA.surface) or
(physicsB and physicsB.surface)
if surface then
-- 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
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
|