| part of flutter_sprites; |
| |
| 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 { |
| PhysicsJoint(this._bodyA, this._bodyB, this.breakingForce, this.breakCallback) { |
| bodyA._joints.add(this); |
| bodyB._joints.add(this); |
| } |
| |
| PhysicsBody _bodyA; |
| |
| /// The first body connected to the joint. |
| /// |
| /// PhysicsBody body = myJoint.bodyA; |
| PhysicsBody get bodyA => _bodyA; |
| |
| PhysicsBody _bodyB; |
| |
| /// The second body connected to the joint. |
| /// |
| /// PhysicsBody body = myJoint.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 PhysicsJointBreakCallback breakCallback; |
| |
| bool _active = true; |
| box2d.Joint _joint; |
| |
| PhysicsWorld _physicsWorld; |
| |
| void _completeCreation() { |
| if (bodyA._attached && bodyB._attached) { |
| _attach(bodyA._physicsWorld); |
| } |
| } |
| |
| void _attach(PhysicsWorld physicsNode) { |
| if (_joint == null) { |
| _physicsWorld = physicsNode; |
| _joint = _createB2Joint(physicsNode); |
| _physicsWorld._joints.add(this); |
| } |
| } |
| |
| void _detach() { |
| if (_joint != null && _active) { |
| _physicsWorld.b2World.destroyJoint(_joint); |
| _joint = null; |
| _physicsWorld._joints.remove(this); |
| } |
| _active = false; |
| } |
| |
| 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() { |
| _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) { |
| // Destroy the joint |
| destroy(); |
| |
| // Notify any observer |
| if (breakCallback != null) |
| breakCallback(this); |
| } |
| } |
| } |
| } |
| |
| /// 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 { |
| PhysicsJointRevolute( |
| PhysicsBody bodyA, |
| PhysicsBody bodyB, |
| this._worldAnchor, { |
| this.lowerAngle: 0.0, |
| this.upperAngle: 0.0, |
| this.enableLimit: false, |
| PhysicsJointBreakCallback breakCallback, |
| double breakingForce, |
| bool enableMotor: false, |
| double motorSpeed: 0.0, |
| double maxMotorTorque: 0.0 |
| }) : super(bodyA, bodyB, breakingForce, breakCallback) { |
| _enableMotor = enableMotor; |
| _motorSpeed = motorSpeed; |
| _maxMotorTorque = maxMotorTorque; |
| _completeCreation(); |
| } |
| |
| final Point _worldAnchor; |
| |
| /// The lower angle of the limits of this joint, only used if [enableLimit] |
| /// is set to true. |
| final double lowerAngle; |
| |
| /// The upper angle of the limits of this joint, only used if [enableLimit] |
| /// is set to true. |
| final double upperAngle; |
| |
| /// If set to true, the rotation will be limited to a value between |
| /// [lowerAngle] and [upperAngle]. |
| final bool enableLimit; |
| |
| 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; |
| |
| set enableMotor(bool enableMotor) { |
| _enableMotor = enableMotor; |
| if (_joint != null) { |
| box2d.RevoluteJoint revoluteJoint = _joint; |
| revoluteJoint.enableMotor(enableMotor); |
| } |
| } |
| |
| 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; |
| |
| set motorSpeed(double motorSpeed) { |
| _motorSpeed = motorSpeed; |
| if (_joint != null) { |
| box2d.RevoluteJoint revoluteJoint = _joint; |
| revoluteJoint.setMotorSpeed(radians(motorSpeed)); |
| } |
| } |
| |
| double _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) { |
| _maxMotorTorque = maxMotorTorque; |
| if (_joint != null) { |
| box2d.RevoluteJoint revoluteJoint = _joint; |
| revoluteJoint.setMaxMotorTorque(maxMotorTorque); |
| } |
| } |
| |
| box2d.Joint _createB2Joint(PhysicsWorld physicsNode) { |
| // Create Joint Definition |
| Vector2 vecAnchor = new Vector2( |
| _worldAnchor.x / physicsNode.b2WorldToNodeConversionFactor, |
| _worldAnchor.y / physicsNode.b2WorldToNodeConversionFactor |
| ); |
| |
| box2d.RevoluteJointDef b2Def = new box2d.RevoluteJointDef(); |
| b2Def.initialize(bodyA._body, bodyB._body, vecAnchor); |
| b2Def.enableLimit = enableLimit; |
| b2Def.lowerAngle = lowerAngle; |
| b2Def.upperAngle = upperAngle; |
| |
| b2Def.enableMotor = _enableMotor; |
| b2Def.motorSpeed = _motorSpeed; |
| b2Def.maxMotorTorque = _maxMotorTorque; |
| |
| // Create joint |
| return physicsNode.b2World.createJoint(b2Def); |
| } |
| } |
| |
| /// 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 { |
| PhysicsJointPrismatic( |
| PhysicsBody bodyA, |
| PhysicsBody bodyB, |
| this.axis, { |
| double breakingForce, |
| PhysicsJointBreakCallback breakCallback, |
| bool enableMotor: false, |
| double motorSpeed: 0.0, |
| double maxMotorForce: 0.0 |
| } |
| ) : super(bodyA, bodyB, breakingForce, breakCallback) { |
| _enableMotor = enableMotor; |
| _motorSpeed = motorSpeed; |
| _maxMotorForce = maxMotorForce; |
| _completeCreation(); |
| } |
| |
| /// Axis that the movement is restricted to (in global space at the time of |
| /// creation) |
| final Offset axis; |
| |
| bool _enableMotor; |
| |
| /// For the motor to be effective you also need to set [motorSpeed] and |
| /// [maxMotorForce]. |
| bool get enableMotor => _enableMotor; |
| |
| set enableMotor(bool enableMotor) { |
| _enableMotor = enableMotor; |
| if (_joint != null) { |
| box2d.PrismaticJoint prismaticJoint = _joint; |
| prismaticJoint.enableMotor(enableMotor); |
| } |
| } |
| |
| 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; |
| |
| set motorSpeed(double motorSpeed) { |
| _motorSpeed = motorSpeed; |
| if (_joint != null) { |
| box2d.PrismaticJoint prismaticJoint = _joint; |
| prismaticJoint.setMotorSpeed(motorSpeed / _physicsWorld.b2WorldToNodeConversionFactor); |
| } |
| } |
| |
| 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; |
| |
| set maxMotorForce(double maxMotorForce) { |
| _maxMotorForce = maxMotorForce; |
| if (_joint != null) { |
| box2d.PrismaticJoint prismaticJoint = _joint; |
| prismaticJoint.setMaxMotorForce(maxMotorForce / _physicsWorld.b2WorldToNodeConversionFactor); |
| } |
| } |
| |
| box2d.Joint _createB2Joint(PhysicsWorld physicsNode) { |
| box2d.PrismaticJointDef b2Def = new box2d.PrismaticJointDef(); |
| b2Def.initialize(bodyA._body, bodyB._body, bodyA._body.position, new Vector2(axis.dx, axis.dy)); |
| b2Def.enableMotor = _enableMotor; |
| b2Def.motorSpeed = _motorSpeed; |
| b2Def.maxMotorForce = _maxMotorForce; |
| |
| return physicsNode.b2World.createJoint(b2Def); |
| } |
| } |
| |
| /// The weld joint attempts to constrain all relative motion between two bodies. |
| /// |
| /// new PhysicsJointWeld(bodyA.physicsJoint, bodyB.physicsJoint) |
| class PhysicsJointWeld extends PhysicsJoint { |
| PhysicsJointWeld( |
| PhysicsBody bodyA, |
| PhysicsBody bodyB, { |
| double breakingForce, |
| PhysicsJointBreakCallback breakCallback, |
| this.dampening: 0.0, |
| this.frequency: 0.0 |
| } |
| ) : super(bodyA, bodyB, breakingForce, breakCallback) { |
| _completeCreation(); |
| } |
| |
| final double dampening; |
| final double frequency; |
| |
| box2d.Joint _createB2Joint(PhysicsWorld physicsNode) { |
| 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); |
| b2Def.dampingRatio = dampening; |
| b2Def.frequencyHz = frequency; |
| return physicsNode.b2World.createJoint(b2Def); |
| } |
| } |
| |
| /// 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 { |
| PhysicsJointPulley( |
| PhysicsBody bodyA, |
| PhysicsBody bodyB, |
| this.groundAnchorA, |
| this.groundAnchorB, |
| this.anchorA, |
| this.anchorB, |
| this.ratio, { |
| double breakingForce, |
| PhysicsJointBreakCallback breakCallback |
| } |
| ) : super(bodyA, bodyB, breakingForce, breakCallback) { |
| _completeCreation(); |
| } |
| |
| final Point groundAnchorA; |
| final Point groundAnchorB; |
| final Point anchorA; |
| final Point anchorB; |
| final double ratio; |
| |
| box2d.Joint _createB2Joint(PhysicsWorld physicsNode) { |
| 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); |
| } |
| } |
| |
| /// 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 { |
| PhysicsJointGear( |
| PhysicsBody bodyA, |
| PhysicsBody bodyB, { |
| double breakingForce, |
| PhysicsJointBreakCallback breakCallback, |
| this.ratio: 1.0 |
| } |
| ) : super(bodyA, bodyB, breakingForce, breakCallback) { |
| _completeCreation(); |
| } |
| |
| /// The ratio of the rotation for bodyA relative bodyB. |
| final double ratio; |
| |
| box2d.Joint _createB2Joint(PhysicsWorld physicsNode) { |
| box2d.GearJointDef b2Def = new box2d.GearJointDef(); |
| b2Def.bodyA = bodyA._body; |
| b2Def.bodyB = bodyB._body; |
| b2Def.ratio = ratio; |
| |
| return physicsNode.b2World.createJoint(b2Def); |
| } |
| } |
| |
| /// Keeps a fixed distance between two bodies, [anchorA] and [anchorB] are |
| /// defined in world coordinates. |
| class PhysicsJointDistance extends PhysicsJoint { |
| PhysicsJointDistance( |
| PhysicsBody bodyA, |
| PhysicsBody bodyB, |
| this.anchorA, |
| this.anchorB, { |
| double breakingForce, |
| PhysicsJointBreakCallback breakCallback, |
| this.length, |
| this.dampening: 0.0, |
| this.frequency: 0.0 |
| } |
| ) : super(bodyA, bodyB, breakingForce, breakCallback) { |
| _completeCreation(); |
| } |
| |
| /// The anchor of bodyA in world coordinates at the time of creation. |
| final Point anchorA; |
| |
| /// The anchor of bodyB in world coordinates at the time of creation. |
| 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; |
| |
| /// Dampening factor. |
| final double dampening; |
| |
| /// Dampening frequency. |
| final double frequency; |
| |
| box2d.Joint _createB2Joint(PhysicsWorld physicsNode) { |
| 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); |
| } |
| } |
| |
| /// 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 { |
| PhysicsJointWheel( |
| PhysicsBody bodyA, |
| PhysicsBody bodyB, |
| this.anchor, |
| this.axis, { |
| double breakingForce, |
| PhysicsJointBreakCallback breakCallback, |
| this.dampening: 0.0, |
| this.frequency: 0.0 |
| } |
| ) : super(bodyA, bodyB, breakingForce, breakCallback) { |
| _completeCreation(); |
| } |
| |
| /// The rotational point in global space at the time of creation. |
| final Point anchor; |
| |
| /// The axis which to restrict the movement to. |
| final Offset axis; |
| |
| /// Dampening factor. |
| final double dampening; |
| |
| /// Dampening frequency. |
| final double frequency; |
| |
| box2d.Joint _createB2Joint(PhysicsWorld physicsNode) { |
| 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); |
| } |
| } |
| |
| /// The friction joint is used for top-down friction. The joint provides 2D |
| /// translational friction and angular friction. |
| class PhysicsJointFriction extends PhysicsJoint { |
| PhysicsJointFriction( |
| PhysicsBody bodyA, |
| PhysicsBody bodyB, |
| this.anchor, { |
| double breakingForce, |
| PhysicsJointBreakCallback breakCallback, |
| this.maxForce: 0.0, |
| this.maxTorque: 0.0 |
| } |
| ) : super(bodyA, bodyB, breakingForce, breakCallback) { |
| _completeCreation(); |
| } |
| |
| final Point anchor; |
| final double maxForce; |
| final double maxTorque; |
| |
| box2d.Joint _createB2Joint(PhysicsWorld physicsNode) { |
| 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, |
| PhysicsJointBreakCallback breakCallback, |
| this.dampening, |
| this.frequency |
| } |
| ) : super(null, null, breakingForce, breakCallback) { |
| assert(bodies.length > 2); |
| _bodyA = bodies[0]; |
| _bodyB = bodies[1]; |
| _completeCreation(); |
| } |
| |
| final List<PhysicsBody> bodies; |
| final double dampening; |
| final double frequency; |
| |
| box2d.Joint _createB2Joint(PhysicsWorld physicsNode) { |
| 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); |
| } |
| } |
| |
| Vector2 _convertPosToVec(Point pt, PhysicsWorld physicsNode) { |
| return new Vector2( |
| pt.x / physicsNode.b2WorldToNodeConversionFactor, |
| pt.y / physicsNode.b2WorldToNodeConversionFactor |
| ); |
| } |