/*******************************************************************************
* 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.steer.Limiter;
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.steer.limiters.NullLimiter;
import com.badlogic.gdx.math.Vector;
import com.badlogic.gdx.utils.Array;
/** This combination behavior simply sums up all the behaviors, applies their weights, and truncates the result before returning.
* There are no constraints on the blending weights; they don't have to sum to one, for example, and rarely do. Don't think of
* {@code BlendedSteering} as a weighted mean, because it's not.
* <p>
* With {@code BlendedSteering} you can combine multiple behaviors to get a more complex behavior. It can work fine, but the
* trade-off is that it comes with a few problems:
* <ul>
* <li>Since every active behavior is calculated every time step, it can be a costly method to process.</li>
* <li>Behavior weights can be difficult to tweak. There have been research projects that have tried to evolve the steering
* weights using genetic algorithms or neural networks. Results have not been encouraging, however, and manual experimentation
* still seems to be the most sensible approach.</li>
* <li>It's problematic with conflicting forces. For instance, a common scenario is where an agent is backed up against a wall by
* several other agents. In this example, the separating forces from the neighboring agents can be greater than the repulsive
* force from the wall and the agent can end up being pushed through the wall boundary. This is almost certainly not going to be
* favorable. Sure you can make the weights for the wall avoidance huge, but then your agent may behave strangely next time it
* finds itself alone and next to a wall.</li>
* </ul>
*
* @param <T> Type of vector, either 2D or 3D, implementing the {@link Vector} interface
*
* @author davebaol */
public class BlendedSteering<T extends Vector<T>> extends SteeringBehavior<T> {
/** The list of behaviors and their corresponding blending weights. */
protected Array<BehaviorAndWeight<T>> list;
private SteeringAcceleration<T> steering;
/** Creates a {@code BlendedSteering} for the specified {@code owner}, {@code maxLinearAcceleration} and
* {@code maxAngularAcceleration}.
* @param owner the owner of this behavior. */
public BlendedSteering (Steerable<T> owner) {
super(owner);
this.list = new Array<BehaviorAndWeight<T>>();
this.steering = new SteeringAcceleration<T>(newVector(owner));
}
/** Adds a steering behavior and its weight to the list.
* @param behavior the steering behavior to add
* @param weight the weight of the behavior
* @return this behavior for chaining. */
public BlendedSteering<T> add (SteeringBehavior<T> behavior, float weight) {
return add(new BehaviorAndWeight<T>(behavior, weight));
}
/** Adds a steering behavior and its weight to the list.
* @param item the steering behavior and its weight
* @return this behavior for chaining. */
public BlendedSteering<T> add (BehaviorAndWeight<T> item) {
item.behavior.setOwner(owner);
list.add(item);
return this;
}
/** Removes a steering behavior from the list.
* @param item the steering behavior to remove */
public void remove (BehaviorAndWeight<T> item) {
list.removeValue(item, true);
}
/** Removes a steering behavior from the list.
* @param behavior the steering behavior to remove */
public void remove (SteeringBehavior<T> behavior) {
for (int i = 0; i < list.size; i++) {
if(list.get(i).behavior == behavior) {
list.removeIndex(i);
return;
}
}
}
/** Returns the weighted behavior at the specified index.
* @param index the index of the weighted behavior to return */
public BehaviorAndWeight<T> get (int index) {
return list.get(index);
}
@Override
protected SteeringAcceleration<T> calculateRealSteering (SteeringAcceleration<T> blendedSteering) {
// Clear the output to start with
blendedSteering.setZero();
// Go through all the behaviors
int len = list.size;
for (int i = 0; i < len; i++) {
BehaviorAndWeight<T> bw = list.get(i);
// Calculate the behavior's steering
bw.behavior.calculateSteering(steering);
// Scale and add the steering to the accumulator
blendedSteering.mulAdd(steering, bw.weight);
}
Limiter actualLimiter = getActualLimiter();
// Crop the result
blendedSteering.linear.limit(actualLimiter.getMaxLinearAcceleration());
if (blendedSteering.angular > actualLimiter.getMaxAngularAcceleration())
blendedSteering.angular = actualLimiter.getMaxAngularAcceleration();
return blendedSteering;
}
//
// Setters overridden in order to fix the correct return type for chaining
//
@Override
public BlendedSteering<T> setOwner (Steerable<T> owner) {
this.owner = owner;
return this;
}
@Override
public BlendedSteering<T> 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 and angular
* accelerations. You can use {@link NullLimiter#NEUTRAL_LIMITER} to avoid all truncations.
* @return this behavior for chaining. */
@Override
public BlendedSteering<T> setLimiter (Limiter limiter) {
this.limiter = limiter;
return this;
}
//
// Nested classes
//
public static class BehaviorAndWeight<T extends Vector<T>> {
protected SteeringBehavior<T> behavior;
protected float weight;
public BehaviorAndWeight (SteeringBehavior<T> behavior, float weight) {
this.behavior = behavior;
this.weight = weight;
}
public SteeringBehavior<T> getBehavior () {
return behavior;
}
public void setBehavior (SteeringBehavior<T> behavior) {
this.behavior = behavior;
}
public float getWeight () {
return weight;
}
public void setWeight (float weight) {
this.weight = weight;
}
}
}