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 }