1 /*
2 Copyright (c) 2013-2014 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 dmech.constraint;
30 
31 import std.math;
32 import std.algorithm;
33 
34 import dlib.math.vector;
35 import dlib.math.matrix;
36 import dlib.math.quaternion;
37 
38 import dmech.rigidbody;
39 
40 abstract class Constraint
41 {
42     RigidBody body1;
43     RigidBody body2;
44 
45     void prepare(double delta);
46     void step();
47 }
48 
49 /*
50  * Keeps bodies at some fixed (or max/min) distance from each other.
51  * Also works as a spring, if softness is set to a higher value.
52  */
53 class DistanceConstraint: Constraint
54 {
55     enum DistanceBehavior
56     {
57         LimitDistance,
58         LimitMaximumDistance,
59         LimitMinimumDistance,
60     }
61 
62     Vector3f r1, r2;
63 
64     float biasFactor = 0.1f;
65     float softness = 0.01f;
66     float distance;
67 
68     DistanceBehavior behavior = DistanceBehavior.LimitDistance;
69 
70     this(RigidBody body1, RigidBody body2, float dist = 0.0f)
71     {
72         this.body1 = body1;
73         this.body2 = body2;
74 
75         if (dist > 0.0f)
76             distance = dist;
77         else
78             distance = (body1.worldCenterOfMass - body2.worldCenterOfMass).length;
79     }
80 
81     float effectiveMass = 0.0f;
82     float accumulatedImpulse = 0.0f;
83     float bias;
84     float softnessOverDt;
85 
86     Vector3f[4] jacobian;
87 
88     bool skipConstraint = false;
89 
90     float myCounter = 0.0f;
91 
92     override void prepare(double dt)
93     {
94         r1 = Vector3f(0.0f, 0.0f, 0.0f);
95         r2 = Vector3f(0.0f, 0.0f, 0.0f);
96 
97         Vector3f dp = body2.worldCenterOfMass - body1.worldCenterOfMass;
98 
99         float deltaLength = dp.length - distance;
100 
101         if (behavior == DistanceBehavior.LimitMaximumDistance && deltaLength <= 0.0f)
102             skipConstraint = true;
103         else if (behavior == DistanceBehavior.LimitMinimumDistance && deltaLength >= 0.0f)
104             skipConstraint = true;
105         else
106         {
107             skipConstraint = false;
108 
109             Vector3f n = dp;
110             if (n.lengthsqr != 0.0f)
111                 n.normalize();
112 
113             jacobian[0] = -n;
114             jacobian[1] = -cross(r1, n);
115             jacobian[2] = n;
116             jacobian[3] = cross(r2, n);
117 
118             effectiveMass = 
119                 body1.invMass + body2.invMass
120               + dot(jacobian[1] * body1.invInertiaTensor, jacobian[1])
121               + dot(jacobian[3] * body2.invInertiaTensor, jacobian[3]);
122 
123             softnessOverDt = softness / dt;
124             effectiveMass += softnessOverDt;
125 
126             if (effectiveMass != 0)
127                 effectiveMass = 1.0f / effectiveMass;
128 
129             bias = deltaLength * biasFactor * (1.0f / dt);
130 
131             if (body1.dynamic)
132             {
133                 body1.linearVelocity +=  jacobian[0] * accumulatedImpulse * body1.invMass;
134                 body1.angularVelocity += jacobian[1] * accumulatedImpulse * body1.invInertiaTensor;
135             }
136 
137             if (body2.dynamic)
138             {
139                 body2.linearVelocity +=  jacobian[2] * accumulatedImpulse * body2.invMass;
140                 body2.angularVelocity += jacobian[3] * accumulatedImpulse * body2.invInertiaTensor;
141             }
142         }
143     }
144 
145     override void step()
146     {
147         if (skipConstraint)
148             return;
149 
150         float jv = 
151             dot(body1.linearVelocity,  jacobian[0])
152           + dot(body1.angularVelocity, jacobian[1])
153           + dot(body2.linearVelocity,  jacobian[2])
154           + dot(body2.angularVelocity, jacobian[3]);
155 
156         float softnessScalar = accumulatedImpulse * softnessOverDt;
157 
158         float lambda = -effectiveMass * (jv + bias + softnessScalar);
159 
160         if (behavior == DistanceBehavior.LimitMinimumDistance)
161         {
162             float previousAccumulatedImpulse = accumulatedImpulse;
163             accumulatedImpulse = max(accumulatedImpulse + lambda, 0);
164             lambda = accumulatedImpulse - previousAccumulatedImpulse;
165         }
166         else if (behavior == DistanceBehavior.LimitMaximumDistance)
167         {
168             float previousAccumulatedImpulse = accumulatedImpulse;
169             accumulatedImpulse = min(accumulatedImpulse + lambda, 0);
170             lambda = accumulatedImpulse - previousAccumulatedImpulse;
171         }
172         else
173         {
174             accumulatedImpulse += lambda;
175         }
176 
177         if (body1.dynamic)
178         {
179             body1.linearVelocity +=  jacobian[0] * lambda * body1.invMass;
180             body1.angularVelocity += jacobian[1] * lambda * body1.invInertiaTensor;
181         }
182         if (body2.dynamic)
183         {
184             body2.linearVelocity +=  jacobian[2] * lambda * body2.invMass;
185             body2.angularVelocity += jacobian[3] * lambda * body2.invInertiaTensor;
186         }
187     }
188 }
189 
190 /*
191  * Limits the translation so that the local anchor points of two rigid bodies 
192  * match in world space.
193  */
194 class BallConstraint: Constraint
195 {
196     Vector3f localAnchor1, localAnchor2;
197     Vector3f r1, r2;
198 
199     Vector3f[4] jacobian; 
200    
201     float accumulatedImpulse = 0.0f;
202     
203     float biasFactor = 0.1f;
204     float softness = 0.01f; //0.05f;
205     
206     float softnessOverDt;
207     float effectiveMass;
208     float bias;
209 
210     this(RigidBody body1, RigidBody body2, Vector3f anchor1, Vector3f anchor2)
211     {
212         this.body1 = body1;
213         this.body2 = body2;
214         
215         localAnchor1 = anchor1;
216         localAnchor2 = anchor2;
217     }
218     
219     override void prepare(double delta)
220     {
221         Vector3f r1 = body1.orientation.rotate(localAnchor1);
222         Vector3f r2 = body2.orientation.rotate(localAnchor2);
223 
224         Vector3f p1, p2, dp;
225         p1 = body1.worldCenterOfMass + r1;
226         p2 = body2.worldCenterOfMass + r2;
227 
228         dp = p2 - p1;
229 
230         float deltaLength = dp.length;
231         Vector3f n = dp.normalized;
232 
233         jacobian[0] = -n;
234         jacobian[1] = -cross(r1, n);
235         jacobian[2] = n;
236         jacobian[3] = cross(r2, n);
237 
238         effectiveMass = 
239             body1.invMass + 
240             body2.invMass +
241             dot(jacobian[1] * body1.invInertiaTensor, jacobian[1]) +
242             dot(jacobian[3] * body2.invInertiaTensor, jacobian[3]);
243 
244         softnessOverDt = softness / delta;
245         effectiveMass += softnessOverDt;
246         effectiveMass = 1.0f / effectiveMass;
247 
248         bias = deltaLength * biasFactor * (1.0f / delta);
249 
250         if (body1.dynamic)
251         {
252             body1.linearVelocity += jacobian[0] * body1.invMass * accumulatedImpulse;
253             body1.angularVelocity += jacobian[1] * body1.invInertiaTensor * accumulatedImpulse;
254         }
255 
256         if (body2.dynamic)
257         {
258             body2.linearVelocity += jacobian[2] * body2.invMass * accumulatedImpulse;
259             body2.angularVelocity += jacobian[3] * body2.invInertiaTensor * accumulatedImpulse;
260         }
261     }
262     
263     override void step()
264     {
265         float jv =
266             dot(body1.linearVelocity, jacobian[0]) +
267             dot(body1.angularVelocity, jacobian[1]) +
268             dot(body2.linearVelocity, jacobian[2]) +
269             dot(body2.angularVelocity, jacobian[3]);
270 
271         float softnessScalar = accumulatedImpulse * softnessOverDt;
272         float lambda = -effectiveMass * (jv + bias + softnessScalar);
273 
274         accumulatedImpulse += lambda;
275 
276         if (body1.dynamic)
277         {
278             body1.linearVelocity += jacobian[0] * body1.invMass * lambda;
279             body1.angularVelocity += jacobian[1] * body1.invInertiaTensor * lambda;
280         }
281 
282         if (body2.dynamic)
283         {
284             body2.linearVelocity += jacobian[2] * body2.invMass * lambda;
285             body2.angularVelocity += jacobian[3] * body2.invInertiaTensor * lambda;
286         }
287     }
288 }
289 
290 /*
291  * Constraints a point on a body to be fixed on a line
292  * which is fixed on another body.
293  */
294 class SliderConstraint: Constraint
295 {
296     Vector3f lineNormal;
297 
298     Vector3f localAnchor1, localAnchor2;
299     Vector3f r1, r2;
300 
301     Vector3f[4] jacobian; 
302    
303     float accumulatedImpulse = 0.0f;
304     
305     float biasFactor = 0.5f;
306     float softness = 0.0f;
307     
308     float softnessOverDt;
309     float effectiveMass;
310     float bias;
311 
312     this(RigidBody body1, RigidBody body2, Vector3f lineStartPointBody1, Vector3f pointBody2)
313     {
314         this.body1 = body1;
315         this.body2 = body2;
316         
317         localAnchor1 = lineStartPointBody1;
318         localAnchor2 = pointBody2;
319 
320         lineNormal = (lineStartPointBody1 + body1.worldCenterOfMass) - 
321                      (pointBody2 + body2.worldCenterOfMass);
322 
323         if (lineNormal.lengthsqr != 0.0f)
324             lineNormal.normalize();
325     }
326 
327     override void prepare(double delta)
328     {
329         Vector3f r1 = body1.orientation.rotate(localAnchor1);
330         Vector3f r2 = body2.orientation.rotate(localAnchor2);
331 
332         Vector3f p1, p2, dp;
333         p1 = body1.worldCenterOfMass + r1;
334         p2 = body2.worldCenterOfMass + r2;
335 
336         dp = p2 - p1;
337 
338         Vector3f l = body1.orientation.rotate(lineNormal);
339 
340         Vector3f t = cross((p1 - p2), l);
341         if (t.lengthsqr != 0.0f)
342             t.normalize();
343         t = cross(t, l);
344 
345         jacobian[0] = t;
346         jacobian[1] = cross((r1 + p2 - p1), t);
347         jacobian[2] = -t;
348         jacobian[3] = -cross(r2, t);
349 
350         effectiveMass = 
351             body1.invMass + 
352             body2.invMass +
353             dot(jacobian[1] * body1.invInertiaTensor, jacobian[1]) +
354             dot(jacobian[3] * body2.invInertiaTensor, jacobian[3]);
355 
356         softnessOverDt = softness / delta;
357         effectiveMass += softnessOverDt;
358 
359         if (effectiveMass != 0)
360             effectiveMass = 1.0f / effectiveMass;
361 
362         bias = -cross(l, (p2 - p1)).length * biasFactor * (1.0f / delta);
363 
364         if (body1.dynamic)
365         {
366             body1.linearVelocity += body1.invMass * accumulatedImpulse * jacobian[0];
367             body1.angularVelocity += accumulatedImpulse * jacobian[1] * body1.invInertiaTensor;
368         }
369 
370         if (body2.dynamic)
371         {
372             body2.linearVelocity += body2.invMass * accumulatedImpulse * jacobian[2];
373             body2.angularVelocity += accumulatedImpulse * jacobian[3] * body2.invInertiaTensor;
374         }
375     }
376 
377     override void step()
378     {
379         float jv =
380             dot(body1.linearVelocity, jacobian[0]) +
381             dot(body1.angularVelocity, jacobian[1]) +
382             dot(body2.linearVelocity, jacobian[2]) +
383             dot(body2.angularVelocity, jacobian[3]);
384 
385         float softnessScalar = accumulatedImpulse * softnessOverDt;
386         float lambda = -effectiveMass * (jv + bias + softnessScalar);
387 
388         accumulatedImpulse += lambda;
389 
390         if (body1.dynamic)
391         {
392             body1.linearVelocity += body1.invMass * lambda * jacobian[0];
393             body1.angularVelocity += lambda * jacobian[1] * body1.invInertiaTensor;
394         }
395 
396         if (body2.dynamic)
397         {
398             body2.linearVelocity += body2.invMass * lambda * jacobian[2];
399             body2.angularVelocity += lambda * jacobian[3] * body2.invInertiaTensor;
400         }
401     }
402 }
403 
404 /*
405  * Constraints bodies so that they always take the same rotation
406  * relative to each other.
407  */
408 class AngleConstraint: Constraint
409 {
410     Vector3f[4] jacobian; 
411    
412     Vector3f accumulatedImpulse = Vector3f(0, 0, 0);
413     
414     float biasFactor = 0.05f;
415     float softness = 0.0f;
416     
417     float softnessOverDt;
418     Matrix3x3f effectiveMass;
419     Vector3f bias;
420 
421     this(RigidBody body1, RigidBody body2)
422     {
423         this.body1 = body1;
424         this.body2 = body2;
425     }
426 
427     override void prepare(double dt)
428     {
429         effectiveMass = body1.invInertiaTensor + body2.invInertiaTensor;
430 
431         softnessOverDt = softness / dt;
432 
433         effectiveMass.a11 += softnessOverDt;
434         effectiveMass.a22 += softnessOverDt;
435         effectiveMass.a33 += softnessOverDt;
436 
437         effectiveMass = effectiveMass.inverse;
438 
439         Quaternionf dq = body2.orientation * body1.orientation.conj;
440         Vector3f axis = dq.generator;
441 /*
442         // Matrix version
443         Matrix3x3f orientationDifference = Matrix3x3f.identity;
444         auto rot1 = body1.orientation.toMatrix3x3;
445         auto rot2 = body2.orientation.toMatrix3x3;
446         Matrix3x3f q = orientationDifference * rot2 * rot1.inverse;
447 
448         Vector3f axis;
449         float x = q.a32 - q.a23;
450         float y = q.a13 - q.a31;
451         float z = q.a21 - q.a12;
452         float r = sqrt(x * x + y * y + z * z);
453         float t = q.a11 + q.a22 + q.a33;
454         float angle = atan2(r, t - 1);
455         axis = Vector3f(x, y, z) * angle;
456 */
457 
458         bias = axis * biasFactor * (-1.0f / dt);
459 
460         if (body1.dynamic)
461             body1.angularVelocity += accumulatedImpulse * body1.invInertiaTensor;
462         if (body2.dynamic)
463             body2.angularVelocity += -accumulatedImpulse * body2.invInertiaTensor;
464     }
465 
466     override void step()
467     {
468         Vector3f jv = body1.angularVelocity - body2.angularVelocity;
469         Vector3f softnessVector = accumulatedImpulse * softnessOverDt;
470 
471         Vector3f lambda = -1.0f * (jv+bias+softnessVector) * effectiveMass;
472         accumulatedImpulse += lambda;
473 
474         if (body1.dynamic)
475             body1.angularVelocity += lambda * body1.invInertiaTensor;
476         if (body2.dynamic)
477             body2.angularVelocity += -lambda * body2.invInertiaTensor;
478     }
479 }
480 
481 /*
482  * Constrains two bodies to rotate only around a single axis in worldspace.
483  */
484 class AxisAngleConstraint: Constraint
485 {
486     Vector3f axis;
487 
488     Vector3f localAxis1;
489     Vector3f localAxis2;
490     Vector3f localConstrAxis1;
491     Vector3f localConstrAxis2;
492     Vector3f worldConstrAxis1;
493     Vector3f worldConstrAxis2;
494 
495     Vector3f accumulatedImpulse = Vector3f(0, 0, 0);
496 
497     float biasFactor = 0.4f;
498     float softness = 0.0f;
499     
500     float softnessOverDt;
501     Matrix3x3f effectiveMass;
502     Vector3f bias;
503 
504     this(RigidBody body1, RigidBody body2, Vector3f axis)
505     {
506         this.body1 = body1;
507         this.body2 = body2;
508         this.axis = axis;
509 
510         // Axis in body space 
511         this.localAxis1 = axis * body1.orientation.toMatrix3x3.transposed;
512         this.localAxis2 = axis * body2.orientation.toMatrix3x3.transposed;
513 
514         localConstrAxis1 = cross(Vector3f(0, 1, 0), localAxis1);
515         if (localConstrAxis1.lengthsqr < 0.001f)
516             localConstrAxis1 = cross(Vector3f(1, 0, 0), localAxis1);
517             
518         localConstrAxis2 = cross(localAxis1, localConstrAxis1);
519         localConstrAxis1.normalize();
520         localConstrAxis2.normalize();
521     }
522 
523     override void prepare(double dt)
524     {
525         effectiveMass = body1.invInertiaTensor + body2.invInertiaTensor;
526 
527         softnessOverDt = softness / dt;
528 
529         effectiveMass.a11 += softnessOverDt;
530         effectiveMass.a22 += softnessOverDt;
531         effectiveMass.a33 += softnessOverDt;
532 
533         effectiveMass = effectiveMass.inverse;
534 
535         auto rot1 = body1.orientation.toMatrix3x3;
536         auto rot2 = body2.orientation.toMatrix3x3;
537 
538         Vector3f worldAxis1 = localAxis1 * rot1;
539         Vector3f worldAxis2 = localAxis2 * rot2;
540 
541         worldConstrAxis1 = localConstrAxis1 * rot1;
542         worldConstrAxis2 = localConstrAxis2 * rot2;
543 
544         Vector3f error = cross(worldAxis1, worldAxis2);
545 
546         Vector3f errorAxis = Vector3f(0, 0, 0);
547         errorAxis.x = dot(error, worldConstrAxis1);
548         errorAxis.y = dot(error, worldConstrAxis2);
549 
550         bias = errorAxis * biasFactor * (-1.0f / dt);
551 
552         Vector3f impulse;
553         impulse.x = worldConstrAxis1.x * accumulatedImpulse.x 
554                   + worldConstrAxis2.x * accumulatedImpulse.y;
555         impulse.y = worldConstrAxis1.y * accumulatedImpulse.x 
556                   + worldConstrAxis2.y * accumulatedImpulse.y;
557         impulse.z = worldConstrAxis1.z * accumulatedImpulse.x 
558                   + worldConstrAxis2.z * accumulatedImpulse.y;
559 
560         if (body1.dynamic)
561             body1.angularVelocity += impulse * body1.invInertiaTensor;
562         if (body2.dynamic)
563             body2.angularVelocity += -impulse * body2.invInertiaTensor;
564     }
565 
566     override void step()
567     {
568         Vector3f vd = body1.angularVelocity - body2.angularVelocity;
569         Vector3f jv = Vector3f(0, 0, 0);
570         jv.x = dot(vd, worldConstrAxis1);
571         jv.y = dot(vd, worldConstrAxis2);
572 
573         Vector3f softnessVector = accumulatedImpulse * softnessOverDt;
574 
575         Vector3f lambda = -(jv + bias + softnessVector) * effectiveMass;
576         accumulatedImpulse += lambda;
577 
578         Vector3f impulse;
579         impulse.x = worldConstrAxis1.x * lambda.x + worldConstrAxis2.x * lambda.y;
580         impulse.y = worldConstrAxis1.y * lambda.x + worldConstrAxis2.y * lambda.y;
581         impulse.z = worldConstrAxis1.z * lambda.x + worldConstrAxis2.z * lambda.y;
582 
583         if (body1.dynamic)
584             body1.angularVelocity += impulse * body1.invInertiaTensor;
585         if (body2.dynamic)
586             body2.angularVelocity += -impulse * body2.invInertiaTensor;
587     }
588 }
589 
590 /*
591  * Combination of SliderConstraint and AngleConstraint.
592  * Restrics 5 degrees of freedom so that bodies can only move in one direction
593  * relative to each other.
594  */
595 class PrismaticConstraint: Constraint
596 {
597     AngleConstraint ac;
598     SliderConstraint sc;
599 
600     this(RigidBody body1, RigidBody body2)
601     {
602         this.body1 = body1;
603         this.body2 = body2;
604 
605         ac = new AngleConstraint(body1, body2);
606         sc = new SliderConstraint(body1, body2, 
607             Vector3f(0, 0, 0), Vector3f(0, 0, 0));
608     }
609 
610     override void prepare(double dt)
611     {
612         ac.prepare(dt);
613         sc.prepare(dt);
614     }
615 
616     override void step()
617     {
618         ac.step();
619         sc.step();
620     }
621 }
622 
623 /*
624  * Combination of BallConstraint and AxisAngleConstraint.
625  * Restricts 5 degrees of freedom, so the bodies are fixed relative to
626  * anchor point and can only rotate around one axis.
627  * This can be useful to represent doors or wheels.
628  */
629 class HingeConstraint: Constraint
630 {
631     AxisAngleConstraint aac;
632     BallConstraint bc;
633 
634     this(RigidBody body1, 
635          RigidBody body2, 
636          Vector3f anchor1,
637          Vector3f anchor2, 
638          Vector3f axis)
639     {
640         this.body1 = body1;
641         this.body2 = body2;
642 
643         aac = new AxisAngleConstraint(body1, body2, axis);
644         bc = new BallConstraint(body1, body2, anchor1, anchor2);
645     }
646 
647     override void prepare(double dt)
648     {
649         aac.prepare(dt);
650         bc.prepare(dt);
651     }
652 
653     override void step()
654     {
655         aac.step();
656         bc.step();
657     }
658 }
659