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