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