/*******************************************************************************
* Copyright 2014 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.badlogic.gdx.ai.tests.steer.box2d;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.ai.steer.Steerable;
import com.badlogic.gdx.ai.steer.SteeringAcceleration;
import com.badlogic.gdx.ai.steer.SteeringBehavior;
import com.badlogic.gdx.ai.utils.Location;
import com.badlogic.gdx.graphics.g2d.Batch;
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;
/** A steering entity for box2d physics engine.
*
* @author davebaol */
public class Box2dSteeringEntity implements Steerable<Vector2> {
TextureRegion region;
Body body;
float boundingRadius;
boolean tagged;
float maxLinearSpeed;
float maxLinearAcceleration;
float maxAngularSpeed;
float maxAngularAcceleration;
boolean independentFacing;
protected SteeringBehavior<Vector2> steeringBehavior;
private static final SteeringAcceleration<Vector2> steeringOutput = new SteeringAcceleration<Vector2>(new Vector2());
public Box2dSteeringEntity (TextureRegion region, Body body, boolean independentFacing, float boundingRadius) {
this.region = region;
this.body = body;
this.independentFacing = independentFacing;
this.boundingRadius = boundingRadius;
this.tagged = false;
body.setUserData(this);
}
public TextureRegion getRegion () {
return region;
}
public void setRegion (TextureRegion region) {
this.region = region;
}
public Body getBody () {
return body;
}
public void setBody (Body body) {
this.body = body;
}
public boolean isIndependentFacing () {
return independentFacing;
}
public void setIndependentFacing (boolean independentFacing) {
this.independentFacing = independentFacing;
}
@Override
public Vector2 getPosition () {
return body.getPosition();
}
@Override
public float getOrientation () {
return body.getAngle();
}
@Override
public void setOrientation (float orientation) {
body.setTransform(getPosition(), orientation);
}
@Override
public Vector2 getLinearVelocity () {
return body.getLinearVelocity();
}
@Override
public float getAngularVelocity () {
return body.getAngularVelocity();
}
@Override
public float getBoundingRadius () {
return boundingRadius;
}
@Override
public boolean isTagged () {
return tagged;
}
@Override
public void setTagged (boolean tagged) {
this.tagged = tagged;
}
@Override
public Location<Vector2> newLocation () {
return new Box2dLocation();
}
@Override
public float vectorToAngle (Vector2 vector) {
return Box2dSteeringUtils.vectorToAngle(vector);
}
@Override
public Vector2 angleToVector (Vector2 outVector, float angle) {
return Box2dSteeringUtils.angleToVector(outVector, angle);
}
public SteeringBehavior<Vector2> getSteeringBehavior () {
return steeringBehavior;
}
public void setSteeringBehavior (SteeringBehavior<Vector2> steeringBehavior) {
this.steeringBehavior = steeringBehavior;
}
public void update (float deltaTime) {
if (steeringBehavior != null) {
// Calculate steering acceleration
steeringBehavior.calculateSteering(steeringOutput);
/*
* Here you might want to add a motor control layer filtering steering accelerations.
*
* For instance, a car in a driving game has physical constraints on its movement: it cannot turn while stationary; the
* faster it moves, the slower it can turn (without going into a skid); it can brake much more quickly than it can
* accelerate; and it only moves in the direction it is facing (ignoring power slides).
*/
// Apply steering acceleration
applySteering(steeringOutput, deltaTime);
}
wrapAround(Box2dSteeringTest.pixelsToMeters(Gdx.graphics.getWidth()),
Box2dSteeringTest.pixelsToMeters(Gdx.graphics.getHeight()));
}
protected void applySteering (SteeringAcceleration<Vector2> steering, float deltaTime) {
boolean anyAccelerations = false;
// Update position and linear velocity.
if (!steeringOutput.linear.isZero()) {
// this method internally scales the force by deltaTime
body.applyForceToCenter(steeringOutput.linear, true);
anyAccelerations = true;
}
// Update orientation and angular velocity
if (isIndependentFacing()) {
if (steeringOutput.angular != 0) {
// this method internally scales the torque by deltaTime
body.applyTorque(steeringOutput.angular, true);
anyAccelerations = true;
}
} else {
// If we haven't got any velocity, then we can do nothing.
Vector2 linVel = getLinearVelocity();
if (!linVel.isZero(getZeroLinearSpeedThreshold())) {
float newOrientation = vectorToAngle(linVel);
body.setAngularVelocity((newOrientation - getAngularVelocity()) * deltaTime); // this is superfluous if independentFacing is always true
body.setTransform(body.getPosition(), newOrientation);
}
}
if (anyAccelerations) {
// body.activate();
// TODO:
// Looks like truncating speeds here after applying forces doesn't work as expected.
// We should likely cap speeds form inside an InternalTickCallback, see
// http://www.bulletphysics.org/mediawiki-1.5.8/index.php/Simulation_Tick_Callbacks
// Cap the linear speed
Vector2 velocity = body.getLinearVelocity();
float currentSpeedSquare = velocity.len2();
float maxLinearSpeed = getMaxLinearSpeed();
if (currentSpeedSquare > maxLinearSpeed * maxLinearSpeed) {
body.setLinearVelocity(velocity.scl(maxLinearSpeed / (float)Math.sqrt(currentSpeedSquare)));
}
// Cap the angular speed
float maxAngVelocity = getMaxAngularSpeed();
if (body.getAngularVelocity() > maxAngVelocity) {
body.setAngularVelocity(maxAngVelocity);
}
}
}
// the display area is considered to wrap around from top to bottom
// and from left to right
protected void wrapAround (float maxX, float maxY) {
float k = Float.POSITIVE_INFINITY;
Vector2 pos = body.getPosition();
if (pos.x > maxX) k = pos.x = 0.0f;
if (pos.x < 0) k = pos.x = maxX;
if (pos.y < 0) k = pos.y = maxY;
if (pos.y > maxY) k = pos.y = 0.0f;
if (k != Float.POSITIVE_INFINITY) body.setTransform(pos, body.getAngle());
}
public void draw (Batch batch) {
Vector2 pos = body.getPosition();
float w = region.getRegionWidth();
float h = region.getRegionHeight();
float ox = w / 2f;
float oy = h / 2f;
batch.draw(region, //
Box2dSteeringTest.metersToPixels(pos.x) - ox, Box2dSteeringTest.metersToPixels(pos.y) - oy, //
ox, oy, //
w, h, //
1, 1, //
body.getAngle() * MathUtils.radiansToDegrees); //
}
//
// Limiter implementation
//
@Override
public float getMaxLinearSpeed () {
return maxLinearSpeed;
}
@Override
public void setMaxLinearSpeed (float maxLinearSpeed) {
this.maxLinearSpeed = maxLinearSpeed;
}
@Override
public float getMaxLinearAcceleration () {
return maxLinearAcceleration;
}
@Override
public void setMaxLinearAcceleration (float maxLinearAcceleration) {
this.maxLinearAcceleration = maxLinearAcceleration;
}
@Override
public float getMaxAngularSpeed () {
return maxAngularSpeed;
}
@Override
public void setMaxAngularSpeed (float maxAngularSpeed) {
this.maxAngularSpeed = maxAngularSpeed;
}
@Override
public float getMaxAngularAcceleration () {
return maxAngularAcceleration;
}
@Override
public void setMaxAngularAcceleration (float maxAngularAcceleration) {
this.maxAngularAcceleration = maxAngularAcceleration;
}
@Override
public float getZeroLinearSpeedThreshold () {
return 0.001f;
}
@Override
public void setZeroLinearSpeedThreshold (float value) {
throw new UnsupportedOperationException();
}
}