585 lines
17 KiB
Dart
585 lines
17 KiB
Dart
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;
|
|
|
|
void 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;
|
|
|
|
void 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.
|
|
void 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;
|
|
|
|
void 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;
|
|
|
|
void 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;
|
|
|
|
void 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
|
|
);
|
|
}
|