1. 程式人生 > >7.Libgdx擴充套件學習之Box2D_距離關節 旋轉關節

7.Libgdx擴充套件學習之Box2D_距離關節 旋轉關節

文章中的概念來自《Box2D》中文手冊

上一節中我們介紹了關節和滑鼠關節的使用方法,本章中我們繼續介紹距離關節和旋轉關節

距離關節(DistanceJoint)

距離關節是兩個物體上各有一點,兩點之間的距離必須固定不變。當指定一個距離關節時,兩個物體必須已在應有的位置上。之後指定世界座標中的兩個錨點。第一個錨點連線到物體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; } } }

旋轉關節(RevoluteJoint)

旋轉關節會強制兩個物體公用一個錨點。旋轉關節只有一個自由度:兩個物體相對旋轉。這稱之為關節角。
要指定一個旋轉關節,需要提供兩個物體以及世界座標的一個錨點,可以參考下面定義:
這裡寫圖片描述

// 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;
        }
    }
}