physics_joint.dart 16.9 KB
Newer Older
1
part of flutter_sprites;
2

3 4
typedef void PhysicsJointBreakCallback(PhysicsJoint joint);

5 6 7
/// A joint connects two physics bodies and restricts their movements. Some
/// types of joints also support motors that adds forces to the connected
/// bodies.
8
abstract class PhysicsJoint {
9
  PhysicsJoint(this._bodyA, this._bodyB, this.breakingForce, this.breakCallback) {
10 11 12 13
    bodyA._joints.add(this);
    bodyB._joints.add(this);
  }

14 15
  PhysicsBody _bodyA;

16 17 18
  /// The first body connected to the joint.
  ///
  ///     PhysicsBody body = myJoint.bodyA;
19 20 21 22
  PhysicsBody get bodyA => _bodyA;

  PhysicsBody _bodyB;

23 24 25
  /// The second body connected to the joint.
  ///
  ///     PhysicsBody body = myJoint.bodyB;
26 27
  PhysicsBody get bodyB => _bodyB;

28 29
  /// The maximum force the joint can handle before it breaks. If set to null,
  /// the joint will never break.
30
  final double breakingForce;
31

32 33
  final PhysicsJointBreakCallback breakCallback;

34 35 36
  bool _active = true;
  box2d.Joint _joint;

37
  PhysicsWorld _physicsWorld;
38

39 40
  void _completeCreation() {
    if (bodyA._attached && bodyB._attached) {
41
      _attach(bodyA._physicsWorld);
42 43 44
    }
  }

45
  void _attach(PhysicsWorld physicsNode) {
46
    if (_joint == null) {
47
      _physicsWorld = physicsNode;
48
      _joint = _createB2Joint(physicsNode);
49
      _physicsWorld._joints.add(this);
50 51 52 53 54
    }
  }

  void _detach() {
    if (_joint != null && _active) {
55
      _physicsWorld.b2World.destroyJoint(_joint);
56
      _joint = null;
57
      _physicsWorld._joints.remove(this);
58 59 60 61
    }
    _active = false;
  }

62
  box2d.Joint _createB2Joint(PhysicsWorld physicsNode);
63

64 65
  /// If the joint is no longer needed, call the the [destroy] method to detach
  /// if from its connected bodies.
66 67 68 69 70 71 72 73 74 75 76 77
  void destroy() {
    _detach();
  }

  void _checkBreakingForce(double dt) {
    if (breakingForce == null) return;

    if (_joint != null && _active) {
      Vector2 reactionForce = new Vector2.zero();
      _joint.getReactionForce(1.0 / dt, reactionForce);

      if (breakingForce * breakingForce < reactionForce.length2) {
78
        // Destroy the joint
79
        destroy();
80 81 82 83

        // Notify any observer
        if (breakCallback != null)
          breakCallback(this);
84 85 86
      }
    }
  }
87 88
}

89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
/// The revolute joint can be thought of as a hinge, a pin, or an axle.
/// An anchor point is defined in global space.
///
/// Revolute joints can be given limits so that the bodies can rotate only to a
/// certain point using [lowerAngle], [upperAngle], and [enableLimit].
/// They can also be given a motor using [enableMotore] together with
/// [motorSpeed] and [maxMotorTorque] so that the bodies will try
/// to rotate at a given speed, with a given torque.
///
/// Common uses for revolute joints include:
/// - wheels or rollers
/// - chains or swingbridges (using multiple revolute joints)
/// - rag-doll joints
/// - rotating doors, catapults, levers
///
///     new PhysicsJointRevolute(
///       nodeA.physicsBody,
///       nodeB.physicsBody,
///       nodeB.position
///     );
109 110 111 112
class PhysicsJointRevolute extends PhysicsJoint {
  PhysicsJointRevolute(
    PhysicsBody bodyA,
    PhysicsBody bodyB,
113
    this._worldAnchor, {
114 115
      this.lowerAngle: 0.0,
      this.upperAngle: 0.0,
116
      this.enableLimit: false,
117
      PhysicsJointBreakCallback breakCallback,
118 119 120 121
      double breakingForce,
      bool enableMotor: false,
      double motorSpeed: 0.0,
      double maxMotorTorque: 0.0
122
    }) : super(bodyA, bodyB, breakingForce, breakCallback) {
123 124 125
    _enableMotor = enableMotor;
    _motorSpeed = motorSpeed;
    _maxMotorTorque = maxMotorTorque;
126
    _completeCreation();
127 128
  }

129
  final Point _worldAnchor;
130 131 132

  /// The lower angle of the limits of this joint, only used if [enableLimit]
  /// is set to true.
133
  final double lowerAngle;
134 135 136

  /// The upper angle of the limits of this joint, only used if [enableLimit]
  /// is set to true.
137
  final double upperAngle;
138 139 140

  /// If set to true, the rotation will be limited to a value between
  /// [lowerAngle] and [upperAngle].
141
  final bool enableLimit;
142

143 144
  bool _enableMotor;

145 146 147
  /// By setting enableMotor to true, the joint will automatically rotate, e.g.
  /// this can be used for creating an engine for a wheel. For this to be
  /// useful you also need to set [motorSpeed] and [maxMotorTorque].
148 149 150 151 152 153 154 155 156 157 158 159
  bool get enableMotor => _enableMotor;

  set enableMotor(bool enableMotor) {
    _enableMotor = enableMotor;
    if (_joint != null) {
      box2d.RevoluteJoint revoluteJoint = _joint;
      revoluteJoint.enableMotor(enableMotor);
    }
  }

  double _motorSpeed;

160 161
  /// Sets the motor speed of this joint, will only work if [enableMotor] is
  /// set to true and [maxMotorTorque] is set to a non zero value.
162 163 164 165 166 167 168 169 170 171 172 173 174 175
  double get motorSpeed => _motorSpeed;

  set motorSpeed(double motorSpeed) {
    _motorSpeed = motorSpeed;
    if (_joint != null) {
      box2d.RevoluteJoint revoluteJoint = _joint;
      revoluteJoint.setMotorSpeed(radians(motorSpeed));
    }
  }

  double _maxMotorTorque;

  double get maxMotorTorque => _maxMotorTorque;

176 177
  /// Sets the motor torque of this joint, will only work if [enableMotor] is
  /// set to true and [motorSpeed] is set to a non zero value.
178 179 180 181 182 183 184 185
  set maxMotorTorque(double maxMotorTorque) {
    _maxMotorTorque = maxMotorTorque;
    if (_joint != null) {
      box2d.RevoluteJoint revoluteJoint = _joint;
      revoluteJoint.setMaxMotorTorque(maxMotorTorque);
    }
  }

186
  box2d.Joint _createB2Joint(PhysicsWorld physicsNode) {
187 188
    // Create Joint Definition
    Vector2 vecAnchor = new Vector2(
189 190
      _worldAnchor.x / physicsNode.b2WorldToNodeConversionFactor,
      _worldAnchor.y / physicsNode.b2WorldToNodeConversionFactor
191 192 193 194 195 196 197 198
    );

    box2d.RevoluteJointDef b2Def = new box2d.RevoluteJointDef();
    b2Def.initialize(bodyA._body, bodyB._body, vecAnchor);
    b2Def.enableLimit = enableLimit;
    b2Def.lowerAngle = lowerAngle;
    b2Def.upperAngle = upperAngle;

199 200 201 202
    b2Def.enableMotor = _enableMotor;
    b2Def.motorSpeed = _motorSpeed;
    b2Def.maxMotorTorque = _maxMotorTorque;

203 204 205 206 207
    // Create joint
    return physicsNode.b2World.createJoint(b2Def);
  }
}

208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
/// The prismatic joint is probably more commonly known as a slider joint.
/// The two joined bodies have their rotation held fixed relative to each
/// other, and they can only move along a specified axis.
///
/// Prismatic joints can be given limits so that the bodies can only move
/// along the axis within a specific range. They can also be given a motor so
/// that the bodies will try to move at a given speed, with a given force.
///
/// Common uses for prismatic joints include:
/// - elevators
/// - moving platforms
/// - sliding doors
/// - pistons
///
///     new PhysicsJointPrismatic(
///       nodeA.physicsBody,
///       nodeB.physicsBody,
///       new Offset(0.0, 1.0)
///     );
227 228 229 230
class PhysicsJointPrismatic extends PhysicsJoint {
  PhysicsJointPrismatic(
    PhysicsBody bodyA,
    PhysicsBody bodyB,
231
    this.axis, {
232
      double breakingForce,
233 234 235 236
      PhysicsJointBreakCallback breakCallback,
      bool enableMotor: false,
      double motorSpeed: 0.0,
      double maxMotorForce: 0.0
237
    }
238
  ) : super(bodyA, bodyB, breakingForce, breakCallback) {
239 240 241
    _enableMotor = enableMotor;
    _motorSpeed = motorSpeed;
    _maxMotorForce = maxMotorForce;
242 243 244
    _completeCreation();
  }

245 246
  /// Axis that the movement is restricted to (in global space at the time of
  /// creation)
247 248 249 250
  final Offset axis;

  bool _enableMotor;

251 252
  /// For the motor to be effective you also need to set [motorSpeed] and
  /// [maxMotorForce].
253 254 255 256 257 258 259 260 261 262 263 264
  bool get enableMotor => _enableMotor;

  set enableMotor(bool enableMotor) {
    _enableMotor = enableMotor;
    if (_joint != null) {
      box2d.PrismaticJoint prismaticJoint = _joint;
      prismaticJoint.enableMotor(enableMotor);
    }
  }

  double _motorSpeed;

265 266
  /// Sets the motor speed of this joint, will only work if [enableMotor] is
  /// set to true and [maxMotorForce] is set to a non zero value.
267 268 269 270 271 272
  double get motorSpeed => _motorSpeed;

  set motorSpeed(double motorSpeed) {
    _motorSpeed = motorSpeed;
    if (_joint != null) {
      box2d.PrismaticJoint prismaticJoint = _joint;
273
      prismaticJoint.setMotorSpeed(motorSpeed / _physicsWorld.b2WorldToNodeConversionFactor);
274 275 276 277 278
    }
  }

  double _maxMotorForce;

279 280
  /// Sets the motor force of this joint, will only work if [enableMotor] is
  /// set to true and [motorSpeed] is set to a non zero value.
281 282 283 284 285 286
  double get maxMotorForce => _maxMotorForce;

  set maxMotorForce(double maxMotorForce) {
    _maxMotorForce = maxMotorForce;
    if (_joint != null) {
      box2d.PrismaticJoint prismaticJoint = _joint;
287
      prismaticJoint.setMaxMotorForce(maxMotorForce / _physicsWorld.b2WorldToNodeConversionFactor);
288 289
    }
  }
290

291
  box2d.Joint _createB2Joint(PhysicsWorld physicsNode) {
292 293
    box2d.PrismaticJointDef b2Def = new box2d.PrismaticJointDef();
    b2Def.initialize(bodyA._body, bodyB._body, bodyA._body.position, new Vector2(axis.dx, axis.dy));
294 295 296 297
    b2Def.enableMotor = _enableMotor;
    b2Def.motorSpeed = _motorSpeed;
    b2Def.maxMotorForce = _maxMotorForce;

298 299 300 301
    return physicsNode.b2World.createJoint(b2Def);
  }
}

302 303 304
/// The weld joint attempts to constrain all relative motion between two bodies.
///
///     new PhysicsJointWeld(bodyA.physicsJoint, bodyB.physicsJoint)
305 306 307
class PhysicsJointWeld extends PhysicsJoint {
  PhysicsJointWeld(
    PhysicsBody bodyA,
308
    PhysicsBody bodyB, {
309
      double breakingForce,
310
      PhysicsJointBreakCallback breakCallback,
311 312
      this.dampening: 0.0,
      this.frequency: 0.0
313
    }
314
  ) : super(bodyA, bodyB, breakingForce, breakCallback) {
315 316 317
    _completeCreation();
  }

318 319 320
  final double dampening;
  final double frequency;

321
  box2d.Joint _createB2Joint(PhysicsWorld physicsNode) {
322 323 324 325 326 327
    box2d.WeldJointDef b2Def = new box2d.WeldJointDef();
    Vector2 middle = new Vector2(
      (bodyA._body.position.x + bodyB._body.position.x) / 2.0,
      (bodyA._body.position.y + bodyB._body.position.y) / 2.0
    );
    b2Def.initialize(bodyA._body, bodyB._body, middle);
328 329
    b2Def.dampingRatio = dampening;
    b2Def.frequencyHz = frequency;
330 331
    return physicsNode.b2World.createJoint(b2Def);
  }
332
}
333

334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
/// A pulley is used to create an idealized pulley. The pulley connects two
/// bodies to ground and to each other. As one body goes up, the other goes
/// down.
///
/// The total length of the pulley rope is conserved according to the initial
/// configuration.
///
///     new PhysicsJointPulley(
///       nodeA.physicsBody,
///       nodeB.physicsBody,
///       new Point(0.0, 100.0),
///       new Point(100.0, 100.0),
///       nodeA.position,
///       nodeB.position,
///       1.0
///     );
350 351 352 353 354 355 356 357
class PhysicsJointPulley extends PhysicsJoint {
  PhysicsJointPulley(
    PhysicsBody bodyA,
    PhysicsBody bodyB,
    this.groundAnchorA,
    this.groundAnchorB,
    this.anchorA,
    this.anchorB,
358
    this.ratio, {
359 360
      double breakingForce,
      PhysicsJointBreakCallback breakCallback
361
    }
362
  ) : super(bodyA, bodyB, breakingForce, breakCallback) {
363 364 365 366 367 368 369 370 371
    _completeCreation();
  }

  final Point groundAnchorA;
  final Point groundAnchorB;
  final Point anchorA;
  final Point anchorB;
  final double ratio;

372
  box2d.Joint _createB2Joint(PhysicsWorld physicsNode) {
373 374 375 376 377 378 379 380 381 382 383 384 385 386
    box2d.PulleyJointDef b2Def = new box2d.PulleyJointDef();
    b2Def.initialize(
      bodyA._body,
      bodyB._body,
      _convertPosToVec(groundAnchorA, physicsNode),
      _convertPosToVec(groundAnchorB, physicsNode),
      _convertPosToVec(anchorA, physicsNode),
      _convertPosToVec(anchorB, physicsNode),
      ratio
    );
    return physicsNode.b2World.createJoint(b2Def);
  }
}

387 388 389 390 391 392 393 394
/// The gear joint can only connect revolute and/or prismatic joints.
///
/// Like the pulley ratio, you can specify a gear ratio. However, in this case
/// the gear ratio can be negative. Also keep in mind that when one joint is a
/// revolute joint (angular) and the other joint is prismatic (translation),
/// and then the gear ratio will have units of length or one over length.
///
///     new PhysicsJointGear(nodeA.physicsBody, nodeB.physicsBody);
395 396 397 398 399
class PhysicsJointGear extends PhysicsJoint {
  PhysicsJointGear(
    PhysicsBody bodyA,
    PhysicsBody bodyB, {
      double breakingForce,
400
      PhysicsJointBreakCallback breakCallback,
401
      this.ratio: 1.0
402
    }
403
  ) : super(bodyA, bodyB, breakingForce, breakCallback) {
404 405 406
    _completeCreation();
  }

407
  /// The ratio of the rotation for bodyA relative bodyB.
408 409
  final double ratio;

410
  box2d.Joint _createB2Joint(PhysicsWorld physicsNode) {
411 412 413 414 415 416 417 418 419
    box2d.GearJointDef b2Def = new box2d.GearJointDef();
    b2Def.bodyA = bodyA._body;
    b2Def.bodyB = bodyB._body;
    b2Def.ratio = ratio;

    return physicsNode.b2World.createJoint(b2Def);
  }
}

420 421
/// Keeps a fixed distance between two bodies, [anchorA] and [anchorB] are
/// defined in world coordinates.
422 423 424 425 426 427 428
class PhysicsJointDistance extends PhysicsJoint {
  PhysicsJointDistance(
    PhysicsBody bodyA,
    PhysicsBody bodyB,
    this.anchorA,
    this.anchorB, {
      double breakingForce,
429
      PhysicsJointBreakCallback breakCallback,
430 431 432 433
      this.length,
      this.dampening: 0.0,
      this.frequency: 0.0
    }
434
  ) : super(bodyA, bodyB, breakingForce, breakCallback) {
435 436 437
    _completeCreation();
  }

438
  /// The anchor of bodyA in world coordinates at the time of creation.
439
  final Point anchorA;
440 441

  /// The anchor of bodyB in world coordinates at the time of creation.
442
  final Point anchorB;
443 444 445 446

  /// The desired distance between the joints, if not passed in at creation
  /// it will be set automatically to the distance between the anchors at the
  /// time of creation.
447
  final double length;
448 449

  /// Dampening factor.
450
  final double dampening;
451 452

  /// Dampening frequency.
453 454
  final double frequency;

455
  box2d.Joint _createB2Joint(PhysicsWorld physicsNode) {
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
    box2d.DistanceJointDef b2Def = new box2d.DistanceJointDef();
    b2Def.initialize(
      bodyA._body,
      bodyB._body,
      _convertPosToVec(anchorA, physicsNode),
      _convertPosToVec(anchorB, physicsNode)
    );
    b2Def.dampingRatio = dampening;
    b2Def.frequencyHz = frequency;
    if (length != null)
      b2Def.length = length / physicsNode.b2WorldToNodeConversionFactor;

    return physicsNode.b2World.createJoint(b2Def);
  }
}

472 473
/// The wheel joint restricts a point on bodyB to a line on bodyA. The wheel
/// joint also optionally provides a suspension spring.
474 475 476 477 478 479 480
class PhysicsJointWheel extends PhysicsJoint {
  PhysicsJointWheel(
    PhysicsBody bodyA,
    PhysicsBody bodyB,
    this.anchor,
    this.axis, {
      double breakingForce,
481
      PhysicsJointBreakCallback breakCallback,
482 483 484
      this.dampening: 0.0,
      this.frequency: 0.0
    }
485
  ) : super(bodyA, bodyB, breakingForce, breakCallback) {
486 487 488
    _completeCreation();
  }

489
  /// The rotational point in global space at the time of creation.
490
  final Point anchor;
491 492

  /// The axis which to restrict the movement to.
493
  final Offset axis;
494 495

  /// Dampening factor.
496
  final double dampening;
497 498

  /// Dampening frequency.
499 500
  final double frequency;

501
  box2d.Joint _createB2Joint(PhysicsWorld physicsNode) {
502 503 504 505 506 507 508 509 510 511 512 513 514 515
    box2d.WheelJointDef b2Def = new box2d.WheelJointDef();
    b2Def.initialize(
      bodyA._body,
      bodyB._body,
      _convertPosToVec(anchor, physicsNode),
      new Vector2(axis.dx, axis.dy)
    );
    b2Def.dampingRatio = dampening;
    b2Def.frequencyHz = frequency;

    return physicsNode.b2World.createJoint(b2Def);
  }
}

516 517
/// The friction joint is used for top-down friction. The joint provides 2D
/// translational friction and angular friction.
518 519 520 521 522 523
class PhysicsJointFriction extends PhysicsJoint {
  PhysicsJointFriction(
    PhysicsBody bodyA,
    PhysicsBody bodyB,
    this.anchor, {
      double breakingForce,
524
      PhysicsJointBreakCallback breakCallback,
525 526 527
      this.maxForce: 0.0,
      this.maxTorque: 0.0
    }
528
  ) : super(bodyA, bodyB, breakingForce, breakCallback) {
529 530 531 532 533 534 535
    _completeCreation();
  }

  final Point anchor;
  final double maxForce;
  final double maxTorque;

536
  box2d.Joint _createB2Joint(PhysicsWorld physicsNode) {
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
    box2d.FrictionJointDef b2Def = new box2d.FrictionJointDef();
    b2Def.initialize(
      bodyA._body,
      bodyB._body,
      _convertPosToVec(anchor, physicsNode)
    );
    b2Def.maxForce = maxForce / physicsNode.b2WorldToNodeConversionFactor;
    b2Def.maxTorque = maxTorque / physicsNode.b2WorldToNodeConversionFactor;
    return physicsNode.b2World.createJoint(b2Def);
  }
}

class PhysicsJointConstantVolume extends PhysicsJoint {
  PhysicsJointConstantVolume(
    this.bodies, {
      double breakingForce,
553
      PhysicsJointBreakCallback breakCallback,
554 555 556
      this.dampening,
      this.frequency
    }
557
  ) : super(null, null, breakingForce, breakCallback) {
558 559 560 561 562 563 564 565 566 567
    assert(bodies.length > 2);
    _bodyA = bodies[0];
    _bodyB = bodies[1];
    _completeCreation();
  }

  final List<PhysicsBody> bodies;
  final double dampening;
  final double frequency;

568
  box2d.Joint _createB2Joint(PhysicsWorld physicsNode) {
569 570 571 572 573 574 575 576 577 578
    box2d.ConstantVolumeJointDef b2Def = new box2d.ConstantVolumeJointDef();
    for (PhysicsBody body in bodies) {
      b2Def.addBody(body._body);
    }
    b2Def.dampingRatio = dampening;
    b2Def.frequencyHz = frequency;
    return physicsNode.b2World.createJoint(b2Def);
  }
}

579
Vector2 _convertPosToVec(Point pt, PhysicsWorld physicsNode) {
580 581 582 583 584
  return new Vector2(
    pt.x / physicsNode.b2WorldToNodeConversionFactor,
    pt.y / physicsNode.b2WorldToNodeConversionFactor
  );
}