package com.cookbook.samples;
import java.util.Random;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
import com.badlogic.gdx.physics.box2d.ChainShape;
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.RayCastCallback;
import com.badlogic.gdx.physics.box2d.Transform;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.TimeUtils;
import com.badlogic.gdx.utils.viewport.FitViewport;
import com.badlogic.gdx.utils.viewport.Viewport;
import com.cookbook.box2d.RayCastManager;
public class Box2DFixedTimeStepSample extends GdxSample {
private static final String TAG = "Box2DFixedTimeStepSample";
static final float PIXELS_TO_METRES = 0.01f;
static final float METRES_TO_PIXELS = PIXELS_TO_METRES * 10000f;
private static final float SCENE_WIDTH = 12.80f; // 12.8 metres wide
private static final float SCENE_HEIGHT = 7.20f; // 7.2 metres high
private Viewport viewport;
private SpriteBatch batch;
//General Box2D
Box2DDebugRenderer debugRenderer;
World world;
Body groundBody;
// Generated boxes
Array<Box> activeBoxes;
Texture boxTexture;
// last second
double lastTime;
//Time-step stuff
private double accumulator;
private double currentTime;
private float step = 1f/60f;
TimeStep timeStepType;
// Current time-step
int lastFPS = 0, frameCount = 0;
BitmapFont font, font2;
float titleWidth;
Vector2 pos = new Vector2();
Camera gameCamera, stageCamera;
Stage stage;
TextButton btnFixed, btnFixedInt, btnVariable;
public enum TimeStep { FIXED, FIXED_INTERPOLATION, VARIABLE }
private class Box {
private final Body boxBody;
private float BOX_WIDTH;
private float BOX_HEIGHT;
public float x, y;
public float angle;
private Texture boxTex;
public Box(float x, float y, float box_width, float box_height, Texture texture) {
BOX_WIDTH = box_width;
BOX_HEIGHT = box_height;
boxTex = texture;
boxBody = createPolygon(BodyType.DynamicBody, x, y, 1f, 0.2f, 0.8f, box_width * 0.5f,
box_height * 0.5f);
this.x = x - (BOX_WIDTH*.5f);
this.y = y - (BOX_HEIGHT*.5f);
this.angle = boxBody.getAngle();
}
public Body getBody() {
return boxBody;
}
public void draw(SpriteBatch batch) {
batch.draw(
boxTex,
x - (BOX_WIDTH*.5f), y - (BOX_HEIGHT*.5f),
BOX_WIDTH*.5f, BOX_HEIGHT*.5f,
BOX_WIDTH, BOX_HEIGHT,
1f, 1f,
angle * MathUtils.radDeg,
0, 0,
boxTex.getWidth(), boxTex.getHeight(),
false, false);
}
}
@Override
public void create () {
super.create();
gameCamera = new OrthographicCamera();
stageCamera = new OrthographicCamera();
viewport = new FitViewport(SCENE_WIDTH, SCENE_HEIGHT, gameCamera);
batch = new SpriteBatch();
Viewport v2 = new FitViewport(SCENE_WIDTH * METRES_TO_PIXELS, SCENE_HEIGHT * METRES_TO_PIXELS, stageCamera);
stage = new Stage(v2, batch);
// Center camera to get (0,0) as the origin of the Box2D world
viewport.getCamera().position.set(viewport.getCamera().position.x + SCENE_WIDTH*0.5f,
viewport.getCamera().position.y + SCENE_HEIGHT*0.5f
, 0);
viewport.getCamera().update();
// Stage will listen for input events (button click in our sample)
Gdx.input.setInputProcessor(stage);
// Create Physics World
world = new World(new Vector2(0,-10), true);
font = new BitmapFont(Gdx.files.internal("data/verdana39.fnt"), false);
font2 = new BitmapFont(Gdx.files.internal("data/default.fnt"), false);
// Time-step variables
accumulator = 0.0;
currentTime = TimeUtils.millis() / 1000.0;
lastTime = currentTime;
timeStepType = TimeStep.VARIABLE;
// Track active boxes
activeBoxes = new Array<Box>();
// Retrieve the common box texture
boxTexture = new Texture(Gdx.files.internal("data/box2D/box.png"));
// Creates a ground to avoid objects falling forever
createGround();
// Generate the buttons to switch between time-step types
TextButton.TextButtonStyle tbs = new TextButton.TextButtonStyle();
tbs.font = font2;
TextureRegion buttonImg = new TextureRegion(new Texture(Gdx.files.internal("data/scene2d/myactor.png")));
tbs.up = new TextureRegionDrawable(buttonImg);
float width = buttonImg.getRegionWidth();
float height = buttonImg.getRegionHeight();
pos = new Vector2(300f, 50f);
btnVariable = new TextButton("Variable", tbs);
btnVariable.setPosition(pos.x, pos.y);
btnVariable.addListener( new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
if(timeStepType != TimeStep.VARIABLE) {
timeStepType = TimeStep.VARIABLE;
accumulator = 0;
}
};
});
pos = new Vector2(500f, 50f);
btnFixed = new TextButton("Fixed", tbs);
btnFixed.setPosition(500, 50);
btnFixed.addListener( new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
if(timeStepType != TimeStep.FIXED) {
timeStepType = TimeStep.FIXED;
}
};
});
pos = new Vector2(700f, 50f);
btnFixedInt = new TextButton("Fixed-Interpolation", tbs);
btnFixedInt.setPosition(700, 50);
btnFixedInt.addListener( new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
if(timeStepType != TimeStep.FIXED_INTERPOLATION) {
timeStepType = TimeStep.FIXED_INTERPOLATION;
}
};
});
// Place buttons
Table table = new Table();
stage.addActor(table);
table.setPosition(600, 85);
table.debug();
table.add(btnFixedInt).width(width).height(height).padRight(25f);
table.add(btnVariable).width(width).height(height).padRight(25f);
table.add(btnFixed).width(width).height(height);
// Fill scene with boxes
for(int i=0; i<10; i++)
activeBoxes.add(generateRandomBox(0.5f, 0.5f));
// Instantiate the class in charge of drawing physics shapes
debugRenderer = new Box2DDebugRenderer();
}
private void createGround() {
ChainShape chainShape = new ChainShape();
chainShape.createLoop(new Vector2[] {
new Vector2(.0f, .0f),
new Vector2(SCENE_WIDTH, .0f),
new Vector2(SCENE_WIDTH, 7f),
new Vector2(SCENE_WIDTH-1f, 7f),
new Vector2(SCENE_WIDTH-1f, 1.5f),
new Vector2(1f, 1.5f),
new Vector2(1f, 7f),
new Vector2(0f, 7f),});
BodyDef chainBodyDef = new BodyDef();
chainBodyDef.type = BodyType.StaticBody;
chainBodyDef.position.set(.0f, .0f);
groundBody = world.createBody(chainBodyDef);
FixtureDef groundFD = new FixtureDef();
groundFD.shape = chainShape;
groundFD.density = 0;
groundBody.createFixture(groundFD);
groundBody.setUserData("Ground Body");
chainShape.dispose();
}
private Body createPolygon(BodyType type, float x, float y, float d, float r, float f, float halfwidth, float halfheight) {
BodyDef bodyDef = new BodyDef();
bodyDef.type = type;
bodyDef.position.set(x,y);
bodyDef.angle=45 * MathUtils.degreesToRadians;
Body square = world.createBody(bodyDef);
FixtureDef fixtureDef=new FixtureDef();
fixtureDef.density=d;
fixtureDef.restitution=r;
fixtureDef.friction=f;
fixtureDef.shape=new PolygonShape();
((PolygonShape) fixtureDef.shape).setAsBox(halfwidth, halfheight);
square.createFixture(fixtureDef);
fixtureDef.shape.dispose();
return square;
}
private Box generateRandomBox(float boxWidth, float boxHeight) {
// Create X boxes and place them randomly within the screen
float minWidth = 1f + (boxWidth*0.5f);
float maxWidth = SCENE_WIDTH - 1f - (boxWidth*0.5f);
float maxHeight = SCENE_HEIGHT - (boxHeight*0.5f);
Random rand = new Random();
float x;
x = rand.nextFloat() * ( maxWidth - minWidth) + minWidth;
return new Box(x,maxHeight, boxWidth, boxHeight, boxTexture);
}
@Override
public void resize(int width, int height) {
viewport.update(width, height);
}
@Override
public void dispose() {
boxTexture.dispose();
debugRenderer.dispose();
stage.dispose();
batch.dispose();
world.dispose();
}
// Interpolate every single box
public void interpolate(float alpha) {
for (Box box : activeBoxes) {
Body body = box.getBody();
if(body.isActive()) {
Transform transform = body.getTransform();
Vector2 bodyPosition = transform.getPosition();
float bodyAngle = transform.getRotation();
box.x = bodyPosition.x * alpha + box.x * (1.0f - alpha);
box.y = bodyPosition.y * alpha + box.y * (1.0f - alpha);
box.angle = bodyAngle * alpha + box.angle * (1.0f - alpha);
}
}
}
@Override
public void render () {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// Update boxes position
for(Box box : activeBoxes) {
Transform bodyTransform = box.getBody().getTransform();
Vector2 bodyPosition = bodyTransform.getPosition();
box.x = bodyPosition.x;
box.y = bodyPosition.y;
box.angle = bodyTransform.getRotation();
}
frameCount++;
double frameTime = 0;
// Step the world according to the selected method
if(timeStepType == TimeStep.VARIABLE) {
double newTime = TimeUtils.millis() / 1000.0;
frameTime = newTime - currentTime;
currentTime = newTime;
world.step((float) frameTime, 6, 2);
}
else if(timeStepType == TimeStep.FIXED || timeStepType == TimeStep.FIXED_INTERPOLATION) {
double newTime = TimeUtils.millis() / 1000.0;
frameTime = Math.min(newTime - currentTime, 0.25);
currentTime = newTime;
accumulator += frameTime;
while (accumulator >= step) {
world.step(step, 6, 2);
accumulator -= step;
if(timeStepType == TimeStep.FIXED_INTERPOLATION)
interpolate((float) (accumulator/step));
}
}
// Draw fps and time-step type
batch.begin();
font.draw(batch, "FPS: " + lastFPS, 100, 100);
font2.draw(batch, timeStepType.name(), 100, 50);
batch.end();
// Draw boxes
batch.begin();
batch.setProjectionMatrix(viewport.getCamera().combined);
for(Box box : activeBoxes)
box.draw(batch);
batch.end();
// Draw buttons
stage.act((float) frameTime);
stage.draw();
// Make boxes appear every second and get the frames per second(fps)
if(currentTime>= lastTime+1) {
lastTime++;
lastFPS = frameCount;
frameCount = 0;
for(int i=0; i<2; i++)
activeBoxes.add(generateRandomBox(0.25f, 0.25f));
}
// Render debug lines
//debugRenderer.render(world, viewport.getCamera().combined);
}
}