physics_body.dart 15 KB
Newer Older
1
part of flutter_sprites;
2 3 4 5 6 7

enum PhysicsBodyType {
    static,
    dynamic
}

8 9 10 11 12 13 14 15 16 17 18 19
/// A physics body can be assigned to any node to make it simulated by physics.
/// The body has a shape, and physical properties such as density, friction,
/// and velocity.
///
/// Bodies can be either dynamic or static. Dynamic bodies will move and rotate
/// the nodes that are associated with it. Static bodies can be moved by moving
/// or animating the node associated with them.
///
/// For a body to be simulated it needs to be associated with a [Node], through
/// the node's physicsBody property. The node also need to be a child of either
/// a [PhysicsWorld] or a [PhysicsGroup] (which in turn is a child of a
/// [PhysicsWorld] or a [Physics Group]).
20 21 22 23
class PhysicsBody {
  PhysicsBody(this.shape, {
    this.tag: null,
    this.type: PhysicsBodyType.dynamic,
24 25 26 27
    double density: 1.0,
    double friction: 0.0,
    double restitution: 0.0,
    bool isSensor: false,
28 29
    Offset linearVelocity: Offset.zero,
    double angularVelocity: 0.0,
30
    this.linearDampening: 0.0,
31
    double angularDampening: 0.0,
32 33 34 35 36
    bool allowSleep: true,
    bool awake: true,
    bool fixedRotation: false,
    bool bullet: false,
    bool active: true,
37 38 39
    this.gravityScale: 1.0,
    collisionCategory: "Default",
    collisionMask: null
40 41 42 43 44
  }) {
    this.density = density;
    this.friction = friction;
    this.restitution = restitution;
    this.isSensor = isSensor;
45 46 47 48

    this.linearVelocity = linearVelocity;
    this.angularVelocity = angularVelocity;
    this.angularDampening = angularDampening;
49 50 51 52 53 54

    this.allowSleep = allowSleep;
    this.awake = awake;
    this.fixedRotation = fixedRotation;
    this.bullet = bullet;
    this.active = active;
55 56 57

    this.collisionCategory = collisionCategory;
    this.collisionMask = collisionMask;
58
  }
59

60 61
  Vector2 _lastPosition;
  double _lastRotation;
62 63
  Vector2 _targetPosition;
  double _targetAngle;
64

65 66
  double _scale;

67 68 69 70
  /// An object associated with this body, normally used for detecting
  /// collisions.
  ///
  /// myBody.tag = "SpaceShip";
71 72
  Object tag;

73 74 75 76
  /// The shape of this physics body. The shape cannot be modified once the
  /// body is created. If the shape is required to change, create a new body.
  ///
  ///     myShape = myBody.shape;
77
  final PhysicsShape shape;
78

79 80 81 82 83
  /// The type of the body. This is either [PhysicsBodyType.dynamic] or
  /// [PhysicsBodyType.static]. Dynamic bodies are simulated by the physics,
  /// static objects affect the physics but are not moved by the physics.
  ///
  ///     myBody.type = PhysicsBodyType.static;
84 85
  PhysicsBodyType type;

86 87
  double _density;

88 89 90 91
  /// The density of the body, default value is 1.0. The density has no specific
  /// unit, instead densities are relative to each other.
  ///
  ///     myBody.density = 0.5;
92 93 94 95 96 97 98
  double get density => _density;

  set density(double density) {
    _density = density;

    if (_body == null)
      return;
99
    for (box2d.Fixture f = _body.getFixtureList(); f != null; f = f.getNext()) {
100 101 102 103 104 105
      f.setDensity(density);
    }
  }

  double _friction;

106 107 108 109
  /// The fricion of the body, the default is 0.0 and the value should be in
  /// the range of 0.0 to 1.0.
  ///
  ///     myBody.friction = 0.4;
110 111 112 113 114 115 116
  double get friction => _friction;

  set friction(double friction) {
    _friction = friction;

    if (_body == null)
      return;
117
    for (box2d.Fixture f = _body.getFixtureList(); f != null; f = f.getNext()) {
118 119 120 121 122 123 124 125
      f.setFriction(friction);
    }
  }

  double _restitution;

  double get restitution => _restitution;

126 127 128 129
  /// The restitution of the body, the default is 0.0 and the value should be in
  /// the range of 0.0 to 1.0.
  ///
  ///     myBody.restitution = 0.5;
130 131 132 133 134
  set restitution(double restitution) {
    _restitution = restitution;

    if (_body == null)
      return;
135
    for (box2d.Fixture f = _body.getFixtureList(); f != null; f = f.getNext()) {
136 137 138 139 140 141
      f.setRestitution(restitution);
    }
  }

  bool _isSensor;

142 143 144 145 146
  /// True if the body is a sensor. Sensors doesn't collide with other bodies,
  /// but will return collision callbacks. Use a sensor body to detect if two
  /// bodies are overlapping.
  ///
  ///     myBody.isSensor = true;
147 148 149 150 151 152 153
  bool get isSensor => _isSensor;

  set isSensor(bool isSensor) {
    _isSensor = isSensor;

    if (_body == null)
      return;
154
    for (box2d.Fixture f = _body.getFixtureList(); f != null; f = f.getNext()) {
155 156 157
      f.setSensor(isSensor);
    }
  }
158

159 160
  Offset _linearVelocity;

161 162 163
  /// The current linear velocity of the body in points / second.
  ///
  ///     myBody.velocity = Offset.zero;
164 165 166 167
  Offset get linearVelocity {
    if (_body == null)
      return _linearVelocity;
    else {
168 169
      double dx = _body.linearVelocity.x * _physicsWorld.b2WorldToNodeConversionFactor;
      double dy = _body.linearVelocity.y * _physicsWorld.b2WorldToNodeConversionFactor;
170 171 172
      return new Offset(dx, dy);
    }
  }
173

174 175
  set linearVelocity(Offset linearVelocity) {
    _linearVelocity = linearVelocity;
176

177 178
    if (_body != null) {
      Vector2 vec = new Vector2(
179 180
        linearVelocity.dx / _physicsWorld.b2WorldToNodeConversionFactor,
        linearVelocity.dy / _physicsWorld.b2WorldToNodeConversionFactor
181 182 183 184 185 186 187
      );
      _body.linearVelocity = vec;
    }
  }

  double _angularVelocity;

188 189 190
  /// The angular velocity of the body in degrees / second.
  ///
  ///     myBody.angularVelocity = 0.0;
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
  double get angularVelocity {
    if (_body == null)
      return _angularVelocity;
    else
      return _body.angularVelocity;
  }

  set angularVelocity(double angularVelocity) {
    _angularVelocity = angularVelocity;

    if (_body != null) {
      _body.angularVelocity = angularVelocity;
    }
  }

  // TODO: Should this be editable in box2d.Body ?
207 208 209 210

  /// Linear dampening, in the 0.0 to 1.0 range, default is 0.0.
  ///
  ///     double dampening = myBody.linearDampening;
211 212 213 214
  final double linearDampening;

  double _angularDampening;

215 216 217
  /// Angular dampening, in the 0.0 to 1.0 range, default is 0.0.
  ///
  ///     myBody.angularDampening = 0.1;
218 219 220 221 222 223 224 225
  double get angularDampening => _angularDampening;

  set angularDampening(double angularDampening) {
    _angularDampening = angularDampening;

    if (_body != null)
      _body.angularDamping = angularDampening;
  }
226

227 228
  bool _allowSleep;

229 230 231
  /// Allows the body to sleep if it hasn't moved.
  ///
  ///     myBody.allowSleep = false;
232 233 234 235
  bool get allowSleep => _allowSleep;

  set allowSleep(bool allowSleep) {
    _allowSleep = allowSleep;
236

237 238 239
    if (_body != null)
      _body.setSleepingAllowed(allowSleep);
  }
240

241 242
  bool _awake;

243 244 245
  /// True if the body is currently awake.
  ///
  ///     bool isAwake = myBody.awake;
246 247 248 249 250 251
  bool get awake {
    if (_body != null)
      return _body.isAwake();
    else
      return _awake;
  }
252

253 254
  set awake(bool awake) {
    _awake = awake;
255

256 257 258 259 260 261
    if (_body != null)
      _body.setAwake(awake);
  }

  bool _fixedRotation;

262 263 264
  /// If true, the body cannot be rotated by the physics simulation.
  ///
  ///     myBody.fixedRotation = true;
265 266 267 268 269 270 271 272 273 274 275 276 277
  bool get fixedRotation => _fixedRotation;

  set fixedRotation(bool fixedRotation) {
    _fixedRotation = fixedRotation;

    if (_body != null)
      _body.setFixedRotation(fixedRotation);
  }

  bool _bullet;

  bool get bullet => _bullet;

278 279 280 281 282
  /// If true, the body cannot pass through other objects when moved at high
  /// speed. Bullet bodies are slower to simulate, so only use this option
  /// if neccessary.
  ///
  ///     myBody.bullet = true;
283 284 285 286 287 288 289 290 291 292
  set bullet(bool bullet) {
    _bullet = bullet;

    if (_body != null) {
      _body.setBullet(bullet);
    }
  }

  bool _active;

293 294 295 296
  /// An active body is used in the physics simulation. Set this to false if
  /// you want to temporarily exclude a body from the simulation.
  ///
  ///     myBody.active = false;
297 298 299 300 301 302 303 304 305 306 307 308 309
  bool get active {
    if (_body != null)
      return _body.isActive();
    else
      return _active;
  }

  set active(bool active) {
    _active = active;

    if (_body != null)
      _body.setActive(active);
  }
310 311 312

  double gravityScale;

313 314
  Object _collisionCategory = null;

315 316 317 318 319
  /// The collision category assigned to this body. The default value is
  /// "Default". The body will only collide with bodies that have the either
  /// the [collisionMask] set to null or has the category in the mask.
  ///
  ///     myBody.collisionCategory = "Air";
320 321 322 323 324 325 326 327 328 329 330
  Object get collisionCategory {
    return _collisionCategory;
  }

  set collisionCategory(Object collisionCategory) {
    _collisionCategory = collisionCategory;
    _updateFilter();
  }

  List<Object> _collisionMask = null;

331 332 333 334
  /// A list of collision categories that this object will collide with. If set
  /// to null (the default value) the body will collide with all other bodies.
  ///
  ///     myBody.collisionMask = ["Air", "Ground"];
335 336 337 338 339 340 341 342
  List<Object> get collisionMask => _collisionMask;

  set collisionMask(List<Object> collisionMask) {
    _collisionMask = collisionMask;
    _updateFilter();
  }

  box2d.Filter get _b2Filter {
343
    print("_physicsNode: $_physicsWorld groups: ${_physicsWorld._collisionGroups}");
344
    box2d.Filter f = new box2d.Filter();
345 346
    f.categoryBits = _physicsWorld._collisionGroups.getBitmaskForKeys([_collisionCategory]);
    f.maskBits = _physicsWorld._collisionGroups.getBitmaskForKeys(_collisionMask);
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361

    print("Filter: $f category: ${f.categoryBits} mask: ${f.maskBits}");

    return f;
  }

  void _updateFilter() {
    if (_body != null) {
      box2d.Filter filter = _b2Filter;
      for (box2d.Fixture fixture = _body.getFixtureList(); fixture != null; fixture = fixture.getNext()) {
        fixture.setFilterData(filter);
      }
    }
  }

362
  PhysicsWorld _physicsWorld;
363 364 365 366
  Node _node;

  box2d.Body _body;

Hixie's avatar
Hixie committed
367
  List<PhysicsJoint> _joints = <PhysicsJoint>[];
368

369 370
  bool _attached = false;

371 372 373 374
  /// Applies a force to the body at the [worldPoint] position in world
  /// cordinates.
  ///
  ///     myBody.applyForce(new Offset(0.0, 100.0), myNode.position);
375 376 377 378
  void applyForce(Offset force, Point worldPoint) {
    assert(_body != null);

    Vector2 b2Force = new Vector2(
379 380
      force.dx / _physicsWorld.b2WorldToNodeConversionFactor,
      force.dy / _physicsWorld.b2WorldToNodeConversionFactor);
381 382

    Vector2 b2Point = new Vector2(
383 384
      worldPoint.x / _physicsWorld.b2WorldToNodeConversionFactor,
      worldPoint.y / _physicsWorld.b2WorldToNodeConversionFactor
385 386 387 388 389
    );

    _body.applyForce(b2Force, b2Point);
  }

390 391 392
  /// Applice a force to the body at the its center of gravity.
  ///
  ///     myBody.applyForce(new Offset(0.0, 100.0));
393 394 395 396
  void applyForceToCenter(Offset force) {
    assert(_body != null);

    Vector2 b2Force = new Vector2(
397 398
      force.dx / _physicsWorld.b2WorldToNodeConversionFactor,
      force.dy / _physicsWorld.b2WorldToNodeConversionFactor);
399 400 401 402

    _body.applyForceToCenter(b2Force);
  }

403 404 405
  /// Applies a torque to the body.
  ///
  ///     myBody.applyTorque(10.0);
406 407 408
  void applyTorque(double torque) {
    assert(_body != null);

409
    _body.applyTorque(torque / _physicsWorld.b2WorldToNodeConversionFactor);
410 411
  }

412 413 414 415
  /// Applies a linear impulse to the body at the [worldPoint] position in world
  /// cordinates.
  ///
  ///     myBody.applyLinearImpulse(new Offset(0.0, 100.0), myNode.position);
416 417 418 419
  void applyLinearImpulse(Offset impulse, Point worldPoint, [bool wake = true]) {
    assert(_body != null);

    Vector2 b2Impulse = new Vector2(
420 421
      impulse.dx / _physicsWorld.b2WorldToNodeConversionFactor,
      impulse.dy / _physicsWorld.b2WorldToNodeConversionFactor);
422 423

    Vector2 b2Point = new Vector2(
424 425
      worldPoint.x / _physicsWorld.b2WorldToNodeConversionFactor,
      worldPoint.y / _physicsWorld.b2WorldToNodeConversionFactor
426 427 428 429 430
    );

    _body.applyLinearImpulse(b2Impulse, b2Point, wake);
  }

431 432 433
  /// Applies an angular impulse to the body.
  ///
  ///     myBody.applyAngularImpulse(20.0);
434 435 436
  void applyAngularImpulse(double impulse) {
    assert(_body != null);

437
    _body.applyAngularImpulse(impulse / _physicsWorld.b2WorldToNodeConversionFactor);
438 439
  }

440
  void _attach(PhysicsWorld physicsNode, Node node) {
441 442
    assert(_attached == false);

443
    _physicsWorld = physicsNode;
444

445 446 447 448 449
    // Account for physics groups
    Point positionWorld = node._positionToPhysics(node.position, node.parent);
    double rotationWorld = node._rotationToPhysics(node.rotation, node.parent);
    double scaleWorld = node._scaleToPhysics(node.scale, node.parent);

450
    // Update scale
451
    _scale = scaleWorld;
452

453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469
    // Create BodyDef
    box2d.BodyDef bodyDef = new box2d.BodyDef();
    bodyDef.linearVelocity = new Vector2(linearVelocity.dx, linearVelocity.dy);
    bodyDef.angularVelocity = angularVelocity;
    bodyDef.linearDamping = linearDampening;
    bodyDef.angularDamping = angularDampening;
    bodyDef.allowSleep = allowSleep;
    bodyDef.awake = awake;
    bodyDef.fixedRotation = fixedRotation;
    bodyDef.bullet = bullet;
    bodyDef.active = active;
    bodyDef.gravityScale = gravityScale;
    if (type == PhysicsBodyType.dynamic)
      bodyDef.type = box2d.BodyType.DYNAMIC;
    else
      bodyDef.type = box2d.BodyType.STATIC;

470
    // Convert to world coordinates and set position and angle
471
    double conv = physicsNode.b2WorldToNodeConversionFactor;
472 473
    bodyDef.position = new Vector2(positionWorld.x / conv, positionWorld.y / conv);
    bodyDef.angle = radians(rotationWorld);
474 475 476 477

    // Create Body
    _body = physicsNode.b2World.createBody(bodyDef);

478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
    _createFixtures(physicsNode);

    _body.userData = this;

    _node = node;

    _attached = true;

    // Attach any joints
    for (PhysicsJoint joint in _joints) {
      if (joint.bodyA._attached && joint.bodyB._attached) {
        joint._attach(physicsNode);
      }
    }
  }

494
  void _createFixtures(PhysicsWorld physicsNode) {
495 496 497 498 499 500
    // Create FixtureDef
    box2d.FixtureDef fixtureDef = new box2d.FixtureDef();
    fixtureDef.friction = friction;
    fixtureDef.restitution = restitution;
    fixtureDef.density = density;
    fixtureDef.isSensor = isSensor;
501
    fixtureDef.filter = _b2Filter;
502 503

    // Get shapes
Hixie's avatar
Hixie committed
504 505
    List<box2d.Shape> b2Shapes = <box2d.Shape>[];
    List<PhysicsShape> physicsShapes = <PhysicsShape>[];
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
    _addB2Shapes(physicsNode, shape, b2Shapes, physicsShapes);

    // Create fixtures
    for (int i = 0; i < b2Shapes.length; i++) {
      box2d.Shape b2Shape = b2Shapes[i];
      PhysicsShape physicsShape = physicsShapes[i];

      fixtureDef.shape = b2Shape;
      box2d.Fixture fixture = _body.createFixtureFromFixtureDef(fixtureDef);
      fixture.userData = physicsShape;
    }
  }

  void _detach() {
    if (_attached) {
521
      _physicsWorld._bodiesScheduledForDestruction.add(_body);
522 523 524 525
      _attached = false;
    }
  }

526
  void _updateScale(PhysicsWorld physicsNode) {
527 528 529 530 531 532 533 534 535 536 537 538
    // Destroy old fixtures
    for (box2d.Fixture fixture = _body.getFixtureList(); fixture != null; fixture = fixture.getNext()) {
      _body.destroyFixture(fixture);
    }

    // Make sure we create new b2Shapes
    shape._invalidate();

    // Create new fixtures
    _createFixtures(physicsNode);
  }

539
  void _addB2Shapes(PhysicsWorld physicsNode, PhysicsShape shape, List<box2d.Shape> b2Shapes, List<PhysicsShape> physicsShapes) {
540 541 542 543 544
    if (shape is PhysicsShapeGroup) {
      for (PhysicsShape child in shape.shapes) {
        _addB2Shapes(physicsNode, child, b2Shapes, physicsShapes);
      }
    } else {
545
      b2Shapes.add(shape.getB2Shape(physicsNode, _scale));
546 547 548 549
      physicsShapes.add(shape);
    }
  }
}