docs: MotorJoint doc and example (#2394)

Documentation covering MotorJoint and a usage example.
This commit is contained in:
Eugene Kleshnin
2023-03-12 22:31:20 +00:00
committed by GitHub
parent 63704c40ee
commit cb52f5a58a
8 changed files with 185 additions and 4 deletions

View File

@ -21,7 +21,7 @@ Currently, Forge2D supports the following joints:
- [`DistanceJoint`](#distancejoint)
- [`FrictionJoint`](#frictionjoint)
- GearJoint
- MotorJoint
- [`MotorJoint`](#motorjoint)
- MouseJoint
- PrismaticJoint
- PulleyJoint
@ -160,3 +160,57 @@ values:
In other words, the former simulates the friction, when the body is sliding and the latter simulates
the friction when the body is spinning.
### `MotorJoint`
A `MotorJoint` is used to control the relative motion between two bodies. A typical usage is to
control the movement of a dynamic body with respect to the fixed point, for example to create
animations.
A `MotorJoint` lets you control the motion of a body by specifying target position and rotation
offsets. You can set the maximum motor force and torque that will be applied to reach the target
position and rotation. If the body is blocked, it will stop and the contact forces will be
proportional the maximum motor force and torque.
```dart
final motorJointDef = MotorJointDef()
..initialize(first, second)
..maxTorque = 1000
..maxForce = 1000
..correctionFactor = 0.1;
world.createJoint(MotorJoint(motorJointDef));
```
A `MotorJointDef` has three optional parameters:
- `maxForce`: the maximum translational force which will be applied to the joined body to reach the
target position.
- `maxTorque`: the maximum angular force which will be applied to the joined body to reach the
target rotation.
- `correctionFactor`: position correction factor in range [0, 1]. It adjusts the joint's response to
deviation from target position. A higher value makes the joint respond faster, while a lower value
makes it respond slower. If the value is set too high, the joint may overcompensate and oscillate,
becoming unstable. If set too low, it may respond too slowly.
The linear and angular offsets are the target distance and angle that the bodies should achieve
relative to each other's position and rotation. By default, the linear target will be the distance
between the two body centers and the angular target will be the relative rotation of the bodies.
Use the `setLinearOffset(Vector2)` and `setLinearOffset(double)` methods of the `MotorJoint` to set
the desired relative translation and rotate between the bodies.
For example, this code increments the angular offset of the joint every update cycle, causing the
body to rotate.
```dart
@override
void update(double dt) {
super.update(dt);
final angularOffset = joint.getAngularOffset() + motorSpeed * dt;
joint.setAngularOffset(angularOffset);
}
```

View File

@ -8,6 +8,7 @@ import 'package:examples/stories/bridge_libraries/forge2d/flame_forge2d.dart';
import 'package:examples/stories/bridge_libraries/forge2d/joints/constant_volume_joint.dart';
import 'package:examples/stories/bridge_libraries/forge2d/joints/distance_joint.dart';
import 'package:examples/stories/bridge_libraries/forge2d/joints/friction_joint.dart';
import 'package:examples/stories/bridge_libraries/forge2d/joints/motor_joint.dart';
import 'package:examples/stories/camera_and_viewport/camera_and_viewport.dart';
import 'package:examples/stories/collision_detection/collision_detection.dart';
import 'package:examples/stories/components/components.dart';
@ -37,6 +38,7 @@ void main() {
'constant_volume_joint': ConstantVolumeJointExample.new,
'distance_joint': DistanceJointExample.new,
'friction_joint': FrictionJointExample.new,
'motor_joint': MotorJointExample.new,
};
final game = routes[page]?.call();
if (game != null) {

View File

@ -11,6 +11,7 @@ import 'package:examples/stories/bridge_libraries/forge2d/joint_example.dart';
import 'package:examples/stories/bridge_libraries/forge2d/joints/constant_volume_joint.dart';
import 'package:examples/stories/bridge_libraries/forge2d/joints/distance_joint.dart';
import 'package:examples/stories/bridge_libraries/forge2d/joints/friction_joint.dart';
import 'package:examples/stories/bridge_libraries/forge2d/joints/motor_joint.dart';
import 'package:examples/stories/bridge_libraries/forge2d/mouse_joint_example.dart';
import 'package:examples/stories/bridge_libraries/forge2d/raycast_example.dart';
import 'package:examples/stories/bridge_libraries/forge2d/revolute_joint_example.dart';
@ -117,18 +118,24 @@ void addJointsStories(Dashbook dashbook) {
'ConstantVolumeJoint',
(DashbookContext ctx) => GameWidget(game: ConstantVolumeJointExample()),
codeLink: link('constant_volume_joint.dart'),
info: BlobExample.description,
info: ConstantVolumeJointExample.description,
)
.add(
'DistanceJoint',
(DashbookContext ctx) => GameWidget(game: DistanceJointExample()),
codeLink: link('distance_joint.dart'),
info: BlobExample.description,
info: DistanceJointExample.description,
)
.add(
'FrictionJoint',
(DashbookContext ctx) => GameWidget(game: FrictionJointExample()),
codeLink: link('friction_joint.dart'),
info: BlobExample.description,
info: FrictionJointExample.description,
)
.add(
'MotorJoint',
(DashbookContext ctx) => GameWidget(game: MotorJointExample()),
codeLink: link('motor_joint.dart'),
info: MotorJointExample.description,
);
}

View File

@ -6,6 +6,11 @@ import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
class ConstantVolumeJointExample extends Forge2DGame with TapDetector {
static const description = '''
This example shows how to use a `ConstantVolumeJoint`. Tap the screen to add
a bunch off balls, that maintain a constant volume within them.
''';
@override
Future<void> onLoad() async {
super.onLoad();

View File

@ -4,6 +4,11 @@ import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
class DistanceJointExample extends Forge2DGame with TapDetector {
static const description = '''
This example shows how to use a `DistanceJoint`. Tap the screen to add a
pair of balls joined with a `DistanceJoint`.
''';
@override
Future<void> onLoad() async {
super.onLoad();

View File

@ -4,6 +4,10 @@ import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
class FrictionJointExample extends Forge2DGame with TapDetector {
static const description = '''
This example shows how to use a `FrictionJoint`. Tap the screen to move the
ball around and observe it slows down due to the friction force.
''';
FrictionJointExample() : super(gravity: Vector2.all(0));
late Wall border;

View File

@ -0,0 +1,82 @@
import 'dart:ui';
import 'package:examples/stories/bridge_libraries/forge2d/utils/balls.dart';
import 'package:examples/stories/bridge_libraries/forge2d/utils/boxes.dart';
import 'package:flame/events.dart';
import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
class MotorJointExample extends Forge2DGame with TapDetector, HasDraggables {
static const description = '''
This example shows how to use a `MotorJoint`. The ball spins around the
center point. Tap the screen to change the direction.
''';
late Ball ball;
late MotorJoint joint;
final motorSpeed = 1;
bool clockWise = true;
MotorJointExample() : super(gravity: Vector2.zero());
@override
Future<void> onLoad() async {
super.onLoad();
final box = Box(size / 2, 2, 1);
add(box);
ball = Ball(Vector2(size.x / 2, size.y / 2 - 5));
add(ball);
await Future.wait([ball.loaded, box.loaded]);
joint = createJoint(ball.body, box.body);
}
@override
Future<void> onTapDown(TapDownInfo details) async {
super.onTapDown(details);
clockWise = !clockWise;
}
MotorJoint createJoint(Body first, Body second) {
final motorJointDef = MotorJointDef()
..initialize(first, second)
..maxForce = 1000
..maxTorque = 1000
..correctionFactor = 0.1;
final joint = MotorJoint(motorJointDef);
world.createJoint(joint);
return joint;
}
@override
void update(double dt) {
super.update(dt);
var deltaOffset = motorSpeed * dt;
if (clockWise) {
deltaOffset = -deltaOffset;
}
final linearOffsetX = joint.getLinearOffset().x + deltaOffset;
final linearOffsetY = joint.getLinearOffset().y + deltaOffset;
final linearOffset = Vector2(linearOffsetX, linearOffsetY);
final angularOffset = joint.getAngularOffset() + deltaOffset;
joint.setLinearOffset(linearOffset);
joint.setAngularOffset(angularOffset);
}
@override
void render(Canvas canvas) {
super.render(canvas);
final p1 = worldToScreen(joint.anchorA);
final p2 = worldToScreen(joint.anchorB);
canvas.drawLine(p1.toOffset(), p2.toOffset(), debugPaint);
}
}

View File

@ -0,0 +1,22 @@
import 'package:flame_forge2d/flame_forge2d.dart';
class Box extends BodyComponent {
final Vector2 _position;
final double _width;
final double _height;
Box(this._position, this._width, this._height);
@override
Body createBody() {
final shape = PolygonShape()
..setAsBox(_width / 2, _height / 2, Vector2.zero(), 0);
final fixtureDef = FixtureDef(shape, friction: 0.3);
final bodyDef = BodyDef(
userData: this, // To be able to determine object in collision
position: _position,
);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}