org.catrobat.catroid.physics.PhysicsObject.java Source code

Java tutorial

Introduction

Here is the source code for org.catrobat.catroid.physics.PhysicsObject.java

Source

/*
 * Catroid: An on-device visual programming system for Android devices
 * Copyright (C) 2010-2016 The Catrobat Team
 * (<http://developer.catrobat.org/credits>)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * An additional term exception under section 7 of the GNU Affero
 * General Public License, version 3, is available at
 * http://developer.catrobat.org/license_additional_term
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.catrobat.catroid.physics;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.CircleShape;
import com.badlogic.gdx.physics.box2d.Filter;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.Shape;
import com.badlogic.gdx.physics.box2d.Transform;
import com.badlogic.gdx.utils.Array;

import org.catrobat.catroid.content.Sprite;

import java.util.Arrays;

public class PhysicsObject {

    public enum Type {
        DYNAMIC, FIXED, NONE
    }

    public static final float DEFAULT_DENSITY = 1.0f;
    public static final float DEFAULT_FRICTION = 0.2f;
    public static final float MAX_FRICTION = 1.0f;
    public static final float MIN_FRICTION = 0.0f;
    public static final float MIN_DENSITY = 0.0f;
    public static final float MIN_BOUNCE_FACTOR = 0.0f;
    public static final float DEFAULT_BOUNCE_FACTOR = 0.8f;
    public static final float DEFAULT_MASS = 1.0f;
    public static final float MIN_MASS = 0.000001f;

    private short collisionMaskRecord = 0;
    private short categoryMaskRecord = PhysicsWorld.CATEGORY_PHYSICSOBJECT;

    private final Body body;
    private final FixtureDef fixtureDef = new FixtureDef();
    private Shape[] shapes;
    private Type type;
    private float mass;
    private float circumference;
    private boolean ifOnEdgeBounce = false;

    private Vector2 bodyAabbLowerLeft;
    private Vector2 bodyAabbUpperRight;
    private Vector2 fixtureAabbLowerLeft;
    private Vector2 fixtureAabbUpperRight;
    private Vector2 tmpVertice;

    private Vector2 velocity = new Vector2();
    private float rotationSpeed = 0;
    private float gravityScale = 0;
    private Type savedType = Type.NONE;

    public PhysicsObject(Body b, Sprite sprite) {
        body = b;
        body.setUserData(sprite);
        mass = PhysicsObject.DEFAULT_MASS;
        fixtureDef.density = PhysicsObject.DEFAULT_DENSITY;
        fixtureDef.friction = PhysicsObject.DEFAULT_FRICTION;
        fixtureDef.restitution = PhysicsObject.DEFAULT_BOUNCE_FACTOR;
        setType(Type.NONE);

        tmpVertice = new Vector2();
    }

    public void setShape(Shape[] shapes) {
        if (Arrays.equals(this.shapes, shapes)) {
            return;
        }

        if (shapes != null) {
            this.shapes = Arrays.copyOf(shapes, shapes.length);
        } else {
            this.shapes = null;
        }

        while (body.getFixtureList().size > 0) {
            Fixture oldFixture = body.getFixtureList().first();
            body.destroyFixture(oldFixture);
        }

        if (shapes != null) {
            for (Shape tempShape : shapes) {
                fixtureDef.shape = tempShape;
                body.createFixture(fixtureDef);
            }
        }

        setMass(mass);
        calculateCircumference();
    }

    private void calculateCircumference() {
        if (body.getFixtureList().size == 0) {
            //Log.d(TAG, "No fixtures, so reset circumference to zero");
            circumference = 0;
            return;
        }
        circumference = PhysicsWorldConverter
                .convertNormalToBox2dCoordinate(getBoundaryBoxDimensions().len() / 2.0f);
    }

    public Type getType() {
        return type;
    }

    public void setType(Type type) {
        if (this.type == type) {
            return;
        }
        this.type = type;

        switch (type) {
        case DYNAMIC:
            body.setType(BodyType.DynamicBody);
            body.setGravityScale(1.0f);
            body.setBullet(true);
            setMass(mass);
            collisionMaskRecord = PhysicsWorld.MASK_PHYSICSOBJECT;
            break;
        case FIXED:
            body.setType(BodyType.KinematicBody);
            collisionMaskRecord = PhysicsWorld.MASK_PHYSICSOBJECT;
            break;
        case NONE:
            body.setType(BodyType.KinematicBody);
            collisionMaskRecord = PhysicsWorld.MASK_NO_COLLISION;
            break;
        }
        calculateCircumference();
        setCollisionBits(categoryMaskRecord, collisionMaskRecord);
    }

    public float getDirection() {
        return PhysicsWorldConverter.convertBox2dToNormalAngle(body.getAngle());
    }

    public void setDirection(float degrees) {
        body.setTransform(body.getPosition(), PhysicsWorldConverter.convertNormalToBox2dAngle(degrees));
    }

    public float getX() {
        return PhysicsWorldConverter.convertBox2dToNormalCoordinate(body.getPosition().x);
    }

    public float getY() {
        return PhysicsWorldConverter.convertBox2dToNormalCoordinate(body.getPosition().y);
    }

    public Vector2 getMassCenter() {
        return body.getWorldCenter();
    }

    public float getCircumference() {
        return PhysicsWorldConverter.convertBox2dToNormalCoordinate(circumference);
    }

    public Vector2 getPosition() {
        return PhysicsWorldConverter.convertBox2dToNormalVector(body.getPosition());
    }

    public void setX(float x) {
        body.setTransform(PhysicsWorldConverter.convertNormalToBox2dCoordinate(x), body.getPosition().y,
                body.getAngle());
    }

    public void setY(float y) {
        body.setTransform(body.getPosition().x, PhysicsWorldConverter.convertNormalToBox2dCoordinate(y),
                body.getAngle());
    }

    public void setPosition(float x, float y) {
        x = PhysicsWorldConverter.convertNormalToBox2dCoordinate(x);
        y = PhysicsWorldConverter.convertNormalToBox2dCoordinate(y);
        body.setTransform(x, y, body.getAngle());
    }

    public void setPosition(Vector2 position) {
        setPosition(position.x, position.y);
    }

    public float getRotationSpeed() {
        return (float) Math.toDegrees(body.getAngularVelocity());
    }

    public void setRotationSpeed(float degreesPerSecond) {
        body.setAngularVelocity((float) Math.toRadians(degreesPerSecond));
    }

    public Vector2 getVelocity() {
        return PhysicsWorldConverter.convertBox2dToNormalVector(body.getLinearVelocity());
    }

    public void setVelocity(float x, float y) {
        body.setLinearVelocity(PhysicsWorldConverter.convertNormalToBox2dCoordinate(x),
                PhysicsWorldConverter.convertNormalToBox2dCoordinate(y));
    }

    public float getMass() {
        return this.mass;
    }

    public float getBounceFactor() {
        return this.fixtureDef.restitution;
    }

    public void setMass(float mass) {
        this.mass = mass;

        if (mass < 0) {
            this.mass = PhysicsObject.MIN_MASS;
        }
        if (mass < PhysicsObject.MIN_MASS) {
            mass = PhysicsObject.MIN_MASS;
        }
        if (isStaticObject()) {
            return;
        }
        float area = body.getMass() / fixtureDef.density;
        float density = mass / area;
        setDensity(density);
    }

    private boolean isStaticObject() {
        return body.getMass() == 0.0f;
    }

    private void setDensity(float density) {
        if (density < MIN_DENSITY) {
            density = PhysicsObject.MIN_DENSITY;
        }
        fixtureDef.density = density;
        for (Fixture fixture : body.getFixtureList()) {
            fixture.setDensity(density);
        }
        body.resetMassData();
    }

    public float getFriction() {
        return fixtureDef.friction;
    }

    public void setFriction(float friction) {
        if (friction < MIN_FRICTION) {
            friction = MIN_FRICTION;
        }
        if (friction > MAX_FRICTION) {
            friction = MAX_FRICTION;
        }

        fixtureDef.friction = friction;
        for (Fixture fixture : body.getFixtureList()) {
            fixture.setFriction(friction);
        }
    }

    public void setBounceFactor(float bounceFactor) {
        if (bounceFactor < MIN_BOUNCE_FACTOR) {
            bounceFactor = MIN_BOUNCE_FACTOR;
        }
        fixtureDef.restitution = bounceFactor;
        for (Fixture fixture : body.getFixtureList()) {
            fixture.setRestitution(bounceFactor);
        }
    }

    public void setGravityScale(float scale) {
        body.setGravityScale(scale);
    }

    public float getGravityScale() {
        return body.getGravityScale();
    }

    public void setIfOnEdgeBounce(boolean bounce, Sprite sprite) {
        if (ifOnEdgeBounce == bounce) {
            return;
        }
        ifOnEdgeBounce = bounce;

        short maskBits;
        if (bounce) {
            maskBits = PhysicsWorld.MASK_TO_BOUNCE;
            body.setUserData(sprite);
        } else {
            maskBits = PhysicsWorld.MASK_PHYSICSOBJECT;
        }

        setCollisionBits(categoryMaskRecord, maskBits);
    }

    protected void setCollisionBits(short categoryBits, short maskBits) {
        setCollisionBits(categoryBits, maskBits, true);
    }

    protected void setCollisionBits(short categoryBits, short maskBits, boolean updateState) {
        fixtureDef.filter.categoryBits = categoryBits;
        fixtureDef.filter.maskBits = maskBits;

        for (Fixture fixture : body.getFixtureList()) {
            Filter filter = fixture.getFilterData();
            filter.categoryBits = categoryBits;
            filter.maskBits = maskBits;
            fixture.setFilterData(filter);
        }

        if (updateState) {
            updateNonCollidingState();
        }
    }

    private void updateNonCollidingState() {
        if (body.getUserData() != null && body.getUserData() instanceof Sprite) {
            Object look = ((Sprite) body.getUserData()).look;
            if (look != null && look instanceof PhysicsLook) {
                ((PhysicsLook) look).setNonColliding(isNonColliding());
            }
        }
    }

    public void getBoundaryBox(Vector2 lowerLeft, Vector2 upperRight) {
        calculateAabb();
        lowerLeft.x = PhysicsWorldConverter.convertBox2dToNormalVector(bodyAabbLowerLeft).x;
        lowerLeft.y = PhysicsWorldConverter.convertBox2dToNormalVector(bodyAabbLowerLeft).y;
        upperRight.x = PhysicsWorldConverter.convertBox2dToNormalVector(bodyAabbUpperRight).x;
        upperRight.y = PhysicsWorldConverter.convertBox2dToNormalVector(bodyAabbUpperRight).y;
    }

    public Vector2 getBoundaryBoxDimensions() {
        calculateAabb();
        float aabbWidth = PhysicsWorldConverter
                .convertBox2dToNormalCoordinate(Math.abs(bodyAabbUpperRight.x - bodyAabbLowerLeft.x)) + 1.0f;
        float aabbHeight = PhysicsWorldConverter
                .convertBox2dToNormalCoordinate(Math.abs(bodyAabbUpperRight.y - bodyAabbLowerLeft.y)) + 1.0f;
        return new Vector2(aabbWidth, aabbHeight);
    }

    public void activateHangup() {
        velocity = new Vector2(getVelocity());
        rotationSpeed = getRotationSpeed();
        gravityScale = getGravityScale();

        setGravityScale(0);
        setVelocity(0, 0);
        setRotationSpeed(0);
    }

    public void deactivateHangup(boolean record) {
        if (record) {
            setGravityScale(gravityScale);
            setVelocity(velocity.x, velocity.y);
            setRotationSpeed(rotationSpeed);
        } else {
            setGravityScale(1);
        }
    }

    public void activateNonColliding(boolean updateState) {
        setCollisionBits(categoryMaskRecord, PhysicsWorld.MASK_NO_COLLISION, updateState);
    }

    public void deactivateNonColliding(boolean record, boolean updateState) {
        if (record) {
            setCollisionBits(categoryMaskRecord, collisionMaskRecord, updateState);
        }
    }

    public void activateFixed() {
        savedType = getType();
        setType(Type.FIXED);
    }

    public void deactivateFixed(boolean record) {
        if (record) {
            setType(savedType);
        }
    }

    public boolean isNonColliding() {
        return collisionMaskRecord == PhysicsWorld.MASK_NO_COLLISION;
    }

    private void calculateAabb() {
        bodyAabbLowerLeft = new Vector2(Integer.MAX_VALUE, Integer.MAX_VALUE);
        bodyAabbUpperRight = new Vector2(Integer.MIN_VALUE, Integer.MIN_VALUE);
        Transform transform = body.getTransform();
        int len = body.getFixtureList().size;
        Array<Fixture> fixtures = body.getFixtureList();
        if (fixtures.size == 0) {
            bodyAabbLowerLeft.x = 0;
            bodyAabbLowerLeft.y = 0;
            bodyAabbUpperRight.x = 0;
            bodyAabbUpperRight.y = 0;
        }
        for (int i = 0; i < len; i++) {
            Fixture fixture = fixtures.get(i);
            calculateAabb(fixture, transform);
        }
    }

    private void calculateAabb(Fixture fixture, Transform transform) {
        fixtureAabbLowerLeft = new Vector2(Integer.MAX_VALUE, Integer.MAX_VALUE);
        fixtureAabbUpperRight = new Vector2(Integer.MIN_VALUE, Integer.MIN_VALUE);
        if (fixture.getType() == Shape.Type.Circle) {
            CircleShape shape = (CircleShape) fixture.getShape();
            float radius = shape.getRadius();
            tmpVertice.set(shape.getPosition());
            tmpVertice.rotate(transform.getRotation()).add(transform.getPosition());
            fixtureAabbLowerLeft.set(tmpVertice.x - radius, tmpVertice.y - radius);
            fixtureAabbUpperRight.set(tmpVertice.x + radius, tmpVertice.y + radius);
        } else if (fixture.getType() == Shape.Type.Polygon) {
            PolygonShape shape = (PolygonShape) fixture.getShape();
            int vertexCount = shape.getVertexCount();

            shape.getVertex(0, tmpVertice);
            fixtureAabbLowerLeft.set(transform.mul(tmpVertice));
            fixtureAabbUpperRight.set(fixtureAabbLowerLeft);
            for (int i = 1; i < vertexCount; i++) {
                shape.getVertex(i, tmpVertice);
                transform.mul(tmpVertice);
                fixtureAabbLowerLeft.x = Math.min(fixtureAabbLowerLeft.x, tmpVertice.x);
                fixtureAabbLowerLeft.y = Math.min(fixtureAabbLowerLeft.y, tmpVertice.y);
                fixtureAabbUpperRight.x = Math.max(fixtureAabbUpperRight.x, tmpVertice.x);
                fixtureAabbUpperRight.y = Math.max(fixtureAabbUpperRight.y, tmpVertice.y);
            }
        }

        bodyAabbLowerLeft.x = Math.min(fixtureAabbLowerLeft.x, bodyAabbLowerLeft.x);
        bodyAabbLowerLeft.y = Math.min(fixtureAabbLowerLeft.y, bodyAabbLowerLeft.y);
        bodyAabbUpperRight.x = Math.max(fixtureAabbUpperRight.x, bodyAabbUpperRight.x);
        bodyAabbUpperRight.y = Math.max(fixtureAabbUpperRight.y, bodyAabbUpperRight.y);
    }
}