文章中的概念来自《Box2D》中文手册
上一节中我们介绍了关节和鼠标关节的使用方法,本章中我们继续介绍距离关节和旋转关节
距离关节是两个物体上各有一点,两点之间的距离必须固定不变。当指定一个距离关节时,两个物体必须已在应有的位置上。之后指定世界坐标中的两个锚点。第一个锚点连接到物体1,第二个锚点连接到物体2。这两点隐含距离约束的长度。
// Define the distance joint
DistanceJointDef distanceJointDef = new DistanceJointDef();
// 距离关节连接的2个Body
distanceJointDef.bodyA=smallBall;
distanceJointDef.bodyB=bigBall;
// 是否允许两个Body碰撞
distanceJointDef.collideConnected=false;
// 两个Body之间的距离
distanceJointDef.length = 2.0f;
关节可以具有弹性,通过定义2个常数:频率(frequency)和阻尼率(damping ratio)。频率影响震动的快慢,典型情况下频率要小于时间步的一半。比如每秒执行60次时间步,距离关节的频率就要小于30。
阻尼率无单位,取值在「0,1」之间。当阻尼率设置为1时,没有振动。
// 下面2个参数使关节具有弹性
distanceJointDef.dampingRatio = 0.4f;
distanceJointDef.frequencyHz = 4.0f;
/**
* 距离关节
*/
public class DistanceJointTest extends ApplicationAdapter {
World world;
Box2DDebugRenderer box2DDebugRenderer;
Body hitBody, groundBody;
OrthographicCamera camera;
Vector3 point = new Vector3();
float scene_width = 12.8f;
float scene_height = 7.2f;
QueryCallback callback = new QueryCallback() {
@Override
public boolean reportFixture(Fixture fixture) {
if (fixture.testPoint(point.x, point.y)) {
hitBody = fixture.getBody();
return false;
} else
return true;
}
};
@Override
public void create() {
world = new World(new Vector2(0.0f, -9.8f), true);
box2DDebugRenderer = new Box2DDebugRenderer();
camera = new OrthographicCamera(scene_width, scene_height);
camera.position.set(scene_width / 2, scene_height / 2, 0);
camera.update();
groundBody = createGroundWall();
Gdx.input.setInputProcessor(new HandA());
createDistanceJoint();
}
@Override
public void render() {
world.step( 1/ 60f, 6, 2);
Gdx.gl.glClearColor(0.39f, 0.58f, 0.92f, 1.0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
box2DDebugRenderer.render(world, camera.combined);
}
@Override
public void dispose() {
world.dispose();
box2DDebugRenderer.dispose();
}
public void createDistanceJoint() {
Body smallBall = createSphere(BodyDef.BodyType.DynamicBody, 0f, 3.75f, .8f, .8f, .4f, .25f);
Body bigBall = createSphere(BodyDef.BodyType.DynamicBody, 3.0f, 4.5f, .8f, 1f, .4f, .5f);
// Define the distance joint
DistanceJointDef distanceJointDef = new DistanceJointDef();
distanceJointDef.bodyA=smallBall;
distanceJointDef.bodyB=bigBall;
distanceJointDef.collideConnected=false;
distanceJointDef.length = 5.0f;
// 下面2个参数使关节具有弹性
distanceJointDef.dampingRatio = 0.4f;
distanceJointDef.frequencyHz = 4.0f;
distanceJointDef.localAnchorA.set(0,0);
distanceJointDef.localAnchorB.set(0,0);
world.createJoint(distanceJointDef);
}
private Body createSphere(BodyDef.BodyType type, float x, float y, float d, float r, float f, float radius) {
BodyDef bodyDef = new BodyDef();
bodyDef.type = type;
bodyDef.position.set(scene_width * 0.5f+x,y);
bodyDef.angle=0;
Body ball = world.createBody(bodyDef);
FixtureDef fixtureDef=new FixtureDef();
fixtureDef.density=d;
fixtureDef.restitution=r;
fixtureDef.friction=f;
fixtureDef.shape=new CircleShape();
fixtureDef.shape.setRadius(radius);
ball.createFixture(fixtureDef);
fixtureDef.shape.dispose();
return ball;
}
public Body createGroundWall() {
BodyDef bodyDef = new BodyDef();
bodyDef.position.set(scene_width * 0.5f, 0.2f);
Body body1 = world.createBody(bodyDef);
PolygonShape polygonShape = new PolygonShape();
polygonShape.setAsBox(scene_width * 0.5f, 0.2f);
body1.createFixture(polygonShape, 0.0f);
bodyDef.position.set(0.4f, scene_height * 0.5f);
Body body2 = world.createBody(bodyDef);
polygonShape.setAsBox(0.2f, scene_height * 0.5f);
body2.createFixture(polygonShape, 0);
bodyDef.position.set(12.4f, scene_height * 0.5f);
Body body3 = world.createBody(bodyDef);
polygonShape.setAsBox(0.2f, scene_height * 0.5f);
body3.createFixture(polygonShape, 0);
bodyDef.position.set(scene_width * 0.5f, 7.0f);
Body body4 = world.createBody(bodyDef);
polygonShape.setAsBox(scene_width * 0.5f, 0.2f);
body4.createFixture(polygonShape, 0);
polygonShape.dispose();
return body1;
}
class HandA extends InputAdapter {
MouseJoint mouseJoint;
Vector2 target = new Vector2();
@Override
public boolean touchDragged(int screenX, int screenY, int pointer) {
if (mouseJoint != null) {
camera.unproject(point.set(screenX, screenY, 0));
mouseJoint.setTarget(target.set(point.x, point.y));
}
return false;
}
@Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
camera.unproject(point.set(screenX, screenY, 0));
hitBody = null;
world.QueryAABB(callback, point.x - 0.0001f, point.y - 0.0001f, point.x + 0.0001f, point.y + 0.0001f);
if (hitBody == null || hitBody.equals(groundBody)) return false;
MouseJointDef mouseJointDef = new MouseJointDef();
mouseJointDef.bodyA = groundBody;
mouseJointDef.bodyB = hitBody;
mouseJointDef.collideConnected = true;
mouseJointDef.target.set(point.x, point.y);
mouseJointDef.maxForce = 1000.0f * hitBody.getMass();
mouseJoint = (MouseJoint) world.createJoint(mouseJointDef);
hitBody.setAwake(true);
return false;
}
@Override
public boolean touchUp(int screenX, int screenY, int pointer, int button) {
// 鼠标关节,不再使用时要销毁
if (mouseJoint != null) {
world.destroyJoint(mouseJoint);
mouseJoint = null;
}
return false;
}
}
}
旋转关节会强制两个物体公用一个锚点。旋转关节只有一个自由度:两个物体相对旋转。这称之为关节角。
要指定一个旋转关节,需要提供两个物体以及世界坐标的一个锚点,可以参考下面定义:
// Define the revolute joint
RevoluteJointDef revoluteJointDef = new RevoluteJointDef();
revoluteJointDef.bodyA=smallBall;
revoluteJointDef.bodyB=bigBall;
revoluteJointDef.collideConnected=false;
// 指定锚点
revoluteJointDef.localAnchorA.set(0,0);
revoluteJointDef.localAnchorB.set(-2.0f,0);
在Box2D中默认是逆时针旋转的,此时关节角为正,而且旋转角也是弧度制的。在创建两个物体时物理当前的角度是怎样的,旋转关节角都为0。
每次执行step后,可以更新马达的参数。这样可以实现有些有趣的功能。可以在每个时间步中更新关节速度,使得它像正炫波或者任意一个想要的函数那样前后摆动
/**
* 旋转关节
*/
public class RevoluteJointTest extends ApplicationAdapter {
World world;
Box2DDebugRenderer box2DDebugRenderer;
Body hitBody, groundBody;
OrthographicCamera camera;
Vector3 point = new Vector3();
float scene_width = 12.8f;
float scene_height = 7.2f;
QueryCallback callback = new QueryCallback() {
@Override
public boolean reportFixture(Fixture fixture) {
if (fixture.testPoint(point.x, point.y)) {
hitBody = fixture.getBody();
return false;
} else
return true;
}
};
@Override
public void create() {
world = new World(new Vector2(0.0f, -9.8f), true);
box2DDebugRenderer = new Box2DDebugRenderer();
camera = new OrthographicCamera(scene_width, scene_height);
camera.position.set(scene_width / 2, scene_height / 2, 0);
camera.update();
groundBody = createGroundWall();
Gdx.input.setInputProcessor(new HandA());
createRevoluteJoin();
}
@Override
public void render() {
world.step( 1/ 60f, 6, 2);
Gdx.gl.glClearColor(0.39f, 0.58f, 0.92f, 1.0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
box2DDebugRenderer.render(world, camera.combined);
}
@Override
public void dispose() {
world.dispose();
box2DDebugRenderer.dispose();
}
public void createRevoluteJoin() {
// 第一个Body要设置为Static才能保证第二个Body围绕第一个旋转
Body smallBall = createSphere(BodyDef.BodyType.StaticBody, 0f, 3.75f, 1f, 1f, 0f, .25f);
Body bigBall = createSphere(BodyDef.BodyType.DynamicBody, 0f, 3.75f, 1f, 1f, 0f, .5f);
// Define the revolute joint
RevoluteJointDef revoluteJointDef = new RevoluteJointDef();
revoluteJointDef.bodyA=smallBall;
revoluteJointDef.bodyB=bigBall;
revoluteJointDef.collideConnected=false;
revoluteJointDef.localAnchorA.set(0,0);
revoluteJointDef.localAnchorB.set(-2.0f,0);
revoluteJointDef.enableMotor=true;
revoluteJointDef.maxMotorTorque=360;
revoluteJointDef.motorSpeed=100f* MathUtils.degreesToRadians;
world.createJoint(revoluteJointDef);
}
private Body createSphere(BodyDef.BodyType type, float x, float y, float d, float r, float f, float radius) {
BodyDef bodyDef = new BodyDef();
bodyDef.type = type;
bodyDef.position.set(scene_width * 0.5f+x,y);
bodyDef.angle=0;
Body ball = world.createBody(bodyDef);
FixtureDef fixtureDef=new FixtureDef();
fixtureDef.density=d;
fixtureDef.restitution=r;
fixtureDef.friction=f;
fixtureDef.shape=new CircleShape();
fixtureDef.shape.setRadius(radius);
ball.createFixture(fixtureDef);
fixtureDef.shape.dispose();
return ball;
}
public Body createGroundWall() {
BodyDef bodyDef = new BodyDef();
bodyDef.position.set(scene_width * 0.5f, 0.2f);
Body body1 = world.createBody(bodyDef);
PolygonShape polygonShape = new PolygonShape();
polygonShape.setAsBox(scene_width * 0.5f, 0.2f);
body1.createFixture(polygonShape, 0.0f);
bodyDef.position.set(0.4f, scene_height * 0.5f);
Body body2 = world.createBody(bodyDef);
polygonShape.setAsBox(0.2f, scene_height * 0.5f);
body2.createFixture(polygonShape, 0);
bodyDef.position.set(12.4f, scene_height * 0.5f);
Body body3 = world.createBody(bodyDef);
polygonShape.setAsBox(0.2f, scene_height * 0.5f);
body3.createFixture(polygonShape, 0);
bodyDef.position.set(scene_width * 0.5f, 7.0f);
Body body4 = world.createBody(bodyDef);
polygonShape.setAsBox(scene_width * 0.5f, 0.2f);
body4.createFixture(polygonShape, 0);
polygonShape.dispose();
return body1;
}
class HandA extends InputAdapter {
MouseJoint mouseJoint;
Vector2 target = new Vector2();
@Override
public boolean touchDragged(int screenX, int screenY, int pointer) {
if (mouseJoint != null) {
camera.unproject(point.set(screenX, screenY, 0));
mouseJoint.setTarget(target.set(point.x, point.y));
}
return false;
}
@Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
camera.unproject(point.set(screenX, screenY, 0));
hitBody = null;
world.QueryAABB(callback, point.x - 0.0001f, point.y - 0.0001f, point.x + 0.0001f, point.y + 0.0001f);
if (hitBody == null || hitBody.equals(groundBody)) return false;
MouseJointDef mouseJointDef = new MouseJointDef();
mouseJointDef.bodyA = groundBody;
mouseJointDef.bodyB = hitBody;
mouseJointDef.collideConnected = true;
mouseJointDef.target.set(point.x, point.y);
mouseJointDef.maxForce = 1000.0f * hitBody.getMass();
mouseJoint = (MouseJoint) world.createJoint(mouseJointDef);
hitBody.setAwake(true);
return false;
}
@Override
public boolean touchUp(int screenX, int screenY, int pointer, int button) {
// 鼠标关节,不再使用时要销毁
if (mouseJoint != null) {
world.destroyJoint(mouseJoint);
mouseJoint = null;
}
return false;
}
}
}