Skip to content

Bombshell 02 Dynamics

contriteobserver edited this page Oct 2, 2017 · 5 revisions

JBox2D based Dynamics

(Just dumping information here at the moment, this will be more coherent)

There are many ways to move objects in Rajawali scenes, one is the a built in animation system, another way us to move objects via the simulation of rigid body dynamics. Rajawali does not contain a built-in physics engine. So this tutorial will show how to integrate Rajawali with 2D physics simulation along a single plane in a Rajawali scene, where JBox2D provides 2D physics simulation.

To do this we construct a Rajawali Renderer, a Physics world, and a bridge that links Dynamic and Kinetic bodies in the Physics World with a corresponding Object3D in the renderer.

Before we begin, add the needed components to our build:

    compile 'org.rajawali3d:rajawali:1.1.970'
    compile 'org.jbox2d:jbox2d-library:2.2.1.1'

Use the source Luke

tarball of the example code

A basic renderer

        class SceneRenderer extends Renderer {
            private Bridge bridge;

            public SceneRenderer(Context context) {
                super(context);
                bridge = new Bridge();
            }

            @Override
            protected void initScene() {
                getCurrentScene().setBackgroundColor(Color.BLUE);

                DirectionalLight key = new DirectionalLight(-3,-4,-5);
                key.setPower(1);
                getCurrentScene().addLight(key);

                bridge.InitKinematics(getCurrentScene());
                bridge.InitDynamics(getCurrentScene());

                getCurrentCamera().setPosition(14,10,24);
                getCurrentCamera().setLookAt(7,5,0);
            }

            @Override
            public void onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep, float yOffsetStep, int xPixelOffset, int yPixelOffset) {

            }

            @Override
            public void onTouchEvent(MotionEvent event) {
                resetRequested = true;
            }

            @Override
            protected void onRender(long ellapsedRealtime, double deltaTime) {
                super.onRender(ellapsedRealtime, deltaTime);
                bridge.Update();

                if(resetRequested) {
                    bridge.ClearDynamics(getCurrentScene());
                    bridge.InitDynamics(getCurrentScene());
                    resetRequested = false;
                } else {
                    bridge.SyncDynamics(getCurrentScene());
                    bridge.PruneDynamics(getCurrentScene());
                }
            }

        }

A simple physics world

class Dynamics2D {

    private World b2World;
    static private final int VELOCITY_ITERATIONS = 8;
    static private final int POSITION_ITERATIONS = 3;

    private enum ObjectType
    {
        Box,
        Floor,
        Ball
    }

    Dynamics2D()
    {
        b2World = new World(new Vec2(0.0f,-9.81f));
    }

    void Step(float delta)
    {
        b2World.step(delta, VELOCITY_ITERATIONS, POSITION_ITERATIONS);
    }

    void destroyBody(Body body) {
        b2World.destroyBody(body);
    }

    Body CreateBox(Vec2 position, @Nullable Vec2 velocity)
    {
        Vec2 v = velocity == null ? new Vec2() : velocity;

        BodyDef bodyDef = new BodyDef();

        bodyDef.position = position;
        bodyDef.angle = (float)Math.PI/4;
        bodyDef.linearVelocity = v;
        bodyDef.angularVelocity = 0.0f;
        bodyDef.fixedRotation = false;
        bodyDef.active = true;
        bodyDef.bullet = false;
        bodyDef.allowSleep = true;
        bodyDef.gravityScale = 1.0f;
        bodyDef.linearDamping = 0.0f;
        bodyDef.angularDamping = 0.0f;
        bodyDef.userData = ObjectType.Box;
        bodyDef.type = BodyType.DYNAMIC;

        PolygonShape shape = new PolygonShape();
        shape.setAsBox(1, 1);

        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = shape;
        fixtureDef.userData = null;
        fixtureDef.friction = 0.35f;
        fixtureDef.restitution = 0.05f;
        fixtureDef.density = 1f;
        fixtureDef.isSensor = false;

        Body body = b2World.createBody(bodyDef);
        body.createFixture(fixtureDef);
        return body;
    }

    Body CreateBall(Vec2 position, @Nullable Vec2 velocity)
    {
        Vec2 v = velocity == null ? new Vec2() : velocity;

        BodyDef bodyDef = new BodyDef();

        bodyDef.position = position;
        bodyDef.angle = 0f;
        bodyDef.linearVelocity = v;
        bodyDef.angularVelocity = 0.0f;
        bodyDef.fixedRotation = false;
        bodyDef.active = true;
        bodyDef.bullet = false;
        bodyDef.allowSleep = true;
        bodyDef.gravityScale = 1.0f;
        bodyDef.linearDamping = 0.0f;
        bodyDef.angularDamping = 0.0f;
        bodyDef.userData = ObjectType.Ball;
        bodyDef.type = BodyType.DYNAMIC;

        CircleShape shape = new CircleShape();
        shape.setRadius(1);

        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = shape;
        fixtureDef.userData = null;
        fixtureDef.friction = 0.45f;
        fixtureDef.restitution = 0.75f;
        fixtureDef.density = 2.0f;
        fixtureDef.isSensor = false;

        Body body = b2World.createBody(bodyDef);
        body.createFixture(fixtureDef);
        return body;
    }

    Body CreateBarrier(Vec2 position, float width, float height)
    {
        BodyDef bodyDef = new BodyDef();

        bodyDef.position = position;
        bodyDef.angle = 0f;
        bodyDef.linearVelocity = new Vec2(0.0f,0.0f);
        bodyDef.angularVelocity = 0.0f;
        bodyDef.fixedRotation = false;
        bodyDef.active = true;
        bodyDef.bullet = false;
        bodyDef.allowSleep = true;
        bodyDef.gravityScale = 1.0f;
        bodyDef.linearDamping = 0.0f;
        bodyDef.angularDamping = 0.0f;
        bodyDef.userData = ObjectType.Floor;
        bodyDef.type = BodyType.KINEMATIC;

        PolygonShape shape = new PolygonShape();
        shape.setAsBox(width/2f, height/2f);

        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = shape;
        fixtureDef.userData = null;
        fixtureDef.friction = 0.5f;
        fixtureDef.restitution = 0.05f;
        fixtureDef.density = 1.0f;
        fixtureDef.isSensor = false;

        Body body = b2World.createBody(bodyDef);
        body.createFixture(fixtureDef);
        return body;
    }
}

A bridge between the renderer and the physics world

class Bridge {
    private HashMap<Body, Object3D> map;
    private Dynamics2D dynamics2D;
    private IDiffuseMethod lambert;
    private long mLastRender;

    Bridge() {
        dynamics2D = new Dynamics2D();
        lambert = new DiffuseMethod.Lambert();
        map = new HashMap<>();
    }

    void Update() {
        final long currentTime = System.nanoTime();
        final float delta = (currentTime - mLastRender) / 1e9f;
        mLastRender = currentTime;
        dynamics2D.Step(delta);
    }

    void InitKinematics(Scene scene) {
        Body floor = dynamics2D.CreateBarrier(new Vec2(7, 0), 10, 2);
        Material gray = new Material();
        gray.setColor(Color.LTGRAY);
        gray.setAmbientColor(Color.GRAY);
        gray.setDiffuseMethod(lambert);
        gray.enableLighting(true);
        Object3D floorPrism = new RectangularPrism(10,2,3);
        floorPrism.setPosition(floor.getPosition().x, floor.getPosition().y, 0);
        floorPrism.setMaterial(gray);
        //floorPrism.setDrawingMode(GLES20.GL_LINES);
        scene.addChild(floorPrism);
    }

    void InitDynamics(Scene scene) {
        Body ball = dynamics2D.CreateBall(new Vec2(9.1f,15), null);
        Material cyan = new Material();
        cyan.setColor(Color.CYAN);
        cyan.setAmbientColor(Color.GRAY);
        cyan.setDiffuseMethod(lambert);
        cyan.enableLighting(true);
        Object3D sphere = new Sphere(1,12,12);
        sphere.setPosition(ball.getPosition().x, ball.getPosition().y, 0);
        sphere.setRotation(Vector3.NEG_Z, toDegrees(ball.getAngle()));
        sphere.setMaterial(cyan);
        //sphere.setDrawingMode(GLES20.GL_LINES);
        scene.addChild(sphere);
        map.put(ball, sphere);

        Body box = dynamics2D.CreateBox(new Vec2(9.3f,12), null);
        Material yellow = new Material();
        yellow.setColor(Color.YELLOW);
        yellow.setAmbientColor(Color.GRAY);
        yellow.setDiffuseMethod(lambert);
        yellow.enableLighting(true);
        Object3D cube = new Cube(2);
        cube.setPosition(box.getPosition().x, box.getPosition().y, 0);
        cube.setRotation(Vector3.NEG_Z, toDegrees(box.getAngle()));
        cube.setMaterial(yellow);
        //cube.setDrawingMode(GLES20.GL_LINES);
        scene.addChild(cube);
        map.put(box, cube);
    }

    void ClearDynamics(Scene scene) {
        for(Iterator<Map.Entry<Body, Object3D>> it = map.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<Body, Object3D> entry = it.next();
            physics2D.destroyBody(entry.getKey());
            scene.removeChild(entry.getValue());
            it.remove();
        }
    }

    void SyncDynamics(Scene scene) {
        for (Map.Entry<Body, Object3D> entry: map.entrySet()) {
            Body body = entry.getKey();
            Object3D object3D = entry.getValue();
            object3D.setPosition(body.getPosition().x, body.getPosition().y, 0);
            object3D.setRotation(Vector3.NEG_Z, toDegrees(body.getAngle()));
        }
    }

    void PruneDynamics(Scene scene) {
        for(Iterator<Map.Entry<Body, Object3D>> it = map.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<Body, Object3D> entry = it.next();
            if(entry.getKey().getPosition().y < -16) {
                physics2D.destroyBody(entry.getKey());
                scene.removeChild(entry.getValue());
                it.remove();
            }
        }
    }
}

A rudimentary user interation

        public boolean onTouchEvent(MotionEvent event) {
            if(event.getAction() == MotionEvent.ACTION_UP) {
                renderer.onTouchEvent(event);
            }
            return super.onTouchEvent(event);
        }
Clone this wiki locally