Commit 856ee978 authored by Viktor Lidholt's avatar Viktor Lidholt

Merge pull request #1863 from vlidholt/master

Adds API documentation to sprite physics
parents 5b3b3dfb 567b0cf2
...@@ -786,6 +786,13 @@ class Node { ...@@ -786,6 +786,13 @@ class Node {
PhysicsBody _physicsBody; PhysicsBody _physicsBody;
/// The physics body associated with this node. If a physics body is assigned,
/// and the node is a child of a [PhysicsWorld] or a [PhysicsGroup] the
/// node's position and rotation will be controlled by the body.
///
/// myNode.physicsBody = new PhysicsBody(
/// new PhysicsShapeCircle(Point.zero, 20.0)
/// );
PhysicsBody get physicsBody => _physicsBody; PhysicsBody get physicsBody => _physicsBody;
set physicsBody(PhysicsBody physicsBody) { set physicsBody(PhysicsBody physicsBody) {
......
...@@ -5,6 +5,18 @@ enum PhysicsBodyType { ...@@ -5,6 +5,18 @@ enum PhysicsBodyType {
dynamic dynamic
} }
/// 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]).
class PhysicsBody { class PhysicsBody {
PhysicsBody(this.shape, { PhysicsBody(this.shape, {
this.tag: null, this.tag: null,
...@@ -52,14 +64,31 @@ class PhysicsBody { ...@@ -52,14 +64,31 @@ class PhysicsBody {
double _scale; double _scale;
/// An object associated with this body, normally used for detecting
/// collisions.
///
/// myBody.tag = "SpaceShip";
Object tag; Object tag;
/// 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;
final PhysicsShape shape; final PhysicsShape shape;
/// 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;
PhysicsBodyType type; PhysicsBodyType type;
double _density; double _density;
/// 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;
double get density => _density; double get density => _density;
set density(double density) { set density(double density) {
...@@ -74,6 +103,10 @@ class PhysicsBody { ...@@ -74,6 +103,10 @@ class PhysicsBody {
double _friction; double _friction;
/// 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;
double get friction => _friction; double get friction => _friction;
set friction(double friction) { set friction(double friction) {
...@@ -90,6 +123,10 @@ class PhysicsBody { ...@@ -90,6 +123,10 @@ class PhysicsBody {
double get restitution => _restitution; double get restitution => _restitution;
/// 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;
set restitution(double restitution) { set restitution(double restitution) {
_restitution = restitution; _restitution = restitution;
...@@ -102,6 +139,11 @@ class PhysicsBody { ...@@ -102,6 +139,11 @@ class PhysicsBody {
bool _isSensor; bool _isSensor;
/// 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;
bool get isSensor => _isSensor; bool get isSensor => _isSensor;
set isSensor(bool isSensor) { set isSensor(bool isSensor) {
...@@ -116,12 +158,15 @@ class PhysicsBody { ...@@ -116,12 +158,15 @@ class PhysicsBody {
Offset _linearVelocity; Offset _linearVelocity;
/// The current linear velocity of the body in points / second.
///
/// myBody.velocity = Offset.zero;
Offset get linearVelocity { Offset get linearVelocity {
if (_body == null) if (_body == null)
return _linearVelocity; return _linearVelocity;
else { else {
double dx = _body.linearVelocity.x * _physicsNode.b2WorldToNodeConversionFactor; double dx = _body.linearVelocity.x * _physicsWorld.b2WorldToNodeConversionFactor;
double dy = _body.linearVelocity.y * _physicsNode.b2WorldToNodeConversionFactor; double dy = _body.linearVelocity.y * _physicsWorld.b2WorldToNodeConversionFactor;
return new Offset(dx, dy); return new Offset(dx, dy);
} }
} }
...@@ -131,8 +176,8 @@ class PhysicsBody { ...@@ -131,8 +176,8 @@ class PhysicsBody {
if (_body != null) { if (_body != null) {
Vector2 vec = new Vector2( Vector2 vec = new Vector2(
linearVelocity.dx / _physicsNode.b2WorldToNodeConversionFactor, linearVelocity.dx / _physicsWorld.b2WorldToNodeConversionFactor,
linearVelocity.dy / _physicsNode.b2WorldToNodeConversionFactor linearVelocity.dy / _physicsWorld.b2WorldToNodeConversionFactor
); );
_body.linearVelocity = vec; _body.linearVelocity = vec;
} }
...@@ -140,6 +185,9 @@ class PhysicsBody { ...@@ -140,6 +185,9 @@ class PhysicsBody {
double _angularVelocity; double _angularVelocity;
/// The angular velocity of the body in degrees / second.
///
/// myBody.angularVelocity = 0.0;
double get angularVelocity { double get angularVelocity {
if (_body == null) if (_body == null)
return _angularVelocity; return _angularVelocity;
...@@ -156,10 +204,17 @@ class PhysicsBody { ...@@ -156,10 +204,17 @@ class PhysicsBody {
} }
// TODO: Should this be editable in box2d.Body ? // TODO: Should this be editable in box2d.Body ?
/// Linear dampening, in the 0.0 to 1.0 range, default is 0.0.
///
/// double dampening = myBody.linearDampening;
final double linearDampening; final double linearDampening;
double _angularDampening; double _angularDampening;
/// Angular dampening, in the 0.0 to 1.0 range, default is 0.0.
///
/// myBody.angularDampening = 0.1;
double get angularDampening => _angularDampening; double get angularDampening => _angularDampening;
set angularDampening(double angularDampening) { set angularDampening(double angularDampening) {
...@@ -171,6 +226,9 @@ class PhysicsBody { ...@@ -171,6 +226,9 @@ class PhysicsBody {
bool _allowSleep; bool _allowSleep;
/// Allows the body to sleep if it hasn't moved.
///
/// myBody.allowSleep = false;
bool get allowSleep => _allowSleep; bool get allowSleep => _allowSleep;
set allowSleep(bool allowSleep) { set allowSleep(bool allowSleep) {
...@@ -182,6 +240,9 @@ class PhysicsBody { ...@@ -182,6 +240,9 @@ class PhysicsBody {
bool _awake; bool _awake;
/// True if the body is currently awake.
///
/// bool isAwake = myBody.awake;
bool get awake { bool get awake {
if (_body != null) if (_body != null)
return _body.isAwake(); return _body.isAwake();
...@@ -198,6 +259,9 @@ class PhysicsBody { ...@@ -198,6 +259,9 @@ class PhysicsBody {
bool _fixedRotation; bool _fixedRotation;
/// If true, the body cannot be rotated by the physics simulation.
///
/// myBody.fixedRotation = true;
bool get fixedRotation => _fixedRotation; bool get fixedRotation => _fixedRotation;
set fixedRotation(bool fixedRotation) { set fixedRotation(bool fixedRotation) {
...@@ -211,6 +275,11 @@ class PhysicsBody { ...@@ -211,6 +275,11 @@ class PhysicsBody {
bool get bullet => _bullet; bool get bullet => _bullet;
/// 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;
set bullet(bool bullet) { set bullet(bool bullet) {
_bullet = bullet; _bullet = bullet;
...@@ -221,6 +290,10 @@ class PhysicsBody { ...@@ -221,6 +290,10 @@ class PhysicsBody {
bool _active; bool _active;
/// 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;
bool get active { bool get active {
if (_body != null) if (_body != null)
return _body.isActive(); return _body.isActive();
...@@ -239,6 +312,11 @@ class PhysicsBody { ...@@ -239,6 +312,11 @@ class PhysicsBody {
Object _collisionCategory = null; Object _collisionCategory = null;
/// 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";
Object get collisionCategory { Object get collisionCategory {
return _collisionCategory; return _collisionCategory;
} }
...@@ -250,6 +328,10 @@ class PhysicsBody { ...@@ -250,6 +328,10 @@ class PhysicsBody {
List<Object> _collisionMask = null; List<Object> _collisionMask = null;
/// 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"];
List<Object> get collisionMask => _collisionMask; List<Object> get collisionMask => _collisionMask;
set collisionMask(List<Object> collisionMask) { set collisionMask(List<Object> collisionMask) {
...@@ -258,10 +340,10 @@ class PhysicsBody { ...@@ -258,10 +340,10 @@ class PhysicsBody {
} }
box2d.Filter get _b2Filter { box2d.Filter get _b2Filter {
print("_physicsNode: $_physicsNode groups: ${_physicsNode._collisionGroups}"); print("_physicsNode: $_physicsWorld groups: ${_physicsWorld._collisionGroups}");
box2d.Filter f = new box2d.Filter(); box2d.Filter f = new box2d.Filter();
f.categoryBits = _physicsNode._collisionGroups.getBitmaskForKeys([_collisionCategory]); f.categoryBits = _physicsWorld._collisionGroups.getBitmaskForKeys([_collisionCategory]);
f.maskBits = _physicsNode._collisionGroups.getBitmaskForKeys(_collisionMask); f.maskBits = _physicsWorld._collisionGroups.getBitmaskForKeys(_collisionMask);
print("Filter: $f category: ${f.categoryBits} mask: ${f.maskBits}"); print("Filter: $f category: ${f.categoryBits} mask: ${f.maskBits}");
...@@ -277,7 +359,7 @@ class PhysicsBody { ...@@ -277,7 +359,7 @@ class PhysicsBody {
} }
} }
PhysicsWorld _physicsNode; PhysicsWorld _physicsWorld;
Node _node; Node _node;
box2d.Body _body; box2d.Body _body;
...@@ -286,62 +368,79 @@ class PhysicsBody { ...@@ -286,62 +368,79 @@ class PhysicsBody {
bool _attached = false; bool _attached = false;
/// Applies a force to the body at the [worldPoint] position in world
/// cordinates.
///
/// myBody.applyForce(new Offset(0.0, 100.0), myNode.position);
void applyForce(Offset force, Point worldPoint) { void applyForce(Offset force, Point worldPoint) {
assert(_body != null); assert(_body != null);
Vector2 b2Force = new Vector2( Vector2 b2Force = new Vector2(
force.dx / _physicsNode.b2WorldToNodeConversionFactor, force.dx / _physicsWorld.b2WorldToNodeConversionFactor,
force.dy / _physicsNode.b2WorldToNodeConversionFactor); force.dy / _physicsWorld.b2WorldToNodeConversionFactor);
Vector2 b2Point = new Vector2( Vector2 b2Point = new Vector2(
worldPoint.x / _physicsNode.b2WorldToNodeConversionFactor, worldPoint.x / _physicsWorld.b2WorldToNodeConversionFactor,
worldPoint.y / _physicsNode.b2WorldToNodeConversionFactor worldPoint.y / _physicsWorld.b2WorldToNodeConversionFactor
); );
_body.applyForce(b2Force, b2Point); _body.applyForce(b2Force, b2Point);
} }
/// Applice a force to the body at the its center of gravity.
///
/// myBody.applyForce(new Offset(0.0, 100.0));
void applyForceToCenter(Offset force) { void applyForceToCenter(Offset force) {
assert(_body != null); assert(_body != null);
Vector2 b2Force = new Vector2( Vector2 b2Force = new Vector2(
force.dx / _physicsNode.b2WorldToNodeConversionFactor, force.dx / _physicsWorld.b2WorldToNodeConversionFactor,
force.dy / _physicsNode.b2WorldToNodeConversionFactor); force.dy / _physicsWorld.b2WorldToNodeConversionFactor);
_body.applyForceToCenter(b2Force); _body.applyForceToCenter(b2Force);
} }
/// Applies a torque to the body.
///
/// myBody.applyTorque(10.0);
void applyTorque(double torque) { void applyTorque(double torque) {
assert(_body != null); assert(_body != null);
_body.applyTorque(torque / _physicsNode.b2WorldToNodeConversionFactor); _body.applyTorque(torque / _physicsWorld.b2WorldToNodeConversionFactor);
} }
/// Applies a linear impulse to the body at the [worldPoint] position in world
/// cordinates.
///
/// myBody.applyLinearImpulse(new Offset(0.0, 100.0), myNode.position);
void applyLinearImpulse(Offset impulse, Point worldPoint, [bool wake = true]) { void applyLinearImpulse(Offset impulse, Point worldPoint, [bool wake = true]) {
assert(_body != null); assert(_body != null);
Vector2 b2Impulse = new Vector2( Vector2 b2Impulse = new Vector2(
impulse.dx / _physicsNode.b2WorldToNodeConversionFactor, impulse.dx / _physicsWorld.b2WorldToNodeConversionFactor,
impulse.dy / _physicsNode.b2WorldToNodeConversionFactor); impulse.dy / _physicsWorld.b2WorldToNodeConversionFactor);
Vector2 b2Point = new Vector2( Vector2 b2Point = new Vector2(
worldPoint.x / _physicsNode.b2WorldToNodeConversionFactor, worldPoint.x / _physicsWorld.b2WorldToNodeConversionFactor,
worldPoint.y / _physicsNode.b2WorldToNodeConversionFactor worldPoint.y / _physicsWorld.b2WorldToNodeConversionFactor
); );
_body.applyLinearImpulse(b2Impulse, b2Point, wake); _body.applyLinearImpulse(b2Impulse, b2Point, wake);
} }
/// Applies an angular impulse to the body.
///
/// myBody.applyAngularImpulse(20.0);
void applyAngularImpulse(double impulse) { void applyAngularImpulse(double impulse) {
assert(_body != null); assert(_body != null);
_body.applyAngularImpulse(impulse / _physicsNode.b2WorldToNodeConversionFactor); _body.applyAngularImpulse(impulse / _physicsWorld.b2WorldToNodeConversionFactor);
} }
void _attach(PhysicsWorld physicsNode, Node node) { void _attach(PhysicsWorld physicsNode, Node node) {
assert(_attached == false); assert(_attached == false);
_physicsNode = physicsNode; _physicsWorld = physicsNode;
// Account for physics groups // Account for physics groups
Point positionWorld = node._positionToPhysics(node.position, node.parent); Point positionWorld = node._positionToPhysics(node.position, node.parent);
...@@ -419,7 +518,7 @@ class PhysicsBody { ...@@ -419,7 +518,7 @@ class PhysicsBody {
void _detach() { void _detach() {
if (_attached) { if (_attached) {
_physicsNode._bodiesScheduledForDestruction.add(_body); _physicsWorld._bodiesScheduledForDestruction.add(_body);
_attached = false; _attached = false;
} }
} }
......
...@@ -3,7 +3,7 @@ part of flutter_sprites; ...@@ -3,7 +3,7 @@ part of flutter_sprites;
class _PhysicsDebugDraw extends box2d.DebugDraw { class _PhysicsDebugDraw extends box2d.DebugDraw {
_PhysicsDebugDraw( _PhysicsDebugDraw(
box2d.ViewportTransform transform, box2d.ViewportTransform transform,
this.physicsNode this.physicsWorld
) : super(transform) { ) : super(transform) {
appendFlags( appendFlags(
box2d.DebugDraw.JOINT_BIT | box2d.DebugDraw.JOINT_BIT |
...@@ -12,7 +12,7 @@ class _PhysicsDebugDraw extends box2d.DebugDraw { ...@@ -12,7 +12,7 @@ class _PhysicsDebugDraw extends box2d.DebugDraw {
); );
} }
PhysicsWorld physicsNode; PhysicsWorld physicsWorld;
PaintingCanvas canvas; PaintingCanvas canvas;
...@@ -93,12 +93,12 @@ class _PhysicsDebugDraw extends box2d.DebugDraw { ...@@ -93,12 +93,12 @@ class _PhysicsDebugDraw extends box2d.DebugDraw {
Point _toPoint(Vector2 vec) { Point _toPoint(Vector2 vec) {
return new Point( return new Point(
vec.x * physicsNode.b2WorldToNodeConversionFactor, vec.x * physicsWorld.b2WorldToNodeConversionFactor,
vec.y * physicsNode.b2WorldToNodeConversionFactor vec.y * physicsWorld.b2WorldToNodeConversionFactor
); );
} }
double _scale(double value) { double _scale(double value) {
return value * physicsNode.b2WorldToNodeConversionFactor; return value * physicsWorld.b2WorldToNodeConversionFactor;
} }
} }
part of flutter_sprites; part of flutter_sprites;
/// A [Node] that acts as a middle layer between a [PhysicsWorld] and a node
/// with an assigned [PhysicsBody]. The group's transformations are limited to
/// [position], [rotation], and uniform [scale].
///
/// PhysicsGroup group = new PhysicsGroup();
/// myWorld.addChild(group);
/// group.addChild(myNode);
class PhysicsGroup extends Node { class PhysicsGroup extends Node {
set scaleX(double scaleX) { set scaleX(double scaleX) {
......
...@@ -2,6 +2,9 @@ part of flutter_sprites; ...@@ -2,6 +2,9 @@ part of flutter_sprites;
typedef void PhysicsJointBreakCallback(PhysicsJoint joint); typedef void PhysicsJointBreakCallback(PhysicsJoint joint);
/// A joint connects two physics bodies and restricts their movements. Some
/// types of joints also support motors that adds forces to the connected
/// bodies.
abstract class PhysicsJoint { abstract class PhysicsJoint {
PhysicsJoint(this._bodyA, this._bodyB, this.breakingForce, this.breakCallback) { PhysicsJoint(this._bodyA, this._bodyB, this.breakingForce, this.breakCallback) {
bodyA._joints.add(this); bodyA._joints.add(this);
...@@ -10,12 +13,20 @@ abstract class PhysicsJoint { ...@@ -10,12 +13,20 @@ abstract class PhysicsJoint {
PhysicsBody _bodyA; PhysicsBody _bodyA;
/// The first body connected to the joint.
///
/// PhysicsBody body = myJoint.bodyA;
PhysicsBody get bodyA => _bodyA; PhysicsBody get bodyA => _bodyA;
PhysicsBody _bodyB; PhysicsBody _bodyB;
/// The second body connected to the joint.
///
/// PhysicsBody body = myJoint.bodyB;
PhysicsBody get bodyB => _bodyB; PhysicsBody get bodyB => _bodyB;
/// The maximum force the joint can handle before it breaks. If set to null,
/// the joint will never break.
final double breakingForce; final double breakingForce;
final PhysicsJointBreakCallback breakCallback; final PhysicsJointBreakCallback breakCallback;
...@@ -23,33 +34,35 @@ abstract class PhysicsJoint { ...@@ -23,33 +34,35 @@ abstract class PhysicsJoint {
bool _active = true; bool _active = true;
box2d.Joint _joint; box2d.Joint _joint;
PhysicsWorld _physicsNode; PhysicsWorld _physicsWorld;
void _completeCreation() { void _completeCreation() {
if (bodyA._attached && bodyB._attached) { if (bodyA._attached && bodyB._attached) {
_attach(bodyA._physicsNode); _attach(bodyA._physicsWorld);
} }
} }
void _attach(PhysicsWorld physicsNode) { void _attach(PhysicsWorld physicsNode) {
if (_joint == null) { if (_joint == null) {
_physicsNode = physicsNode; _physicsWorld = physicsNode;
_joint = _createB2Joint(physicsNode); _joint = _createB2Joint(physicsNode);
_physicsNode._joints.add(this); _physicsWorld._joints.add(this);
} }
} }
void _detach() { void _detach() {
if (_joint != null && _active) { if (_joint != null && _active) {
_physicsNode.b2World.destroyJoint(_joint); _physicsWorld.b2World.destroyJoint(_joint);
_joint = null; _joint = null;
_physicsNode._joints.remove(this); _physicsWorld._joints.remove(this);
} }
_active = false; _active = false;
} }
box2d.Joint _createB2Joint(PhysicsWorld physicsNode); box2d.Joint _createB2Joint(PhysicsWorld physicsNode);
/// If the joint is no longer needed, call the the [destroy] method to detach
/// if from its connected bodies.
void destroy() { void destroy() {
_detach(); _detach();
} }
...@@ -73,6 +86,26 @@ abstract class PhysicsJoint { ...@@ -73,6 +86,26 @@ abstract class PhysicsJoint {
} }
} }
/// 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
/// );
class PhysicsJointRevolute extends PhysicsJoint { class PhysicsJointRevolute extends PhysicsJoint {
PhysicsJointRevolute( PhysicsJointRevolute(
PhysicsBody bodyA, PhysicsBody bodyA,
...@@ -94,12 +127,24 @@ class PhysicsJointRevolute extends PhysicsJoint { ...@@ -94,12 +127,24 @@ class PhysicsJointRevolute extends PhysicsJoint {
} }
final Point _worldAnchor; final Point _worldAnchor;
/// The lower angle of the limits of this joint, only used if [enableLimit]
/// is set to true.
final double lowerAngle; final double lowerAngle;
/// The upper angle of the limits of this joint, only used if [enableLimit]
/// is set to true.
final double upperAngle; final double upperAngle;
/// If set to true, the rotation will be limited to a value between
/// [lowerAngle] and [upperAngle].
final bool enableLimit; final bool enableLimit;
bool _enableMotor; bool _enableMotor;
/// 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].
bool get enableMotor => _enableMotor; bool get enableMotor => _enableMotor;
set enableMotor(bool enableMotor) { set enableMotor(bool enableMotor) {
...@@ -112,6 +157,8 @@ class PhysicsJointRevolute extends PhysicsJoint { ...@@ -112,6 +157,8 @@ class PhysicsJointRevolute extends PhysicsJoint {
double _motorSpeed; double _motorSpeed;
/// 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.
double get motorSpeed => _motorSpeed; double get motorSpeed => _motorSpeed;
set motorSpeed(double motorSpeed) { set motorSpeed(double motorSpeed) {
...@@ -126,6 +173,8 @@ class PhysicsJointRevolute extends PhysicsJoint { ...@@ -126,6 +173,8 @@ class PhysicsJointRevolute extends PhysicsJoint {
double get maxMotorTorque => _maxMotorTorque; double get maxMotorTorque => _maxMotorTorque;
/// 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.
set maxMotorTorque(double maxMotorTorque) { set maxMotorTorque(double maxMotorTorque) {
_maxMotorTorque = maxMotorTorque; _maxMotorTorque = maxMotorTorque;
if (_joint != null) { if (_joint != null) {
...@@ -156,6 +205,25 @@ class PhysicsJointRevolute extends PhysicsJoint { ...@@ -156,6 +205,25 @@ class PhysicsJointRevolute extends PhysicsJoint {
} }
} }
/// 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)
/// );
class PhysicsJointPrismatic extends PhysicsJoint { class PhysicsJointPrismatic extends PhysicsJoint {
PhysicsJointPrismatic( PhysicsJointPrismatic(
PhysicsBody bodyA, PhysicsBody bodyA,
...@@ -174,10 +242,14 @@ class PhysicsJointPrismatic extends PhysicsJoint { ...@@ -174,10 +242,14 @@ class PhysicsJointPrismatic extends PhysicsJoint {
_completeCreation(); _completeCreation();
} }
/// Axis that the movement is restricted to (in global space at the time of
/// creation)
final Offset axis; final Offset axis;
bool _enableMotor; bool _enableMotor;
/// For the motor to be effective you also need to set [motorSpeed] and
/// [maxMotorForce].
bool get enableMotor => _enableMotor; bool get enableMotor => _enableMotor;
set enableMotor(bool enableMotor) { set enableMotor(bool enableMotor) {
...@@ -190,25 +262,29 @@ class PhysicsJointPrismatic extends PhysicsJoint { ...@@ -190,25 +262,29 @@ class PhysicsJointPrismatic extends PhysicsJoint {
double _motorSpeed; double _motorSpeed;
/// 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.
double get motorSpeed => _motorSpeed; double get motorSpeed => _motorSpeed;
set motorSpeed(double motorSpeed) { set motorSpeed(double motorSpeed) {
_motorSpeed = motorSpeed; _motorSpeed = motorSpeed;
if (_joint != null) { if (_joint != null) {
box2d.PrismaticJoint prismaticJoint = _joint; box2d.PrismaticJoint prismaticJoint = _joint;
prismaticJoint.setMotorSpeed(motorSpeed / _physicsNode.b2WorldToNodeConversionFactor); prismaticJoint.setMotorSpeed(motorSpeed / _physicsWorld.b2WorldToNodeConversionFactor);
} }
} }
double _maxMotorForce; double _maxMotorForce;
/// 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.
double get maxMotorForce => _maxMotorForce; double get maxMotorForce => _maxMotorForce;
set maxMotorForce(double maxMotorForce) { set maxMotorForce(double maxMotorForce) {
_maxMotorForce = maxMotorForce; _maxMotorForce = maxMotorForce;
if (_joint != null) { if (_joint != null) {
box2d.PrismaticJoint prismaticJoint = _joint; box2d.PrismaticJoint prismaticJoint = _joint;
prismaticJoint.setMaxMotorForce(maxMotorForce / _physicsNode.b2WorldToNodeConversionFactor); prismaticJoint.setMaxMotorForce(maxMotorForce / _physicsWorld.b2WorldToNodeConversionFactor);
} }
} }
...@@ -223,6 +299,9 @@ class PhysicsJointPrismatic extends PhysicsJoint { ...@@ -223,6 +299,9 @@ class PhysicsJointPrismatic extends PhysicsJoint {
} }
} }
/// The weld joint attempts to constrain all relative motion between two bodies.
///
/// new PhysicsJointWeld(bodyA.physicsJoint, bodyB.physicsJoint)
class PhysicsJointWeld extends PhysicsJoint { class PhysicsJointWeld extends PhysicsJoint {
PhysicsJointWeld( PhysicsJointWeld(
PhysicsBody bodyA, PhysicsBody bodyA,
...@@ -252,6 +331,22 @@ class PhysicsJointWeld extends PhysicsJoint { ...@@ -252,6 +331,22 @@ class PhysicsJointWeld extends PhysicsJoint {
} }
} }
/// 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
/// );
class PhysicsJointPulley extends PhysicsJoint { class PhysicsJointPulley extends PhysicsJoint {
PhysicsJointPulley( PhysicsJointPulley(
PhysicsBody bodyA, PhysicsBody bodyA,
...@@ -289,18 +384,27 @@ class PhysicsJointPulley extends PhysicsJoint { ...@@ -289,18 +384,27 @@ class PhysicsJointPulley extends PhysicsJoint {
} }
} }
/// 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);
class PhysicsJointGear extends PhysicsJoint { class PhysicsJointGear extends PhysicsJoint {
PhysicsJointGear( PhysicsJointGear(
PhysicsBody bodyA, PhysicsBody bodyA,
PhysicsBody bodyB, { PhysicsBody bodyB, {
double breakingForce, double breakingForce,
PhysicsJointBreakCallback breakCallback, PhysicsJointBreakCallback breakCallback,
this.ratio: 0.0 this.ratio: 1.0
} }
) : super(bodyA, bodyB, breakingForce, breakCallback) { ) : super(bodyA, bodyB, breakingForce, breakCallback) {
_completeCreation(); _completeCreation();
} }
/// The ratio of the rotation for bodyA relative bodyB.
final double ratio; final double ratio;
box2d.Joint _createB2Joint(PhysicsWorld physicsNode) { box2d.Joint _createB2Joint(PhysicsWorld physicsNode) {
...@@ -313,6 +417,8 @@ class PhysicsJointGear extends PhysicsJoint { ...@@ -313,6 +417,8 @@ class PhysicsJointGear extends PhysicsJoint {
} }
} }
/// Keeps a fixed distance between two bodies, [anchorA] and [anchorB] are
/// defined in world coordinates.
class PhysicsJointDistance extends PhysicsJoint { class PhysicsJointDistance extends PhysicsJoint {
PhysicsJointDistance( PhysicsJointDistance(
PhysicsBody bodyA, PhysicsBody bodyA,
...@@ -329,10 +435,21 @@ class PhysicsJointDistance extends PhysicsJoint { ...@@ -329,10 +435,21 @@ class PhysicsJointDistance extends PhysicsJoint {
_completeCreation(); _completeCreation();
} }
/// The anchor of bodyA in world coordinates at the time of creation.
final Point anchorA; final Point anchorA;
/// The anchor of bodyB in world coordinates at the time of creation.
final Point anchorB; final Point anchorB;
/// 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.
final double length; final double length;
/// Dampening factor.
final double dampening; final double dampening;
/// Dampening frequency.
final double frequency; final double frequency;
box2d.Joint _createB2Joint(PhysicsWorld physicsNode) { box2d.Joint _createB2Joint(PhysicsWorld physicsNode) {
...@@ -352,6 +469,8 @@ class PhysicsJointDistance extends PhysicsJoint { ...@@ -352,6 +469,8 @@ class PhysicsJointDistance extends PhysicsJoint {
} }
} }
/// The wheel joint restricts a point on bodyB to a line on bodyA. The wheel
/// joint also optionally provides a suspension spring.
class PhysicsJointWheel extends PhysicsJoint { class PhysicsJointWheel extends PhysicsJoint {
PhysicsJointWheel( PhysicsJointWheel(
PhysicsBody bodyA, PhysicsBody bodyA,
...@@ -367,9 +486,16 @@ class PhysicsJointWheel extends PhysicsJoint { ...@@ -367,9 +486,16 @@ class PhysicsJointWheel extends PhysicsJoint {
_completeCreation(); _completeCreation();
} }
/// The rotational point in global space at the time of creation.
final Point anchor; final Point anchor;
/// The axis which to restrict the movement to.
final Offset axis; final Offset axis;
/// Dampening factor.
final double dampening; final double dampening;
/// Dampening frequency.
final double frequency; final double frequency;
box2d.Joint _createB2Joint(PhysicsWorld physicsNode) { box2d.Joint _createB2Joint(PhysicsWorld physicsNode) {
...@@ -387,6 +513,8 @@ class PhysicsJointWheel extends PhysicsJoint { ...@@ -387,6 +513,8 @@ class PhysicsJointWheel extends PhysicsJoint {
} }
} }
/// The friction joint is used for top-down friction. The joint provides 2D
/// translational friction and angular friction.
class PhysicsJointFriction extends PhysicsJoint { class PhysicsJointFriction extends PhysicsJoint {
PhysicsJointFriction( PhysicsJointFriction(
PhysicsBody bodyA, PhysicsBody bodyA,
......
part of flutter_sprites; part of flutter_sprites;
/// Defines the shape of a [PhysicsBody].
abstract class PhysicsShape { abstract class PhysicsShape {
box2d.Shape _b2Shape; box2d.Shape _b2Shape;
...@@ -20,6 +21,9 @@ abstract class PhysicsShape { ...@@ -20,6 +21,9 @@ abstract class PhysicsShape {
} }
} }
/// Defines a circle shape with a given center [point] and [radius].
///
/// var shape = PhysicsShapeCircle(Point.origin, 20.0);
class PhysicsShapeCircle extends PhysicsShape { class PhysicsShapeCircle extends PhysicsShape {
PhysicsShapeCircle(this.point, this.radius); PhysicsShapeCircle(this.point, this.radius);
...@@ -35,6 +39,14 @@ class PhysicsShapeCircle extends PhysicsShape { ...@@ -35,6 +39,14 @@ class PhysicsShapeCircle extends PhysicsShape {
} }
} }
/// Defines a polygon shape from a list of [points];
///
/// var points = [
/// new Point(-10.0, 0.0),
/// new Point(0.0, 10.0),
/// new Point(10.0, 0.0)
/// ];
/// var shape = new PhysicsShapePolygon(points);
class PhysicsShapePolygon extends PhysicsShape { class PhysicsShapePolygon extends PhysicsShape {
PhysicsShapePolygon(this.points); PhysicsShapePolygon(this.points);
...@@ -56,6 +68,9 @@ class PhysicsShapePolygon extends PhysicsShape { ...@@ -56,6 +68,9 @@ class PhysicsShapePolygon extends PhysicsShape {
} }
} }
/// Defines a box shape from a [width] and [height].
///
/// var shape = new PhysicsShapeBox(50.0, 100.0);
class PhysicsShapeBox extends PhysicsShape { class PhysicsShapeBox extends PhysicsShape {
PhysicsShapeBox( PhysicsShapeBox(
this.width, this.width,
...@@ -84,6 +99,15 @@ class PhysicsShapeBox extends PhysicsShape { ...@@ -84,6 +99,15 @@ class PhysicsShapeBox extends PhysicsShape {
} }
} }
/// Defines a chain shape from a set of [points]. This can be used to create
/// a continuous chain of edges or, if [loop] is set to true, concave polygons.
///
/// var points = [
/// new Point(-10.0, 0.0),
/// new Point(0.0, 10.0),
/// new Point(10.0, 0.0)
/// ];
/// var shape = new PhysicsShapeChain(points);
class PhysicsShapeChain extends PhysicsShape { class PhysicsShapeChain extends PhysicsShape {
PhysicsShapeChain(this.points, [this.loop=false]); PhysicsShapeChain(this.points, [this.loop=false]);
...@@ -109,6 +133,12 @@ class PhysicsShapeChain extends PhysicsShape { ...@@ -109,6 +133,12 @@ class PhysicsShapeChain extends PhysicsShape {
} }
} }
/// Defines a single edge line shape from [pointA] to [pointB].
///
/// var shape = new PhysicsShapeEdge(
/// new Point(20.0, 20.0),
/// new Point(50.0, 20.0)
/// );
class PhysicsShapeEdge extends PhysicsShape { class PhysicsShapeEdge extends PhysicsShape {
PhysicsShapeEdge(this.pointA, this.pointB); PhysicsShapeEdge(this.pointA, this.pointB);
...@@ -131,6 +161,11 @@ class PhysicsShapeEdge extends PhysicsShape { ...@@ -131,6 +161,11 @@ class PhysicsShapeEdge extends PhysicsShape {
} }
} }
/// A group combines several [shapes] into a single shape.
///
/// var s0 = new PhysicsShapeCircle(new Point(-10.0, 0.0), 20.0);
/// var s1 = new PhysicsShapeCircle(new Point(10.0, 0.0), 20.0);
/// var shape = new PhysicsShapeGroup([s0, s1]);
class PhysicsShapeGroup extends PhysicsShape { class PhysicsShapeGroup extends PhysicsShape {
PhysicsShapeGroup(this.shapes); PhysicsShapeGroup(this.shapes);
......
...@@ -9,6 +9,14 @@ enum PhysicsContactType { ...@@ -9,6 +9,14 @@ enum PhysicsContactType {
typedef void PhysicsContactCallback(PhysicsContactType type, PhysicsContact contact); typedef void PhysicsContactCallback(PhysicsContactType type, PhysicsContact contact);
/// A [Node] that performs a 2D physics simulation on any children with a
/// [PhysicsBody] attached. To simulate grand children, they need to be placed
/// in a [PhysicsGroup].
///
/// The PhysicsWorld uses Box2D.dart to perform the actual simulation, but
/// wraps its behavior in a way that is more integrated with the sprite node
/// tree. If needed, you can still access the Box2D world through the [b2World]
/// property.
class PhysicsWorld extends Node { class PhysicsWorld extends Node {
PhysicsWorld(Offset gravity) { PhysicsWorld(Offset gravity) {
b2World = new box2d.World.withGravity( b2World = new box2d.World.withGravity(
...@@ -35,6 +43,7 @@ class PhysicsWorld extends Node { ...@@ -35,6 +43,7 @@ class PhysicsWorld extends Node {
b2World.debugDraw = _debugDraw; b2World.debugDraw = _debugDraw;
} }
/// The Box2D world used to perform the physics simulations.
box2d.World b2World; box2d.World b2World;
_ContactHandler _contactHandler; _ContactHandler _contactHandler;
...@@ -47,14 +56,19 @@ class PhysicsWorld extends Node { ...@@ -47,14 +56,19 @@ class PhysicsWorld extends Node {
List<PhysicsBody> _bodiesScheduledForUpdate = <PhysicsBody>[]; List<PhysicsBody> _bodiesScheduledForUpdate = <PhysicsBody>[];
/// If set to true, a debug image of all physics shapes and joints will
/// be drawn on top of the [SpriteBox].
bool drawDebug = false; bool drawDebug = false;
Matrix4 _debugDrawTransform ; Matrix4 _debugDrawTransform ;
_PhysicsDebugDraw _debugDraw; _PhysicsDebugDraw _debugDraw;
/// The conversion factor that is used to convert points in the physics world
/// node to points in the Box2D physics simulation.
double b2WorldToNodeConversionFactor = 10.0; double b2WorldToNodeConversionFactor = 10.0;
/// The gravity vector used in the simulation.
Offset get gravity { Offset get gravity {
Vector2 g = b2World.getGravity(); Vector2 g = b2World.getGravity();
return new Offset(g.x, g.y); return new Offset(g.x, g.y);
...@@ -66,12 +80,14 @@ class PhysicsWorld extends Node { ...@@ -66,12 +80,14 @@ class PhysicsWorld extends Node {
gravity.dy / b2WorldToNodeConversionFactor)); gravity.dy / b2WorldToNodeConversionFactor));
} }
/// If set to true, objects can fall asleep if the haven't moved in a while.
bool get allowSleep => b2World.isAllowSleep(); bool get allowSleep => b2World.isAllowSleep();
set allowSleep(bool allowSleep) { set allowSleep(bool allowSleep) {
b2World.setAllowSleep(allowSleep); b2World.setAllowSleep(allowSleep);
} }
/// True if sub stepping should be used in the simulation.
bool get subStepping => b2World.isSubStepping(); bool get subStepping => b2World.isSubStepping();
set subStepping(bool subStepping) { set subStepping(bool subStepping) {
...@@ -227,6 +243,26 @@ class PhysicsWorld extends Node { ...@@ -227,6 +243,26 @@ class PhysicsWorld extends Node {
} }
} }
/// Adds a contact callback, the callback will be invoked when bodies collide
/// in the world.
///
/// To match specific sets bodies, use the [tagA] and [tagB]
/// which will be matched to the tag property that is set on the
/// [PhysicsBody]. If [tagA] or [tagB] is set to null, it will match any
/// body.
///
/// By default, callbacks are made at four different times during a
/// collision; preSolve, postSolve, begin, and end. If you are only interested
/// in one of these events you can pass in a [type].
///
/// myWorld.addContactCallback(
/// (PhysicsContactType type, PhysicsContact contact) {
/// print("Collision between ship and asteroid");
/// },
/// "Ship",
/// "Asteroid",
/// PhysicsContactType.begin
/// );
void addContactCallback(PhysicsContactCallback callback, Object tagA, Object tagB, [PhysicsContactType type]) { void addContactCallback(PhysicsContactCallback callback, Object tagA, Object tagB, [PhysicsContactType type]) {
_contactHandler.addContactCallback(callback, tagA, tagB, type); _contactHandler.addContactCallback(callback, tagA, tagB, type);
} }
...@@ -238,12 +274,21 @@ class PhysicsWorld extends Node { ...@@ -238,12 +274,21 @@ class PhysicsWorld extends Node {
super.paint(canvas); super.paint(canvas);
} }
/// Draws the debug data of the physics world, normally this method isn't
/// invoked directly. Instead, set the [drawDebug] property to true.
void paintDebug(PaintingCanvas canvas) { void paintDebug(PaintingCanvas canvas) {
_debugDraw.canvas = canvas; _debugDraw.canvas = canvas;
b2World.drawDebugData(); b2World.drawDebugData();
} }
} }
/// Contains information about a physics collision and is normally passed back
/// in callbacks from the [PhysicsWorld].
///
/// void myCallback(PhysicsContactType type, PhysicsContact contact) {
/// if (contact.isTouching)
/// print("Bodies are touching");
/// }
class PhysicsContact { class PhysicsContact {
PhysicsContact( PhysicsContact(
this.nodeA, this.nodeA,
...@@ -256,13 +301,29 @@ class PhysicsContact { ...@@ -256,13 +301,29 @@ class PhysicsContact {
this.touchingNormal this.touchingNormal
); );
/// The first node as matched in the rules set when adding the callback.
final Node nodeA; final Node nodeA;
/// The second node as matched in the rules set when adding the callback.
final Node nodeB; final Node nodeB;
/// The first shape as matched in the rules set when adding the callback.
final PhysicsShape shapeA; final PhysicsShape shapeA;
/// The second shape as matched in the rules set when adding the callback.
final PhysicsShape shapeB; final PhysicsShape shapeB;
/// True if the two nodes are touching.
final isTouching; final isTouching;
/// To ignore the collision to take place, you can set isEnabled to false
/// during the preSolve phase.
bool isEnabled; bool isEnabled;
/// List of points that are touching, in world coordinates.
final List<Point> touchingPoints; final List<Point> touchingPoints;
/// The normal from [shapeA] to [shapeB] at the touchingPoint.
final Offset touchingNormal; final Offset touchingNormal;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment