com.mygdx.game.steerers.FollowPathSteerer.java Source code

Java tutorial

Introduction

Here is the source code for com.mygdx.game.steerers.FollowPathSteerer.java

Source

/*******************************************************************************
 * Copyright 2015 See AUTHORS file.
 * <p/>
 * 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
 * <p/>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p/>
 * 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.mygdx.game.steerers;

import com.badlogic.gdx.ai.GdxAI;
import com.badlogic.gdx.ai.steer.SteeringAcceleration;
import com.badlogic.gdx.ai.steer.behaviors.FollowPath;
import com.badlogic.gdx.ai.steer.utils.paths.LinePath;
import com.badlogic.gdx.ai.steer.utils.paths.LinePath.LinePathParam;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.Ray;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Bits;
import com.mygdx.game.GameEngine;
import com.mygdx.game.GameRenderer;
import com.mygdx.game.GameScreen;
import com.mygdx.game.objects.GameObject;
import com.mygdx.game.objects.SteerableBody;
import com.mygdx.game.pathfinding.NavMeshGraphPath;
import com.mygdx.game.pathfinding.NavMeshPointPath;
import com.mygdx.game.pathfinding.Triangle;
import com.mygdx.game.settings.GameSettings;
import com.mygdx.game.utilities.Entity;
import com.mygdx.game.utilities.MyShapeRenderer;

/**
 * A steerer to follow a path while avoiding collisions. 
 * 
 * @author jsjolund
 * @author davebaol
 */
public class FollowPathSteerer extends CollisionAvoidanceSteererBase {

    /**
     * Path of triangles on the navigation mesh. Used to construct path points.
     */
    public final NavMeshGraphPath navMeshGraphPath = new NavMeshGraphPath();

    /**
     * Path points on the navigation mesh, which the steerable will follow.
     */
    public final NavMeshPointPath navMeshPointPath = new NavMeshPointPath();

    /**
     * Path which is rendered on screen
     */
    public final Array<Vector3> pathToRender = new Array<Vector3>();

    /**
     * Steering behaviour for path following
     */
    public final FollowPath<Vector3, LinePath.LinePathParam> followPathSB;

    /**
     * Holds the path segments for steering behaviour
     */
    protected final LinePath<Vector3> linePath;

    /**
     * Path segment index the steerable is currently following.
     */
    private int currentSegmentIndex = -1;

    /**
     * Points from which to construct the path segments the steerable should follow
     */
    private final Array<Vector3> centerOfMassPath = new Array<Vector3>();

    private Vector3 tmpVec1 = new Vector3();
    private Ray stationarityRayLow = new Ray();
    private Ray stationarityRayHigh = new Ray();
    private float stationarityRayLength;
    private Color stationarityRayColor;

    public FollowPathSteerer(final SteerableBody steerableBody) {
        super(steerableBody);

        // At least two points are needed to construct a line path
        Array<Vector3> waypoints = new Array<Vector3>(new Vector3[] { new Vector3(), new Vector3(1, 0, 1) });
        this.linePath = new LinePath<Vector3>(waypoints, true);
        this.followPathSB = new FollowPath<Vector3, LinePath.LinePathParam>(steerableBody, linePath, 1);

        this.prioritySteering.add(followPathSB);
    }

    public boolean calculateNewPath(Ray ray, Bits visibleLayers) {
        if (GameScreen.screen.engine.getScene().navMesh.getPath(steerableBody.getCurrentTriangle(),
                steerableBody.getGroundPosition(tmpVec1), ray, visibleLayers, GameSettings.CAMERA_PICK_RAY_DST,
                navMeshGraphPath)) {

            calculateNewPath0();
            return true;
        }
        return false;
    }

    public boolean calculateNewPath(Triangle targetTriangle, Vector3 targetPoint) {
        if (GameScreen.screen.engine.getScene().navMesh.getPath(steerableBody.getCurrentTriangle(),
                steerableBody.getGroundPosition(tmpVec1), targetTriangle, targetPoint, navMeshGraphPath)) {

            calculateNewPath0();
            return true;
        }
        return false;
    }

    /**
     * Calculate the navigation mesh point path, then assign this steering provider to the owner
     */
    private void calculateNewPath0() {
        navMeshPointPath.calculateForGraphPath(navMeshGraphPath);

        pathToRender.clear();
        pathToRender.addAll(navMeshPointPath.getVectors());

        centerOfMassPath.clear();
        // Since the navmesh path is on the ground, we need to translate
        // it to align with body origin
        for (Vector3 v : navMeshPointPath) {
            centerOfMassPath.add(new Vector3(v).add(0, steerableBody.halfExtents.y, 0));
        }
        linePath.createPath(centerOfMassPath);

        followPathSB.setTimeToTarget(steerableBody.steerSettings.getTimeToTarget())
                .setArrivalTolerance(steerableBody.steerSettings.getArrivalTolerance())
                .setDecelerationRadius(steerableBody.steerSettings.getDecelerationRadius())
                .setPredictionTime(steerableBody.steerSettings.getPredictionTime())
                .setPathOffset(steerableBody.steerSettings.getPathOffset());
        steerableBody.setZeroLinearSpeedThreshold(steerableBody.steerSettings.getZeroLinearSpeedThreshold());
        currentSegmentIndex = -1;

        collisionAvoidanceSB.setEnabled(true);

        deadlockDetection = false;

        // Make this steerer active
        steerableBody.steerer = this;
    }

    /**
     * Path segment index the steerable is currently following.
     */
    public int getCurrentSegmentIndex() {
        return currentSegmentIndex;
    }

    @Override
    public void startSteering() {
    }

    @Override
    public boolean stopSteering() {
        // Clear path
        pathToRender.clear();
        navMeshPointPath.clear();
        navMeshGraphPath.clear();
        return false;
    }

    boolean deadlockDetection;
    float deadlockDetectionStartTime;
    float collisionDuration;
    private static final float DEADLOCK_TIME = .5f;
    private static final float MAX_NO_COLLISION_TIME = DEADLOCK_TIME + .5f;

    @Override
    public boolean processSteering(SteeringAcceleration<Vector3> steering) {

        // Check if steering target path segment changed.
        LinePathParam pathParam = followPathSB.getPathParam();
        int traversedSegment = pathParam.getSegmentIndex();
        if (traversedSegment > currentSegmentIndex) {
            currentSegmentIndex = traversedSegment;
        }

        if (prioritySteering.getSelectedBehaviorIndex() == 0) {
            /*
             * Collision avoidance management
             */
            float pr = proximity.getRadius() * 1.5f;
            if (linePath.getEndPoint().dst2(steerableBody.getPosition()) <= pr * pr) {
                // Disable collision avoidance near the end of the path since the obstacle
                // will likely prevent the entity from reaching the target.
                collisionAvoidanceSB.setEnabled(false);
                deadlockDetectionStartTime = Float.POSITIVE_INFINITY;
            } else if (deadlockDetection) {
                // Accumulate collision time during deadlock detection
                collisionDuration += GdxAI.getTimepiece().getDeltaTime();

                if (GdxAI.getTimepiece().getTime() - deadlockDetectionStartTime > DEADLOCK_TIME
                        && collisionDuration > DEADLOCK_TIME * .6f) {
                    // Disable collision avoidance since most of the deadlock detection period has been spent on collision avoidance
                    collisionAvoidanceSB.setEnabled(false);
                }
            } else {
                // Start deadlock detection
                deadlockDetectionStartTime = GdxAI.getTimepiece().getTime();
                collisionDuration = 0;
                deadlockDetection = true;
            }
            return true;
        }

        /*
         * Path following management
         */
        float dst2FromPathEnd = steerableBody.getPosition().dst2(linePath.getEndPoint());

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

        // Check if collision avoidance must be re-enabled
        if (deadlockDetection && !collisionAvoidanceSB.isEnabled()
                && GdxAI.getTimepiece().getTime() - deadlockDetectionStartTime > MAX_NO_COLLISION_TIME) {
            collisionAvoidanceSB.setEnabled(true);
            deadlockDetection = false;
        }

        // If linear speed is very low and the entity is colliding something at his feet, like a step of the stairs
        // for instance, we have to increase the acceleration to make him go upstairs. 
        float minVel = .2f;
        if (steerableBody.getLinearVelocity().len2() > minVel * minVel) {
            stationarityRayColor = null;
        } else {
            steerableBody.getGroundPosition(stationarityRayLow.origin).add(0, 0.05f, 0);
            steerableBody.getDirection(stationarityRayLow.direction).scl(1f, 0f, 1f).nor();
            stationarityRayLength = steerableBody.getBoundingRadius() + 0.4f;
            Entity hitEntityLow = GameScreen.screen.engine.rayTest(stationarityRayLow, null, GameEngine.ALL_FLAG,
                    GameEngine.PC_FLAG, stationarityRayLength, null);
            if (hitEntityLow instanceof GameObject) {
                stationarityRayColor = Color.RED;
                stationarityRayHigh.set(stationarityRayLow);
                stationarityRayHigh.origin.add(0, .8f, 0);
                Entity hitEntityHigh = GameScreen.screen.engine.rayTest(stationarityRayHigh, null,
                        GameEngine.ALL_FLAG, GameEngine.PC_FLAG, stationarityRayLength, null);
                if (hitEntityHigh == null) {
                    // The entity is touching a small obstacle with his feet like a step of the stairs.
                    // Increase the acceleration to make him go upstairs.
                    steering.linear.scl(8);
                } else if (hitEntityHigh instanceof GameObject) {
                    // The entity is touching a higher obstacle like a tree, a column or something.
                    // Here we should invent something to circumvent this kind of obstacles :)
                    //steering.linear.rotateRad(Constants.V3_UP, Constants.PI0_25);
                }
            } else {
                stationarityRayColor = Color.BLUE;
            }
        }

        return true;
    }

    @Override
    public void draw(GameRenderer gameRenderer) {
        super.draw(gameRenderer);

        if (pathToRender.size > 0 && currentSegmentIndex >= 0) {
            MyShapeRenderer shapeRenderer = gameRenderer.shapeRenderer;
            shapeRenderer.setProjectionMatrix(gameRenderer.viewport.getCamera().combined);

            // Draw path target position
            Vector3 t = gameRenderer.vTmpDraw1.set(followPathSB.getInternalTargetPosition());
            t.y -= steerableBody.halfExtents.y;
            float size = .05f;
            float offset = size / 2;
            shapeRenderer.begin(MyShapeRenderer.ShapeType.Filled);
            shapeRenderer.setColor(Color.CORAL);
            shapeRenderer.box(t.x - offset, t.y - offset, t.z + offset, size, size, size);

            // Draw path
            shapeRenderer.set(MyShapeRenderer.ShapeType.Line);
            Vector3 p = t;
            int i = getCurrentSegmentIndex() + 1;
            if (i + 1 < pathToRender.size && linePath.calculatePointSegmentSquareDistance(gameRenderer.vTmpDraw2,
                    pathToRender.get(i), pathToRender.get(i + 1), p) < 0.0001)
                i++;
            while (i < pathToRender.size) {
                Vector3 q = pathToRender.get(i++);
                shapeRenderer.line(p, q);
                p = q;
            }

            // Draw stationarity rays
            if (stationarityRayColor != null) {
                shapeRenderer.setColor(stationarityRayColor);
                shapeRenderer.line(stationarityRayLow.origin, tmpVec1.set(stationarityRayLow.origin)
                        .mulAdd(stationarityRayLow.direction, stationarityRayLength));
                shapeRenderer.line(stationarityRayHigh.origin, tmpVec1.set(stationarityRayHigh.origin)
                        .mulAdd(stationarityRayHigh.direction, stationarityRayLength));
            }

            shapeRenderer.end();
        }
    }

}