/******************************************************************************* * 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.bullet; 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.tests.utils.bullet.BulletEntity; import com.badlogic.gdx.ai.utils.Location; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.physics.bullet.dynamics.btRigidBody; /** @author Daniel Holderbaum */ public class SteeringBulletEntity extends BulletEntity implements Steerable<Vector3> { public btRigidBody body; float maxLinearSpeed; float maxLinearAcceleration; float maxAngularSpeed; float maxAngularAcceleration; float boundingRadius; boolean tagged; boolean independentFacing; protected SteeringBehavior<Vector3> steeringBehavior; private static final SteeringAcceleration<Vector3> steeringOutput = new SteeringAcceleration<Vector3>(new Vector3()); private static final Quaternion tmpQuaternion = new Quaternion(); private static final Matrix4 tmpMatrix4 = new Matrix4(); private final Vector3 tmpVector3 = new Vector3(); private static final Vector3 ANGULAR_LOCK = new Vector3(0, 1, 0); public SteeringBulletEntity (BulletEntity copyEntity) { this(copyEntity, false); } public SteeringBulletEntity (BulletEntity copyEntity, boolean independentFacing) { super(copyEntity.modelInstance, copyEntity.body); if (!(copyEntity.body instanceof btRigidBody)) { throw new IllegalArgumentException("Body must be a rigid body."); } // if (copyEntity.body.isStaticOrKinematicObject()) { // throw new IllegalArgumentException("Body must be a dynamic body."); // } this.body = (btRigidBody)copyEntity.body; body.setAngularFactor(ANGULAR_LOCK); this.independentFacing = independentFacing; } public SteeringBehavior<Vector3> getSteeringBehavior () { return steeringBehavior; } public void setSteeringBehavior (SteeringBehavior<Vector3> steeringBehavior) { this.steeringBehavior = steeringBehavior; } // float oldOrientation = 0; 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); } } protected void applySteering (SteeringAcceleration<Vector3> steering, float deltaTime) { boolean anyAccelerations = false; // Update position and linear velocity if (!steeringOutput.linear.isZero()) { // this method internally scales the force by deltaTime body.applyCentralForce(steeringOutput.linear); anyAccelerations = true; } // Update orientation and angular velocity if (isIndependentFacing()) { if (steeringOutput.angular != 0) { // this method internally scales the torque by deltaTime body.applyTorque(tmpVector3.set(0, steeringOutput.angular, 0)); anyAccelerations = true; } } else { // If we haven't got any velocity, then we can do nothing. Vector3 linVel = getLinearVelocity(); if (!linVel.isZero(getZeroLinearSpeedThreshold())) { // // TODO: Commented out!!! // Looks like the code below creates troubles in combination with the applyCentralForce above // Maybe we should be more consistent by only applying forces or setting velocities. // // float newOrientation = vectorToAngle(linVel); // Vector3 angVel = body.getAngularVelocity(); // angVel.y = (newOrientation - oldOrientation) % MathUtils.PI2; // if (angVel.y > MathUtils.PI) angVel.y -= MathUtils.PI2; // angVel.y /= deltaTime; // body.setAngularVelocity(angVel); // anyAccelerations = true; // oldOrientation = 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 Vector3 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 Vector3 angVelocity = body.getAngularVelocity(); if (angVelocity.y > getMaxAngularSpeed()) { angVelocity.y = getMaxAngularSpeed(); body.setAngularVelocity(angVelocity); } } } public boolean isIndependentFacing () { return independentFacing; } public void setIndependentFacing (boolean independentFacing) { this.independentFacing = independentFacing; } @Override public void setOrientation (float orientation) { transform.setToRotationRad(0, 1, 0, orientation); body.setWorldTransform(transform); } @Override public float getOrientation () { transform.getRotation(tmpQuaternion, true); return tmpQuaternion.getYawRad(); } @Override public Vector3 getLinearVelocity () { return body.getLinearVelocity(); } @Override public float getAngularVelocity () { Vector3 angularVelocity = body.getAngularVelocity(); return angularVelocity.y; } @Override public float getBoundingRadius () { // TODO: this should be calculated via the actual btShape return .5f; } @Override public boolean isTagged () { return tagged; } @Override public void setTagged (boolean tagged) { this.tagged = tagged; } @Override public Location<Vector3> newLocation () { return new BulletLocation(); } @Override public float vectorToAngle (Vector3 vector) { return BulletSteeringUtils.vectorToAngle(vector); } @Override public Vector3 angleToVector (Vector3 outVector, float angle) { return BulletSteeringUtils.angleToVector(outVector, angle); } @Override public Vector3 getPosition () { body.getMotionState().getWorldTransform(tmpMatrix4); return tmpMatrix4.getTranslation(tmpVector3); } @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(); } }