Gdx-AI agent for JME3

Well, I started to try gdx-ai without much success. Creating a simple agent that works with the engine was quite simple as I just had to translate the code in here. However, I’m haveing lot of troubles trying to get it work with bullet physics (for which I took a look at this).
My understanding about 3D operations, in special with orientations and angular velocities is really poor so I must have many mistakes but I can’t find them :S.
The problem is that If I try a simple wander steering my steerable starts to fly whatever I do :S (when it just have to move around).

I don’t really understand the angular velocity so I tried to return a constant value, but nothing, the same with the orientation get. I’m almost sure there is something bad with the applySteering method as if I don’t try to rotate the body, it just seems to work.

So… I suppose that my question is more how I get the agent physics to face the linear velocity without affecting it (however, any correction on the agent methods is a big help too).

The usecase is (I packed all classes in the same file to allow a “fast” copy-paste):

package mygame;

import com.badlogic.gdx.ai.GdxAI;
import com.badlogic.gdx.ai.steer.Steerable;
import com.badlogic.gdx.ai.steer.SteeringAcceleration;
import com.badlogic.gdx.ai.steer.behaviors.Wander;
import com.badlogic.gdx.ai.utils.Location;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector3;
import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Quad;
import com.jme3.util.TempVars;

/**
 *
 */
public class Main extends SimpleApplication {

    public static void main(String[] args) {
        Application app = new Main();
        app.start();
    }
    
    Spatial obj;
    BulletAppState bullet;
    
    
    @Override
    public void simpleInitApp() {
        Box box1 = new Box(1,1,1);
        Geometry blue = new Geometry("Box", box1);
        blue.setLocalTranslation(new Vector3f(1,-1,1));
        Material mat1 = new Material(assetManager, 
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat1.setColor("Color", ColorRGBA.Blue);
        blue.setMaterial(mat1);
        
        int quadSize = 1000;
        Quad quad = new Quad(quadSize, quadSize);
        Geometry floor = new Geometry("Floor", quad);
        Material mat2 = new Material(assetManager, 
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat2.setColor("Color", ColorRGBA.Green);
        floor.setMaterial(mat2);
        
        obj = blue;
        flyCam.setMoveSpeed(30);
        
        rootNode.attachChild(obj);
        rootNode.attachChild(floor);
        
        bullet = new BulletAppState();
        stateManager.attach(bullet);
        bullet.setDebugEnabled(true);
        
        floor.rotate(new Quaternion().fromAngleAxis(270 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X));
        floor.setLocalTranslation(-quadSize/2, -2, quadSize/2);
        floor.addControl(new RigidBodyControl(CollisionShapeFactory.createMeshShape(floor), 0));
        bullet.getPhysicsSpace().addAll(floor);
        
       // trySimpleSteering();
        tryBulletSteering();
    }
    
    
    public void tryBulletSteering() {
        
        
        obj.addControl(new RigidBodyControl(CollisionShapeFactory.createBoxShape(obj)));
        final BulletSteeringAgent agent = new BulletSteeringAgent(obj.getControl(RigidBodyControl.class));
        
        bullet.getPhysicsSpace().addAll(obj);
        
        obj.addControl(new AbstractControl() {    
                @Override
                protected void controlUpdate(float tpf) {
                    GdxAI.getTimepiece().update(tpf);
                    
                    agent.update(tpf);
                }

                @Override
                protected void controlRender(RenderManager rm, ViewPort vp) { }
            }
        );
    }
    
    
    
    public static class BulletSteeringAgent extends AbstractBulletSteeringAgent {
        Wander<Vector3> wander = new Wander(this);
        private final SteeringAcceleration<Vector3> steeringOutput = new SteeringAcceleration<>(new Vector3());

        public BulletSteeringAgent(RigidBodyControl rigidBody) {
            super(rigidBody);

            wander.setWanderRadius(1);
            wander.setWanderOffset(2);
            wander.setWanderRate(MathUtils.PI2 * 2);
        }

        public void update(float tpf) {
            wander.calculateSteering(steeringOutput);
            applySteering(steeringOutput, tpf);
        }
    }
    
    
    public static class AbstractBulletSteeringAgent implements Steerable<Vector3> {
        boolean independentFacing;
        RigidBodyControl rigidBody;
        
        private final Vector3 linearVelocity = new Vector3();
        private final Vector3 position = new Vector3();

        private float boundingRadius;
        private float zeroLinearSpeedThreshold;
        private float maxLinearSpeed = 20f;
        private float maxLinearAcceleration = 20f;
        private boolean tagged;
        private float maxAngularSpeed = 20f;
        private float maxAngularAcceleration = 20f;

        public AbstractBulletSteeringAgent(RigidBodyControl rigidBody) {
            this.rigidBody = rigidBody;
        }

        @Override
        public Vector3 getLinearVelocity() {
            TempVars vars = TempVars.get();
            
            rigidBody.getLinearVelocity(vars.vect1);
            Vector3 vector = vector3fToVector3(vars.vect1, linearVelocity);
            
            vars.release();

            return vector;
        }

        @Override
        public float getAngularVelocity() {
            TempVars vars = TempVars.get();
            
            rigidBody.getAngularVelocity(vars.vect1);
            float value = vars.vect1.length();
            
            vars.release();

            return value;
        }

        @Override
        public float getBoundingRadius() {
            return boundingRadius;
        }

        @Override
        public boolean isTagged() {
            return tagged;
        }

        @Override
        public void setTagged(boolean tagged) {
            this.tagged = tagged;
        }

        @Override
        public Vector3 getPosition() {
            TempVars vars = TempVars.get();
            
            Vector3 vector = vector3fToVector3(rigidBody.getPhysicsLocation(vars.vect1), position);
            
            vars.release();

            return vector;
        }

        @Override
        public float getOrientation() {
            TempVars vars = TempVars.get();

            Vector3f vector = rigidBody.getPhysicsRotation(vars.quat1).multLocal(vars.vect1.set(Vector3f.UNIT_Z));
            float orientation = SteeringUtil.vectorToAngle(vector.x, vector.y, vector.z);

            vars.release();

            return orientation;
        }

        @Override
        public void setOrientation(float orientation) {
            TempVars vars = TempVars.get();
            
            rigidBody.setPhysicsRotation(rigidBody.getPhysicsRotation(vars.quat1).fromAngleAxis(-orientation, Vector3f.UNIT_Y));
            
            vars.release();
        }

        @Override
        public float vectorToAngle(Vector3 vector) {
            return SteeringUtil.vectorToAngle(vector);
        }

        @Override
        public Vector3 angleToVector(Vector3 outVector, float angle) {
            return SteeringUtil.angleToVector(outVector, angle);
        }

        @Override
        public Location<Vector3> newLocation() {
            return new SteeringLocation();
        }

        @Override
        public float getZeroLinearSpeedThreshold() {
            return zeroLinearSpeedThreshold;
        }

        @Override
        public void setZeroLinearSpeedThreshold(float zeroLinearSpeedThreshold) {
            this.zeroLinearSpeedThreshold = zeroLinearSpeedThreshold;
        }

        @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;
        }

        protected void applySteering(SteeringAcceleration<Vector3> steering, float time) {
            TempVars vars = TempVars.get();
            rigidBody.getLinearVelocity(vars.vect1);
            Vector3f bodyLinear = vars.vect1;

            vector3fToVector3(bodyLinear, linearVelocity).mulAdd(steering.linear, time).limit(getMaxLinearAcceleration());
            rigidBody.setLinearVelocity(vector3ToVector3f(linearVelocity, bodyLinear));

            if (!linearVelocity.isZero(getZeroLinearSpeedThreshold())) {
                if(!independentFacing) {
                    rigidBody.getPhysicsRotation(vars.quat1);
                    vars.quat1.lookAt(bodyLinear, Vector3f.UNIT_Y);                     //vars.quat1.lookAt(vars.vect3.set(bodyLinear).multLocal(vars.vect4.set(Vector3f.UNIT_XYZ).subtractLocal(Vector3f.UNIT_Y)), Vector3f.UNIT_Y);
                    rigidBody.setPhysicsRotation(vars.quat1);
                }
            }

            vars.release();
            rigidBody.activate();
        }
        
        public static Vector3 vector3fToVector3(Vector3f from, Vector3 to) {
            return to.set(from.x, from.y, from.z);
        }

        public static Vector3f vector3ToVector3f(Vector3 from, Vector3f to) {
            return to.set(from.x, from.y, from.z);
        }
    }
    
    
    public static abstract class SteeringUtil {
    
        public static float vectorToAngle(float x, float y, float z) {
            return (float) Math.atan2(-z, x);
        }

        public static float vectorToAngle(Vector3 vector) {
            return (float) Math.atan2(-vector.z, vector.x);
        }

        public static Vector3 angleToVector(Vector3 outVector, float angle) {
            outVector.z = -(float) Math.sin(angle);
            outVector.y = 0;
            outVector.x = (float) Math.cos(angle);
            return outVector;
        }

        public static float calculateOrientationFromLinearVelocity(Steerable character) {
            // If we haven't got any velocity, then we can do nothing.
            if (character.getLinearVelocity().isZero(character.getZeroLinearSpeedThreshold()))
                return character.getOrientation();

            return character.vectorToAngle(character.getLinearVelocity());
        }
    }
    
    public static class SteeringLocation implements Location<Vector3> {

        private final Vector3 position = new Vector3();
        private float orientation;

        public SteeringLocation() { }

        @Override
        public Vector3 getPosition() {
            return position;
        }

        @Override
        public float getOrientation() {
            return orientation;
        }

        @Override
        public void setOrientation(float orientation) {
            this.orientation = orientation;
        }

        @Override
        public float vectorToAngle(Vector3 vector) {
            return SteeringUtil.vectorToAngle(vector);
        }

        @Override
        public Vector3 angleToVector(Vector3 outVector, float angle) {
            return SteeringUtil.angleToVector(outVector, angle);
        }

        @Override
        public Location<Vector3> newLocation() {
            return new SteeringLocation();
        }
    }   
}

Any help with is appreciated.

1 Like

After some more tries, I could prove that:

protected void applySteering(SteeringAcceleration<Vector3> steering, float time) {
            TempVars vars = TempVars.get();
            rigidBody.getLinearVelocity(vars.vect1);
            Vector3f bodyLinear = vars.vect1;

            vector3fToVector3(bodyLinear, linearVelocity).mulAdd(steering.linear, time).limit(getMaxLinearAcceleration());
            rigidBody.setLinearVelocity(vector3ToVector3f(linearVelocity, bodyLinear));
            
            rigidBody.setPhysicsRotation(rigidBody.getPhysicsRotation());

            vars.release();
            rigidBody.activate();
        }

is causing the same fly effect. So, I wonder why is that setting to the rigidBody it own rotation just makes it behavior different? (maybe any precision thing?). But if this doesn’t work, how can I rotate it without affecting the steerings? :S.

Now I know that I’m really lost!.

I’m sorry I haven’t the time to inspect your code, but are you trying to control your spatials by the physic engine and the AI engine at the same time? It’s like to let mom and dad drive the car together on the road to hollidays :smile:

You could maybe create you own logical entities :

  • create your JME-free game entity,
  • set its steering behavior to AI decision,
  • submit that to your physic engine controlled spatial,
  • let the physic engine apply its physic,
  • at the begin of the next step, apply the physicaly modified velocity back to your game entity

This is how I think I would do what I think you want to do :smile:

1 Like

Well, first I wanted to try the simplest way (and so, directly to the physics, just like @davebaol does on it game demo). However, my final system does:

  • Create a jme-free game entity.
  • TODO: apply the steering behavior to the physic control.
  • Lets the physics engine apply its physics.
  • Apply the physics to the entity.
  • Draws/dump the entity to a spatial

It’s the way I think it must be :smiley:. Well, the thing I wanted to have all basic use-cases (steering applied to spatial, steering applied to physic bodies…) but the last just won me so, as I use a more complex physics body (based on bettercharactercontrol), I’ll try to apply the steering to it and let the “simple” use-case for another moment :stuck_out_tongue:

Well, after some tries with my own agents I think I’m almost there but I’m having a small issue with the agent using the BetterCharacterControl (trying to emulate what @davebaol in here).
The thing is that it works fine with the wander steerer but it never stops with followPath steerer and a navmesh.

If I understand it right:, in the steerer’s method:

public boolean processSteering(SteeringAcceleration<Vector3> steering);
  • returns false when it has to stop to apply the steerer.
  • the steering acceleration is an acceleration to apply to the current linear velocity.

But the code in the followpath steerer example has:

                // Check to see if the entity has reached the end of the path
		if (steering.isZero() && dst2FromPathEnd < followPathSB.getArrivalTolerance() * followPathSB.getArrivalTolerance()) {
			return false;
		}

If the end of the path is determined by the length of the steering acceleration, more concretely, when it is zero, then it is stopping to apply the followpath but it is setting a zero acceleration increment to the object so it is still having it last linear velocity and, thus, the object is still moving.
The steering apply method in the SteerableBody is:

	protected void applySteering(SteeringAcceleration<Vector3> steering, float deltaTime) {
		// Update linear velocity trimming it to maximum speed
		linearVelocity.set(body.getLinearVelocity().mulAdd(steering.linear, deltaTime).limit(getMaxLinearSpeed()));
		body.setLinearVelocity(linearVelocity);

What I’m missing?.

(If I apply the steering accelartion in the agent as if it was it linear velocity it works fine (but it doesn’t smooth the path) for the followpath steerer but the wander steerer just rotates like an helicopter for forever)

See https://github.com/jsjolund/GdxDemo3D/blob/master/core/src/com/mygdx/game/objects/SteerableBody.java#L159-L174
Also, note that stopSteering(true); clears the linear velocity

1 Like

-O-, you just saved my life xD. I’ve been hours and I didn’t see that (I “copied” it bad :S). Thanks. I’ll publish my JME working things with some “tests” soon.