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