bulldog_physics 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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