/*
* 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 android.util.Log;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
import com.badlogic.gdx.physics.box2d.Shape;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.utils.GdxNativesLoader;
import org.catrobat.catroid.common.ScreenValues;
import org.catrobat.catroid.content.Look;
import org.catrobat.catroid.content.Sprite;
import org.catrobat.catroid.physics.shapebuilder.PhysicsShapeBuilder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class PhysicsWorld {
static {
GdxNativesLoader.load();
}
private static final String TAG = PhysicsWorld.class.getSimpleName();
// CATEGORY
public static final short CATEGORY_NO_COLLISION = 0x0000;
public static final short CATEGORY_BOUNDARYBOX = 0x0002;
public static final short CATEGORY_PHYSICSOBJECT = 0x0004;
// COLLISION_MODE
public static final short MASK_BOUNDARYBOX = CATEGORY_PHYSICSOBJECT; // collides with physics_objects
public static final short MASK_PHYSICSOBJECT = ~CATEGORY_BOUNDARYBOX; // collides with everything but not with the boundarybox
public static final short MASK_TO_BOUNCE = -1; // collides with everything
public static final short MASK_NO_COLLISION = 0; // collides with NOBODY
public static final float ACTIVE_AREA_WIDTH_FACTOR = 3.0f;
public static final float ACTIVE_AREA_HEIGHT_FACTOR = 2.0f;
public static final float RATIO = 10.0f;
public static final int VELOCITY_ITERATIONS = 3;
public static final int POSITION_ITERATIONS = 3;
public static final Vector2 DEFAULT_GRAVITY = new Vector2(0.0f, -10.0f);
public static final boolean IGNORE_SLEEPING_OBJECTS = false;
public static Vector2 activeArea;
public static final int STABILIZING_STEPS = 6;
private final World world = new World(PhysicsWorld.DEFAULT_GRAVITY, PhysicsWorld.IGNORE_SLEEPING_OBJECTS);
private final Map<Sprite, PhysicsObject> physicsObjects = new HashMap<Sprite, PhysicsObject>();
private final ArrayList<Sprite> activeVerticalBounces = new ArrayList<Sprite>();
private final ArrayList<Sprite> activeHorizontalBounces = new ArrayList<Sprite>();
private Box2DDebugRenderer renderer;
private int stabilizingSteCounter = 0;
private PhysicsBoundaryBox boundaryBox;
private PhysicsShapeBuilder physicsShapeBuilder = PhysicsShapeBuilder.getInstance();
public PhysicsWorld() {
this(ScreenValues.SCREEN_WIDTH, ScreenValues.SCREEN_HEIGHT);
}
public PhysicsWorld(int width, int height) {
boundaryBox = new PhysicsBoundaryBox(world);
boundaryBox.create(width, height);
activeArea = new Vector2(width * ACTIVE_AREA_WIDTH_FACTOR, height * ACTIVE_AREA_HEIGHT_FACTOR);
world.setContactListener(new PhysicsCollision(this));
}
public void setBounceOnce(Sprite sprite, PhysicsBoundaryBox.BoundaryBoxIdentifier boundaryBoxIdentifier) {
if (physicsObjects.containsKey(sprite)) {
PhysicsObject physicsObject = physicsObjects.get(sprite);
physicsObject.setIfOnEdgeBounce(true, sprite);
switch (boundaryBoxIdentifier) {
case BBI_HORIZONTAL:
activeHorizontalBounces.add(sprite);
break;
case BBI_VERTICAL:
activeVerticalBounces.add(sprite);
break;
}
}
}
public void step(float deltaTime) {
if (stabilizingSteCounter < STABILIZING_STEPS) {
stabilizingSteCounter++;
} else {
try {
world.step(deltaTime, PhysicsWorld.VELOCITY_ITERATIONS, PhysicsWorld.POSITION_ITERATIONS);
} catch (Exception exception) {
Log.e(TAG, Log.getStackTraceString(exception));
}
}
}
public void render(Matrix4 perspectiveMatrix) {
if (renderer == null) {
renderer = new Box2DDebugRenderer(PhysicsDebugSettings.Render.RENDER_BODIES,
PhysicsDebugSettings.Render.RENDER_JOINTS, PhysicsDebugSettings.Render.RENDER_AABB,
PhysicsDebugSettings.Render.RENDER_INACTIVE_BODIES, PhysicsDebugSettings.Render.RENDER_VELOCITIES,
PhysicsDebugSettings.Render.RENDER_CONTACTS);
}
renderer.render(world, perspectiveMatrix.scl(PhysicsWorld.RATIO));
}
public void setGravity(float x, float y) {
world.setGravity(new Vector2(x, y));
}
public Vector2 getGravity() {
return world.getGravity();
}
public void changeLook(PhysicsObject physicsObject, Look look) {
Shape[] shapes = null;
if (look.getLookData() != null && look.getLookData().getLookFileName() != null) {
shapes = physicsShapeBuilder.getScaledShapes(look.getLookData(),
look.getSizeInUserInterfaceDimensionUnit() / 100f);
}
physicsObject.setShape(shapes);
}
public PhysicsObject getPhysicsObject(Sprite sprite) {
if (sprite == null) {
throw new NullPointerException();
}
if (physicsObjects.containsKey(sprite)) {
return physicsObjects.get(sprite);
}
PhysicsObject physicsObject = createPhysicsObject(sprite);
physicsObjects.put(sprite, physicsObject);
return physicsObject;
}
private PhysicsObject createPhysicsObject(Sprite sprite) {
BodyDef bodyDef = new BodyDef();
return new PhysicsObject(world.createBody(bodyDef), sprite);
}
public void bouncedOnEdge(Sprite sprite, PhysicsBoundaryBox.BoundaryBoxIdentifier boundaryBoxIdentifier) {
if (physicsObjects.containsKey(sprite)) {
PhysicsObject physicsObject = physicsObjects.get(sprite);
switch (boundaryBoxIdentifier) {
case BBI_HORIZONTAL:
if (activeHorizontalBounces.remove(sprite) && !activeVerticalBounces.contains(sprite)) {
physicsObject.setIfOnEdgeBounce(false, sprite);
PhysicsCollisionBroadcast.fireEvent(PhysicsCollision.generateBroadcastMessage(sprite.getName(),
PhysicsCollision.COLLISION_WITH_ANYTHING_IDENTIFIER));
}
break;
case BBI_VERTICAL:
if (activeVerticalBounces.remove(sprite) && !activeHorizontalBounces.contains(sprite)) {
physicsObject.setIfOnEdgeBounce(false, sprite);
PhysicsCollisionBroadcast.fireEvent(PhysicsCollision.generateBroadcastMessage(sprite.getName(),
PhysicsCollision.COLLISION_WITH_ANYTHING_IDENTIFIER));
}
break;
}
}
}
}