1 /*
2 Copyright (c) 2013-2020 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 
6 Permission is hereby granted, free of charge, to any person or organization
7 obtaining a copy of the software and accompanying documentation covered by
8 this license (the "Software") to use, reproduce, display, distribute,
9 execute, and transmit the Software, and to prepare derivative works of the
10 Software, and to permit third-parties to whom the Software is furnished to
11 do so, all subject to the following:
12 
13 The copyright notices in the Software and this entire statement, including
14 the above license grant, this restriction and the following disclaimer,
15 must be included in all copies of the Software, in whole or in part, and
16 all derivative works of the Software, unless such copies or derivative
17 works are solely in the form of machine-executable object code generated by
18 a source language processor.
19 
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 DEALINGS IN THE SOFTWARE.
27 */
28 
29 module dagon.ext.physics.rigidbody;
30 
31 import std.math;
32 
33 import dlib.core.ownership;
34 import dlib.core.memory;
35 import dlib.container.array;
36 import dlib.math.vector;
37 import dlib.math.matrix;
38 import dlib.math.quaternion;
39 import dlib.math.transformation;
40 import dlib.math.utils;
41 
42 import dagon.ext.physics.world;
43 import dagon.ext.physics.shape;
44 import dagon.ext.physics.contact;
45 
46 interface CollisionDispatcher
47 {
48     void onNewContact(RigidBody rb, Contact c);
49 }
50 
51 class RigidBody: Owner
52 {
53     Vector3f position;
54     Quaternionf orientation;
55 
56     Vector3f linearVelocity;
57     Vector3f angularVelocity;
58 
59     Vector3f pseudoLinearVelocity;
60     Vector3f pseudoAngularVelocity;
61 
62     Vector3f linearAcceleration;
63     Vector3f angularAcceleration;
64 
65     float mass;
66     float invMass;
67 
68     Matrix3x3f inertiaTensor;
69     Matrix3x3f invInertiaTensor; //TODO: rename to invInertiaWorld
70 
71     Vector3f centerOfMass;
72 
73     Vector3f forceAccumulator;
74     Vector3f torqueAccumulator;
75 
76     float bounce;
77     float friction;
78 
79     bool active = true;
80     bool useGravity = true;
81     bool enableRotation = true;
82 
83     Array!ShapeComponent shapes;
84 
85     bool dynamic;
86 
87     float damping = 0.5f;
88     float stopThreshold = 0.15f; //0.15f
89     float stopThresholdPV = 0.0f; //0.01f
90     float stopThresholdAngular = 0.2f;
91 
92     bool useOwnGravity = false;
93     Vector3f gravity = Vector3f(0, 0, 0);
94 
95     Array!CollisionDispatcher collisionDispatchers;
96 
97     void contactEvent(Contact c)
98     {
99         foreach(d; collisionDispatchers.data)
100         {
101             d.onNewContact(this, c);
102         }
103     }
104 
105     bool raycastable = true;
106     uint numContacts = 0;
107     bool useFriction = true;
108 
109     float maxSpeed = float.max;
110 
111     uint layer = 0;
112 
113     this(PhysicsWorld world)
114     {
115         super(world);
116 
117         position = Vector3f(0.0f, 0.0f, 0.0f);
118         orientation = Quaternionf(0.0f, 0.0f, 0.0f, 1.0f);
119 
120         linearVelocity = Vector3f(0.0f, 0.0f, 0.0f);
121         angularVelocity = Vector3f(0.0f, 0.0f, 0.0f);
122 
123         pseudoLinearVelocity = Vector3f(0.0f, 0.0f, 0.0f);
124         pseudoAngularVelocity = Vector3f(0.0f, 0.0f, 0.0f);
125 
126         linearAcceleration = Vector3f(0.0f, 0.0f, 0.0f);
127         angularAcceleration = Vector3f(0.0f, 0.0f, 0.0f);
128 
129         mass = 1.0f;
130         invMass = 1.0f;
131 
132         inertiaTensor = matrixf(
133             mass, 0, 0,
134             0, mass, 0,
135             0, 0, mass
136         );
137         invInertiaTensor = matrixf(
138             0, 0, 0,
139             0, 0, 0,
140             0, 0, 0
141         );
142 
143         centerOfMass = Vector3f(0.0f, 0.0f, 0.0f);
144 
145         forceAccumulator = Vector3f(0.0f, 0.0f, 0.0f);
146         torqueAccumulator = Vector3f(0.0f, 0.0f, 0.0f);
147 
148         bounce = 0.0f;
149         friction = 0.5f;
150 
151         dynamic = true;
152     }
153 
154     Matrix4x4f transformation()
155     {
156         Matrix4x4f t;
157         t = translationMatrix(position);
158         t *= orientation.toMatrix4x4();
159         return t;
160     }
161 
162     void addShapeComponent(ShapeComponent shape)
163     {
164         shape.transformation = transformation() * translationMatrix(shape.centroid);
165         shapes.append(shape);
166 
167         if (!dynamic)
168             return;
169 
170         centerOfMass = Vector3f(0, 0, 0);
171         float m = 0.0f;
172 
173         foreach (sh; shapes.data)
174         {
175             m += sh.mass;
176             centerOfMass += sh.mass * sh.centroid;
177         }
178 
179         float invm = 1.0f / m;
180         centerOfMass *= invm;
181 
182         mass += shape.mass;
183         invMass = 1.0f / mass;
184 
185         // Compute inertia tensor using Huygens-Steiner theorem
186         inertiaTensor = Matrix3x3f.zero;
187         foreach (sh; shapes.data)
188         {
189             Vector3f r = centerOfMass - sh.centroid;
190             inertiaTensor +=
191                 sh.geometry.inertiaTensor(sh.mass) +
192                 (Matrix3x3f.identity * dot(r, r) - tensorProduct(r, r)) * sh.mass;
193         }
194 
195         invInertiaTensor = inertiaTensor.inverse;
196     }
197 
198     void integrateForces(float dt)
199     {
200         if (!active || !dynamic)
201             return;
202 
203         linearAcceleration = forceAccumulator * invMass;
204         if (enableRotation)
205             angularAcceleration = torqueAccumulator * invInertiaTensor;
206 
207         linearVelocity += linearAcceleration * dt;
208         if (enableRotation)
209             angularVelocity += angularAcceleration * dt;
210     }
211 
212     void integrateVelocities(float dt)
213     {
214         if (!active || !dynamic)
215             return;
216 
217         float d = clamp(1.0f - dt * damping, 0.0f, 1.0f);
218         linearVelocity *= d;
219         angularVelocity *= d;
220 
221         float speed = linearVelocity.length;
222 
223         if (speed > maxSpeed)
224             linearVelocity = linearVelocity.normalized * maxSpeed;
225 
226         if (speed > stopThreshold /* || numContacts < 3 */)
227         {
228             position += linearVelocity * dt;
229         }
230 
231         if (enableRotation)
232         if (angularVelocity.length > stopThresholdAngular /* || numContacts < 3 */) //stopThreshold
233         {
234             orientation += 0.5f * Quaternionf(angularVelocity, 0.0f) * orientation * dt;
235             orientation.normalize();
236         }
237     }
238 
239     void integratePseudoVelocities(float dt)
240     {
241         if (!active || !dynamic)
242             return;
243 
244         float d = clamp(1.0f - dt * damping, 0.0f, 1.0f);
245 
246         pseudoLinearVelocity *= d;
247         pseudoAngularVelocity *= d;
248 
249         if (pseudoLinearVelocity.length > stopThresholdPV)
250         {
251             position += pseudoLinearVelocity;
252         }
253 
254         if (enableRotation)
255         if (pseudoAngularVelocity.length > stopThresholdPV)
256         {
257             orientation += 0.5f * Quaternionf(pseudoAngularVelocity, 0.0f) * orientation;
258             orientation.normalize();
259         }
260 
261         pseudoLinearVelocity = Vector3f(0.0f, 0.0f, 0.0f);
262         pseudoAngularVelocity = Vector3f(0.0f, 0.0f, 0.0f);
263     }
264 
265     @property Vector3f worldCenterOfMass()
266     {
267         return position + orientation.rotate(centerOfMass);
268     }
269 
270     void updateInertia()
271     {
272         if (active && dynamic)
273         {
274             auto rot = orientation.toMatrix3x3;
275             //invInertiaTensor = (rot * inertiaTensor * rot.transposed).inverse;
276             invInertiaTensor = (rot * inertiaTensor.inverse * rot.transposed);
277         }
278     }
279 
280     void updateShapeComponents()
281     {
282         foreach (sh; shapes.data)
283         {
284             sh.transformation = transformation() * translationMatrix(sh.centroid);
285         }
286     }
287 
288     void resetForces()
289     {
290         forceAccumulator = Vector3f(0, 0, 0);
291         torqueAccumulator = Vector3f(0, 0, 0);
292     }
293 
294     void applyForce(Vector3f force)
295     {
296         if (!active || !dynamic)
297             return;
298 
299         forceAccumulator += force;
300     }
301 
302     void applyTorque(Vector3f torque)
303     {
304         if (!active || !dynamic)
305             return;
306 
307         torqueAccumulator += torque;
308     }
309 
310     void applyImpulse(Vector3f impulse, Vector3f point)
311     {
312         if (!active || !dynamic)
313             return;
314 
315         linearVelocity += impulse * invMass;
316         Vector3f angularImpulse = cross(point - worldCenterOfMass, impulse);
317         angularVelocity += angularImpulse * invInertiaTensor;
318     }
319 
320     void applyForceAtPos(Vector3f force, Vector3f pos)
321     {
322         if (!active || !dynamic)
323             return;
324 
325         forceAccumulator += force;
326         torqueAccumulator += cross(pos - worldCenterOfMass, force);
327     }
328 
329     void applyPseudoImpulse(Vector3f impulse, Vector3f point)
330     {
331         if (!active || !dynamic)
332             return;
333 
334         pseudoLinearVelocity += impulse * invMass;
335         Vector3f angularImpulse = cross(point - worldCenterOfMass, impulse);
336         pseudoAngularVelocity += angularImpulse * invInertiaTensor;
337     }
338 
339     @property float linearKineticEnergy()
340     {
341         if (!active || !dynamic)
342             return float.infinity;
343 
344         // 0.5 * m * v^2
345         float v = linearVelocity.length;
346         return 0.5f * mass * v * v;
347     }
348 
349     @property float angularKineticEnergy()
350     {
351         if (!active || !dynamic)
352             return float.infinity;
353 
354         // 0.5 * w * I * w
355         Vector3f w = angularVelocity;
356         return 0.5f * dot(w * inertiaTensor, w);
357     }
358 
359     ~this()
360     {
361         shapes.free();
362         collisionDispatchers.free();
363     }
364 }