bulldog_physics 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,362 @@
1
+ module BulldogPhysics
2
+ class Contact
3
+
4
+
5
+ # Holds the bodies that are involved in the contact. The
6
+ # second of these can be NULL, for contacts with the scenery.
7
+ attr_accessor :body
8
+
9
+ # Holds the lateral friction coefficient at the contact.
10
+ attr_accessor :friction
11
+
12
+ # Holds the normal restitution coefficient at the contact.
13
+ attr_accessor :restitution
14
+
15
+ # Holds the position of the contact in world coordinates.
16
+ attr_accessor :contact_point
17
+
18
+ # Holds the direction of the contact in world coordinates.
19
+ attr_accessor :contact_normal
20
+
21
+ # Holds the depth of penetration at the contact point. If both
22
+ # bodies are specified then the contact point should be midway
23
+ # between the inter-penetrating points.
24
+ attr_accessor :penetration
25
+
26
+
27
+ # A transform matrix that converts co-ordinates in the contact's
28
+ # frame of reference to world co-ordinates. The columns of this
29
+ # matrix form an orthonormal set of vectors.
30
+ attr_accessor :contact_to_world
31
+
32
+ # Holds the closing velocity at the point of contact. This is set
33
+ # when the calculateInternals function is run.
34
+ attr_accessor :contact_velocity
35
+
36
+ # Holds the required change in velocity for this contact to be
37
+ # resolved.
38
+ attr_accessor :desired_delta_velocity
39
+
40
+ # Holds the world space position of the contact point relative to
41
+ # centre of each body. This is set when the calculateInternals
42
+ # function is run.
43
+ attr_accessor :relative_contact_position
44
+
45
+
46
+
47
+ def initialize()
48
+ @body = Array.new(2)
49
+ @contact_normal = Vector3.new
50
+ @contact_point = Vector3.new
51
+ @contact_to_world = Matrix3.new
52
+ @contact_velocity = Vector3.new
53
+ @desired_delta_velocity = Vector3.new
54
+ @relative_contact_position = [ Vector3.new, Vector3.new ]
55
+ @penetration = 0.0
56
+ set_body_data(RigidBody.new, RigidBody.new, 1.0, 0.96)
57
+ end
58
+
59
+ def set_body_data(one, two, friction, restitution)
60
+ @body[0] = one
61
+ @body[1] = two
62
+ @friction = friction
63
+ @restitution = restitution
64
+ end
65
+
66
+
67
+
68
+
69
+ # Calculates internal data from state data. This is called before
70
+ # the resolution algorithm tries to do any resolution. It should
71
+ # never need to be called manually.
72
+ def calculate_internals(duration)
73
+ swap_bodies if @body[0].nil?
74
+
75
+ calculate_contact_basis
76
+
77
+ @relative_contact_position[0] = @contact_point - @body[0].position
78
+
79
+ unless @body[1].nil?
80
+ @relative_contact_position[1] = @contact_point - @body[1].position
81
+ end
82
+
83
+ @contact_velocity = calculate_local_velocity(0, duration)
84
+ unless @body[1].nil?
85
+ @contact_velocity -= calculate_local_velocity(1, duration)
86
+ end
87
+
88
+ calculate_desired_delta_velocity(duration)
89
+ end
90
+
91
+ # Reverses the contact. This involves swapping the two rigid bodies
92
+ # and reversing the contact normal. The internal values should then
93
+ # be recalculated using calculateInternals (this is not done
94
+ # automatically).
95
+ def swap_bodies
96
+ @contact_normal *= -1
97
+ temp = @body[0]
98
+ @body[0] = @body[1]
99
+ @body[1] = temp
100
+ end
101
+
102
+ def match_awake_state
103
+
104
+ return if @body[1].nil?
105
+
106
+ body0awake = @body[0].is_awake
107
+ body1awake = @body[1].is_awake
108
+
109
+ if(body0awake != body1awake)
110
+ if(body0awake)
111
+ @body[1].is_awake = true
112
+ else
113
+ @body[0].is_awake = true
114
+ end
115
+ end
116
+ end
117
+
118
+ def calculate_desired_delta_velocity(duration)
119
+
120
+ velocity_limit = 0.25
121
+
122
+ velocity_from_acc = 0.0
123
+
124
+
125
+ if @body[0].is_awake
126
+ velocity_from_acc = @body[0].last_frame_acceleration.scalarProduct(@contact_normal) * duration
127
+ #@body[0].last_frame_acceleration.componentProductUpdate(@contact_normal * duration)
128
+ end
129
+
130
+ if !@body[1].nil?
131
+ if @body[1].is_awake
132
+ #@body[1].last_frame_acceleration.componentProductUpdate(@contact_normal * duration)
133
+ velocity_from_acc -= @body[1].last_frame_acceleration.scalarProduct(@contact_normal) * duration
134
+ end
135
+ end
136
+
137
+ this_restitution = @restitution
138
+ if(@contact_velocity.x.abs < velocity_limit)
139
+ this_restitution = 0.0
140
+ end
141
+
142
+ @desired_delta_velocity = -@contact_velocity.x - (this_restitution * (@contact_velocity.x - velocity_from_acc))
143
+ end
144
+
145
+ def calculate_local_velocity(body_index, duration)
146
+ this_body = @body[body_index]
147
+
148
+ velocity = this_body.rotation % @relative_contact_position[body_index]
149
+ velocity += this_body.velocity
150
+
151
+ contact_velocity = @contact_to_world.transform_transpose(velocity)
152
+
153
+ #puts this_body.last_frame_acceleration
154
+ acc_velocity = this_body.last_frame_acceleration * duration
155
+ #puts acc_velocity
156
+ acc_velocity = @contact_to_world.transform_transpose(acc_velocity)
157
+
158
+ acc_velocity.x = 0
159
+
160
+ contact_velocity += acc_velocity
161
+
162
+ contact_velocity
163
+ end
164
+
165
+ def calculate_contact_basis
166
+ contact_tangent = [Vector3.new, Vector3.new]
167
+ if(@contact_normal.x.abs > @contact_normal.y.abs)
168
+ s = 1.0 / Math.sqrt( @contact_normal.z * @contact_normal.z + @contact_normal.x * @contact_normal.x)
169
+ contact_tangent[0].x = @contact_normal.z * s
170
+ contact_tangent[0].y = 0
171
+ contact_tangent[0].z = -@contact_normal.x * s
172
+
173
+ contact_tangent[1].x = @contact_normal.y * contact_tangent[0].x
174
+ contact_tangent[1].y = @contact_normal.z * contact_tangent[0].x - @contact_normal.x * contact_tangent[0].z
175
+ contact_tangent[1].z = -@contact_normal.y * contact_tangent[0].x
176
+ else
177
+ s = 1.0 / Math.sqrt( @contact_normal.z * @contact_normal.z + @contact_normal.y * @contact_normal.y)
178
+ contact_tangent[0].x = 0
179
+ contact_tangent[0].y = -@contact_normal.z * s
180
+ contact_tangent[0].z = @contact_normal.y * s
181
+
182
+ contact_tangent[1].x = @contact_normal.y * contact_tangent[0].z - @contact_normal.z * contact_tangent[0].y
183
+ contact_tangent[1].y = -@contact_normal.x * contact_tangent[0].z
184
+ contact_tangent[1].z = @contact_normal.x * contact_tangent[0].y
185
+ end
186
+
187
+ @contact_to_world.setComponents( @contact_normal, contact_tangent[0], contact_tangent[1])
188
+ end
189
+
190
+ def apply_impulse(impulse, body, velocity_change, rotation_change)
191
+ puts "not done yet"
192
+ end
193
+
194
+ def apply_velocity_change(velocity_change, rotation_change)
195
+ inverse_inertia_tensors = [Matrix3.new, Matrix3.new ]
196
+
197
+ inverse_inertia_tensors[0] = @body[0].inverse_inertia_tensor_world.dup
198
+
199
+ unless @body[1].nil?
200
+ inverse_inertia_tensors[1] = @body[1].inverse_inertia_tensor_world.dup
201
+ end
202
+
203
+ impulse_contact = Vector3.new
204
+
205
+ if( @friction == 0.0)
206
+ impulse_contact = calculate_frictionless_impulse(inverse_inertia_tensors)
207
+ else
208
+ impulse_contact = calculate_friction_impulse(inverse_inertia_tensors)
209
+ end
210
+
211
+ impulse = @contact_to_world.transform(impulse_contact)
212
+
213
+ impulsive_torque = @relative_contact_position[0] % impulse
214
+ rotation_change[0] = inverse_inertia_tensors[0].transform(impulsive_torque)
215
+ velocity_change[0].clear()
216
+ velocity_change[0].addScaledVector(impulse, @body[0].inverse_mass)
217
+
218
+ @body[0].velocity += velocity_change[0]
219
+ @body[0].rotation += rotation_change[0]
220
+
221
+ unless @body[1].nil?
222
+ impulsive_torque = impulse % @relative_contact_position[1]
223
+ rotation_change[1] = inverse_inertia_tensors[1].transform(impulsive_torque)
224
+ velocity_change[1].clear()
225
+ velocity_change[1].addScaledVector(impulse, -@body[1].inverse_mass)
226
+
227
+ @body[1].velocity += velocity_change[1]
228
+ @body[1].rotation += rotation_change[1]
229
+ end
230
+ end
231
+
232
+ def apply_position_change(linear_change, angular_change, penetration)
233
+
234
+ angularLimit = 0.2;
235
+ angularMove = [0.0, 0.0]
236
+ linearMove = [0.0, 0.0];
237
+
238
+ totalInertia = 0.0;
239
+ linearInertia = [0.0, 0.0]
240
+ angularInertia = [0.0, 0.0]
241
+
242
+ for i in 0..1
243
+
244
+ break if @body[i].nil?
245
+
246
+ inverseInertiaTensor = @body[i].inverse_inertia_tensor_world.dup
247
+ angularInertiaWorld = @relative_contact_position[i] % @contact_normal
248
+ angularInertiaWorld = inverseInertiaTensor.transform(angularInertiaWorld)
249
+ angularInertiaWorld = angularInertiaWorld % @relative_contact_position[i]
250
+ angularInertia[i] = angularInertiaWorld * @contact_normal
251
+
252
+ linearInertia[i] = body[i].inverse_mass
253
+ totalInertia += linearInertia[i] + angularInertia[i]
254
+
255
+ end
256
+
257
+ for i in 0..1
258
+
259
+ return if @body[i].nil?
260
+
261
+ sign = i == 0 ? 1 : -1
262
+
263
+ angularMove[i] = sign * penetration * (angularInertia[i] / totalInertia)
264
+ linearMove[i] = sign * penetration * (linearInertia[i] / totalInertia)
265
+
266
+ projection = @relative_contact_position[i]
267
+ projection.addScaledVector( @contact_normal, -@relative_contact_position[i].scalarProduct(@contact_normal))
268
+
269
+ maxMagnitude = angularLimit * projection.magnitude
270
+
271
+ if(angularMove[i] < -maxMagnitude)
272
+ totalMove = angularMove[i] + linearMove[i]
273
+ angularMove[i] = -maxMagnitude
274
+ linearMove[i] = totalMove - angularMove[i]
275
+ elsif angularMove[i] > maxMagnitude
276
+ totalMove = angularMove[i] + linearMove[i]
277
+ angularMove[i] = maxMagnitude
278
+ linearMove[i] = totalMove - angularMove[i]
279
+ end
280
+
281
+ if(angularMove[i] == 0)
282
+ angular_change[i].clear
283
+ else
284
+ targetAngularDirection = @relative_contact_position[i].vector_product(@contact_normal)
285
+ inverse_inertia_tensor = @body[i].inverse_inertia_tensor_world.dup
286
+
287
+ angular_change[i] = inverse_inertia_tensor.transform(targetAngularDirection) * (angularMove[i] / angularInertia[i])
288
+ end
289
+
290
+
291
+ linear_change[i] = @contact_normal * linearMove[i]
292
+ @body[i].position.addScaledVector( @contact_normal, linearMove[i])
293
+
294
+ #puts "WORK"
295
+ @body[i].orientation.addScaledVector(angular_change[i], 1.0)
296
+ @body[i].orientation.normalize
297
+
298
+ @body[i].calculate_derived_data if !@body[i].is_awake
299
+ end
300
+ end
301
+
302
+
303
+ def calculate_friction_impulse(inverse_inertia_tensor)
304
+
305
+ impulse_contact = Vector3.new
306
+ inverse_mass = @body[0].inverse_mass
307
+
308
+ impulseToTorque = Matrix3.new
309
+ impulseToTorque.setSkewSymmetric(@relative_contact_position[0])
310
+
311
+ deltaVelWorld = impulseToTorque.dup
312
+ deltaVelWorld *= inverse_inertia_tensor[0]
313
+ deltaVelWorld *= impulseToTorque
314
+ deltaVelWorld *= -1.0
315
+
316
+ unless @body[1].nil?
317
+ impulseToTorque.setSkewSymmetric(@relative_contact_position[1])
318
+ deltaVelWorld2 = impulseToTorque.dup
319
+ deltaVelWorld2 *= inverse_inertia_tensor[1]
320
+ deltaVelWorld2 *= impulseToTorque
321
+ deltaVelWorld2 *= -1.0
322
+
323
+ deltaVelWorld += deltaVelWorld2
324
+
325
+ inverse_mass += @body[1].inverse_mass
326
+ end
327
+
328
+ deltaVelocity = @contact_to_world.transpose()
329
+ deltaVelocity *= deltaVelWorld
330
+ deltaVelocity *= @contact_to_world
331
+
332
+ deltaVelocity.data[0] += inverse_mass
333
+ deltaVelocity.data[4] += inverse_mass
334
+ deltaVelocity.data[4] += inverse_mass
335
+
336
+ impulse_matrix = deltaVelocity.inverse
337
+
338
+ velKill = Vector3.new( @desired_delta_velocity, -@contact_velocity.y, -@contact_velocity.z)
339
+
340
+ impulse_contact = impulse_matrix.transform(velKill)
341
+
342
+ planarImpulse = Math.sqrt( (impulse_contact.y * impulse_contact.y) + (impulse_contact.z * impulse_contact.z))
343
+
344
+ if planarImpulse > impulse_contact.x * @friction
345
+ impulse_contact.y /= planarImpulse
346
+ impulse_contact.z /= planarImpulse
347
+
348
+ impulse_contact.x = deltaVelocity.data[0] + ( deltaVelocity.data[1] * @friction * impulse_contact.y) + (deltaVelocity.data[2] * @friction * impulse_contact.z)
349
+ impulse_contact.x = @desired_delta_velocity / impulse_contact.z
350
+ impulse_contact.y *= @friction * impulse_contact.x;
351
+ impulse_contact.z *= @friction * impulse_contact.x
352
+ end
353
+
354
+ return impulse_contact
355
+
356
+ end
357
+
358
+ def calculate_frictionless_impulse(inverse_inertia_tensor)
359
+ puts "not implemented"
360
+ end
361
+ end
362
+ end
@@ -0,0 +1,7 @@
1
+ module BulldogPhysics
2
+ class ContactGenerator
3
+
4
+ #virtual unsigned addContact(Contact *contact, unsigned limit) const = 0;
5
+ virtual :add_contact
6
+ end
7
+ end
@@ -0,0 +1,143 @@
1
+ module BulldogPhysics
2
+ class ContactResolver
3
+
4
+ attr :velocity_iterations, :position_iterations
5
+
6
+ attr :velocity_epsilon, :position_epsilon
7
+
8
+ attr_accessor :velocity_iterations_used, :position_iterations_used
9
+
10
+ attr :valid_settings
11
+
12
+
13
+ def initialize(iterations, velocity_epsilon = 0.01, position_epsilon = 0.01)
14
+ set_iterations(iterations, iterations)
15
+ set_epsilon(velocity_epsilon, position_epsilon)
16
+ end
17
+
18
+ def is_valid
19
+ @velocity_iterations > 0 && @position_iterations > 0 && @position_epsilon >= 0.0 && @velocity_epsilon >= 0
20
+ end
21
+
22
+ def set_iterations(velocity_iterations, position_iterations)
23
+ @velocity_iterations = velocity_iterations
24
+ @position_iterations = position_iterations
25
+ end
26
+
27
+ def set_epsilon(velocity_epsilon, position_epsilon)
28
+ @velocity_epsilon = velocity_epsilon
29
+ @position_epsilon = position_epsilon
30
+ end
31
+
32
+ def resolve_contacts(contact_array, num_contacts, duration)
33
+ return if num_contacts == 0
34
+ return unless is_valid
35
+
36
+ prepare_contacts(contact_array, num_contacts, duration)
37
+ adjust_positions(contact_array, num_contacts, duration)
38
+ adjust_velocities(contact_array, num_contacts, duration)
39
+ end
40
+
41
+ def prepare_contacts(contact_array, num_contacts, duration)
42
+ contact_array.each do |contact|
43
+ contact.calculate_internals(duration)
44
+ end
45
+ end
46
+
47
+ def adjust_velocities(contact_array, num_contacts, duration)
48
+ velocityChange = [Vector3.new, Vector3.new]
49
+ rotationChange = [Vector3.new, Vector3.new]
50
+ deltaVel = Vector3.new
51
+
52
+ @velocity_iterations_used = 0
53
+
54
+ while @velocity_iterations_used < @velocity_iterations do
55
+
56
+ max = @velocity_epsilon
57
+ index = num_contacts
58
+ for i in 0..num_contacts-1
59
+ if(contact_array[i].desired_delta_velocity > max)
60
+ max = contact_array[i].desired_delta_velocity
61
+ index = i
62
+ end
63
+ end
64
+
65
+ break if index == num_contacts
66
+
67
+ contact_array[index].match_awake_state
68
+ contact_array[index].apply_velocity_change(velocityChange, rotationChange)
69
+
70
+ for i in i..num_contacts-1
71
+ for b in 0..1
72
+ next if contact_array[i].body[b].nil?
73
+ for d in 0..1
74
+ if(contact_array[i].body[b] == contact_array[index].body[d])
75
+ deltaVel = velocityChange[d] + rotationChange[d].vector_product(contact_array[i].relative_contact_position[b]);
76
+ #puts "DELAT VEL #{deltaVel}"
77
+ #puts "CONTACT VELOCITY #{contact_array[i].contact_velocity}"
78
+ #puts "CONTACT TO WORLD "
79
+
80
+ change = contact_array[i].contact_to_world.transformTranspose(deltaVel)
81
+ if(b)
82
+ contact_array[i].contact_velocity -= change
83
+ else
84
+ contact_array[i].contact_velocity += change
85
+ end
86
+
87
+ contact_array[i].calculate_desired_delta_velocity(duration)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ @velocity_iterations_used += 1
93
+ end
94
+
95
+ end
96
+
97
+ #c is a contact array
98
+ def adjust_positions(c, num_contacts, duration)
99
+ linearChange = [Vector3.new, Vector3.new]
100
+ angularChange = [Vector3.new, Vector3.new]
101
+ #angularChange = Vector3.new
102
+ deltaPosition = Vector3.new
103
+
104
+ @position_iterations_used = 0
105
+ while @position_iterations_used < @position_iterations
106
+
107
+ max = @position_epsilon
108
+ index = num_contacts
109
+
110
+ for i in 0..num_contacts
111
+ if( c[i].penetration > max)
112
+ max = c[i].penetration
113
+ puts "MAX #{max}"
114
+ index = i
115
+ end
116
+ end
117
+
118
+ break if (index == num_contacts)
119
+
120
+ c[index].match_awake_state
121
+ c[index].apply_position_change( linearChange, angularChange, max)
122
+
123
+ for i in 0..num_contacts-1
124
+ for b in 0..1
125
+ #next if c[i].body[b].nil?
126
+
127
+ for d in 0..1
128
+
129
+ if(c[i].body[b] == c[index].body[d])
130
+ deltaPosition = linearChange[d] + angularChange[d].vector_product( c[i].relative_contact_position[b])
131
+ test = b == 1 ? 1 : -1
132
+ #puts test
133
+ c[i].penetration += deltaPosition.scalarProduct(c[i].contact_normal) * test
134
+ end
135
+ end
136
+ end
137
+ end
138
+ @position_iterations_used += 1
139
+ end
140
+ end
141
+
142
+ end
143
+ end