All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.badlogic.gdx.ai.steer.behaviors.Jump Maven / Gradle / Ivy

There is a newer version: 1.8.2
Show newest version
/*******************************************************************************
 * 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.steer.behaviors;

import com.badlogic.gdx.ai.GdxAI;
import com.badlogic.gdx.ai.steer.Limiter;
import com.badlogic.gdx.ai.steer.Steerable;
import com.badlogic.gdx.ai.steer.SteerableAdapter;
import com.badlogic.gdx.ai.steer.SteeringAcceleration;
import com.badlogic.gdx.math.Vector;

/** First the {@code Jump} behavior calculates the linear velocity required to achieve the jump. If the calculated velocity doesn't
 * exceed the maximum linear velocity the jump is achievable; otherwise it's not. In either cases, the given callback gets
 * informed through the {@link JumpCallback#reportAchievability(boolean) reportAchievability} method. Also, if the jump is
 * achievable the run up phase begins and the {@code Jump} behavior will start to produce the linear acceleration required to match
 * the calculated velocity. Once the jump point and the linear velocity are reached with a precision within the given tolerance
 * the callback is told to jump through the {@link JumpCallback#takeoff(float, float) takeoff} method.
 * 
 * @param  Type of vector, either 2D or 3D, implementing the {@link Vector} interface
 * 
 * @author davebaol */
public class Jump> extends MatchVelocity {

	public static boolean DEBUG_ENABLED = false;

	/** The jump descriptor to use */
	protected JumpDescriptor jumpDescriptor;

	/** The gravity vector to use. Notice that this behavior only supports gravity along a single axis, which must be the one
	 * returned by the {@link GravityComponentHandler#getComponent(Vector)} method. */
	protected T gravity;

	protected GravityComponentHandler gravityComponentHandler;

	protected JumpCallback callback;

	protected float takeoffPositionTolerance;
	protected float takeoffVelocityTolerance;

	/** The maximum vertical component of jump velocity, where "vertical" stands for the axis where gravity operates. */
	protected float maxVerticalVelocity;

	/** Keeps track of whether the jump is achievable */
	private boolean isJumpAchievable;

	protected float airborneTime = 0;

	private JumpTarget jumpTarget;
	private T planarVelocity;

	/** Creates a {@code Jump} behavior.
	 * @param owner the owner of this behavior
	 * @param jumpDescriptor the descriptor of the jump to make
	 * @param gravity the gravity vector
	 * @param gravityComponentHandler the handler giving access to the vertical axis
	 * @param callback the callback that gets informed about jump achievability and when to jump */
	public Jump (Steerable owner, JumpDescriptor jumpDescriptor, T gravity,
		GravityComponentHandler gravityComponentHandler, JumpCallback callback) {
		super(owner);
		this.gravity = gravity;
		this.gravityComponentHandler = gravityComponentHandler;
		setJumpDescriptor(jumpDescriptor);
		this.callback = callback;

		this.jumpTarget = new JumpTarget(owner);
		this.planarVelocity = newVector(owner);
	}

	@Override
	public SteeringAcceleration calculateRealSteering (SteeringAcceleration steering) {
		// Check if we have a trajectory, and create one if not.
		if (target == null) {
			target = calculateTarget();
			callback.reportAchievability(isJumpAchievable);
		}

		// If the trajectory is zero, return no steering acceleration
		if (!isJumpAchievable) return steering.setZero();

		// Check if the owner has reached target position and velocity with acceptable tolerance
		if (owner.getPosition().epsilonEquals(target.getPosition(), takeoffPositionTolerance)) {
			if (DEBUG_ENABLED) GdxAI.getLogger().info("Jump", "Good position!!!");
			if (owner.getLinearVelocity().epsilonEquals(target.getLinearVelocity(), takeoffVelocityTolerance)) {
				if (DEBUG_ENABLED) GdxAI.getLogger().info("Jump", "Good Velocity!!!");
				isJumpAchievable = false;
				// Perform the jump, and return no steering (the owner is airborne, no need to steer).
				callback.takeoff(maxVerticalVelocity, airborneTime);
				return steering.setZero();
			} else {
				if (DEBUG_ENABLED)
					GdxAI.getLogger().info("Jump",
						"Bad Velocity: Speed diff. = "
							+ planarVelocity.set(target.getLinearVelocity()).sub(owner.getLinearVelocity()).len() + ", diff = ("
							+ planarVelocity + ")");
			}
		}

		// Delegate to MatchVelocity
		return super.calculateRealSteering(steering);
	}

	/** Works out the trajectory calculation. */
	private Steerable calculateTarget () {
		this.jumpTarget.position = jumpDescriptor.takeoffPosition;
		this.airborneTime = calculateAirborneTimeAndVelocity(jumpTarget.linearVelocity, jumpDescriptor, getActualLimiter()
			.getMaxLinearSpeed());
		this.isJumpAchievable = airborneTime >= 0;
		return jumpTarget;
	}

	/** Returns the airborne time and sets the {@code outVelocity} vector to the airborne planar velocity required to achieve the
	 * jump. If the jump is not achievable -1 is returned and the {@code outVelocity} vector remains unchanged.
	 * 

* Be aware that you should avoid using unlimited or very high max velocity, because this might produce a time of flight close * to 0. Actually, the motion equation for T has 2 solutions and Jump always try to use the fastest time. * @param outVelocity the output vector where the airborne planar velocity is calculated * @param jumpDescriptor the jump descriptor * @param maxLinearSpeed the maximum linear speed that can be used to achieve the jump * @return the time of flight or -1 if the jump is not achievable using the given max linear speed. */ public float calculateAirborneTimeAndVelocity (T outVelocity, JumpDescriptor jumpDescriptor, float maxLinearSpeed) { float g = gravityComponentHandler.getComponent(gravity); // Calculate the first jump time, see time of flight at http://hyperphysics.phy-astr.gsu.edu/hbase/traj.html // Notice that the equation has 2 solutions. We'd ideally like to achieve the jump in the fastest time // possible, so we want to use the smaller of the two values. However, this time value might give us // an impossible launch velocity (required speed greater than the max), so we need to check and // use the higher value if necessary. float sqrtTerm = (float)Math.sqrt(2f * g * gravityComponentHandler.getComponent(jumpDescriptor.delta) + maxVerticalVelocity * maxVerticalVelocity); float time = (-maxVerticalVelocity + sqrtTerm) / g; if (DEBUG_ENABLED) GdxAI.getLogger().info("Jump", "1st jump time = " + time); // Check if we can use it if (!checkAirborneTimeAndCalculateVelocity(outVelocity, time, jumpDescriptor, maxLinearSpeed)) { // Otherwise try the other time time = (-maxVerticalVelocity - sqrtTerm) / g; if (DEBUG_ENABLED) GdxAI.getLogger().info("Jump", "2nd jump time = " + time); if (!checkAirborneTimeAndCalculateVelocity(outVelocity, time, jumpDescriptor, maxLinearSpeed)) { return -1f; // Unachievable jump } } return time; // Achievable jump } private boolean checkAirborneTimeAndCalculateVelocity (T outVelocity, float time, JumpDescriptor jumpDescriptor, float maxLinearSpeed) { // Calculate the planar velocity planarVelocity.set(jumpDescriptor.delta).scl(1f / time); gravityComponentHandler.setComponent(planarVelocity, 0f); // Check the planar linear speed if (planarVelocity.len2() < maxLinearSpeed * maxLinearSpeed) { // We have a valid solution, so store it by merging vertical and non-vertical axes float verticalValue = gravityComponentHandler.getComponent(outVelocity); gravityComponentHandler.setComponent(outVelocity.set(planarVelocity), verticalValue); if (DEBUG_ENABLED) GdxAI.getLogger().info("Jump", "targetLinearVelocity = " + outVelocity + "; targetLinearSpeed = " + outVelocity.len()); return true; } return false; } /** Returns the jump descriptor. */ public JumpDescriptor getJumpDescriptor () { return jumpDescriptor; } /** Sets the jump descriptor to use. * @param jumpDescriptor the jump descriptor to set * @return this behavior for chaining. */ public Jump setJumpDescriptor (JumpDescriptor jumpDescriptor) { this.jumpDescriptor = jumpDescriptor; this.target = null; this.isJumpAchievable = false; return this; } /** Returns the gravity vector. */ public T getGravity () { return gravity; } /** Sets the gravity vector. * @param gravity the gravity to set * @return this behavior for chaining. */ public Jump setGravity (T gravity) { this.gravity = gravity; return this; } /** Returns the maximum vertical component of jump velocity, where "vertical" stands for the axis where gravity operates. */ public float getMaxVerticalVelocity () { return maxVerticalVelocity; } /** Sets the maximum vertical component of jump velocity, where "vertical" stands for the axis where gravity operates. * @param maxVerticalVelocity the maximum vertical velocity to set * @return this behavior for chaining. */ public Jump setMaxVerticalVelocity (float maxVerticalVelocity) { this.maxVerticalVelocity = maxVerticalVelocity; return this; } /** Returns the tolerance used to check if the owner has reached the takeoff location. */ public float getTakeoffPositionTolerance () { return takeoffPositionTolerance; } /** Sets the tolerance used to check if the owner has reached the takeoff location. * @param takeoffPositionTolerance the takeoff position tolerance to set * @return this behavior for chaining. */ public Jump setTakeoffPositionTolerance (float takeoffPositionTolerance) { this.takeoffPositionTolerance = takeoffPositionTolerance; return this; } /** Returns the tolerance used to check if the owner has reached the takeoff velocity. */ public float getTakeoffVelocityTolerance () { return takeoffVelocityTolerance; } /** Sets the tolerance used to check if the owner has reached the takeoff velocity. * @param takeoffVelocityTolerance the takeoff velocity tolerance to set * @return this behavior for chaining. */ public Jump setTakeoffVelocityTolerance (float takeoffVelocityTolerance) { this.takeoffVelocityTolerance = takeoffVelocityTolerance; return this; } /** Sets the the tolerance used to check if the owner has reached the takeoff location with the required velocity. * @param takeoffTolerance the takeoff tolerance for both position and velocity * @return this behavior for chaining. */ public Jump setTakeoffTolerance (float takeoffTolerance) { setTakeoffPositionTolerance(takeoffTolerance); setTakeoffVelocityTolerance(takeoffTolerance); return this; } // // Setters overridden in order to fix the correct return type for chaining // @Override public Jump setOwner (Steerable owner) { this.owner = owner; return this; } @Override public Jump setEnabled (boolean enabled) { this.enabled = enabled; return this; } /** Sets the limiter of this steering behavior. The given limiter must at least take care of the maximum linear acceleration and * speed. * @return this behavior for chaining. */ @Override public Jump setLimiter (Limiter limiter) { this.limiter = limiter; return this; } /** Sets the target whose velocity should be matched. Notice that this method is inherited from {@link MatchVelocity}. Usually * with {@code Jump} you should never call it because a specialized internal target has already been created implicitly. * @param target the target to set * @return this behavior for chaining. */ @Override public Jump setTarget (Steerable target) { this.target = target; return this; } @Override public Jump setTimeToTarget (float timeToTarget) { this.timeToTarget = timeToTarget; return this; } // // Nested classes and interfaces // private static class JumpTarget> extends SteerableAdapter { T position; T linearVelocity; public JumpTarget (Steerable other) { this.position = null; this.linearVelocity = other.getPosition().cpy().setZero(); } @Override public T getPosition () { return position; } @Override public T getLinearVelocity () { return linearVelocity; } } /** A {@code JumpDescriptor} contains jump information like the take-off and the landing position. * * @param Type of vector, either 2D or 3D, implementing the {@link Vector} interface * * @author davebaol */ public static class JumpDescriptor> { /** The position of the takeoff pad */ public T takeoffPosition; /** The position of the landing pad */ public T landingPosition; /** The change in position from takeoff to landing. This is calculated from the other values. */ public T delta; /** Creates a {@code JumpDescriptor} with the given takeoff and landing positions. * @param takeoffPosition the position of the takeoff pad * @param landingPosition the position of the landing pad */ public JumpDescriptor (T takeoffPosition, T landingPosition) { this.takeoffPosition = takeoffPosition; this.landingPosition = landingPosition; this.delta = landingPosition.cpy(); set(takeoffPosition, landingPosition); } /** Sets this {@code JumpDescriptor} from the given takeoff and landing positions. * @param takeoffPosition the position of the takeoff pad * @param landingPosition the position of the landing pad */ public void set (T takeoffPosition, T landingPosition) { this.takeoffPosition.set(takeoffPosition); this.landingPosition.set(landingPosition); this.delta.set(landingPosition).sub(takeoffPosition); } } /** A {@code GravityComponentHandler} is aware of the axis along which the gravity acts. * * @param Type of vector, either 2D or 3D, implementing the {@link Vector} interface * * @author davebaol */ public interface GravityComponentHandler> { /** Returns the component of the given vector along which the gravity operates. *

* Assuming a 3D coordinate system where the gravity is acting along the y-axis, this method will be implemented as follows: * *

		 * public float getComponent (Vector3 vector) {
		 * 	return vector.y;
		 * }
		 * 
* * Of course, the equivalent 2D implementation will use Vector2 instead of Vector3. * @param vector the vector * @return the value of the component affected by gravity. */ public float getComponent (T vector); /** Sets the component of the given vector along which the gravity operates. *

* Assuming a 3D coordinate system where the gravity is acting along the y-axis, this method will be implemented as follows: * *

		 * public void setComponent (Vector3 vector, float value) {
		 * 	vector.y = value;
		 * }
		 * 
* * Of course, the equivalent 2D implementation will use Vector2 instead of Vector3. * @param vector the vector * @param value the value of the component affected by gravity */ public void setComponent (T vector, float value); } /** The {@code JumpCallback} allows you to know whether a jump is achievable and when to jump. * * @author davebaol */ public interface JumpCallback { /** Reports whether the jump is achievable or not. *

* A jump is not achievable when the character's maximum linear velocity is not enough, in which case the jump behavior * won't produce any acceleration; you might want to use pathfinding to plan a new path. *

* If the jump is achievable the run up phase will start immediately and the character will try to match the target velocity * toward the takeoff point. This is the right moment to start the run up animation, if needed. * @param achievable whether the jump is achievable or not. */ public void reportAchievability (boolean achievable); /** This method is called to notify that both the position and velocity of the character are good enough to jump. * @param maxVerticalVelocity the velocity to set along the vertical axis to achieve the jump * @param time the duration of the jump */ public void takeoff (float maxVerticalVelocity, float time); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy