/*
* This file is part of QuarterBukkit-Plugin.
* Copyright (c) 2012 QuarterCode <http://www.quartercode.com/>
*
* QuarterBukkit-Plugin is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QuarterBukkit-Plugin is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QuarterBukkit-Plugin. If not, see <http://www.gnu.org/licenses/>.
*/
package com.quartercode.quarterbukkit.api.objectsystem.util;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.bukkit.util.Vector;
import com.quartercode.quarterbukkit.api.objectsystem.ActiveObjectSystem;
import com.quartercode.quarterbukkit.api.objectsystem.physics.PhysicsObject;
import com.quartercode.quarterbukkit.api.objectsystem.physics.VelocityModifier;
/**
* A {@link VelocityModifier} that returns a velocity modification {@link Vector} which accelerates the {@link PhysicsObject} towards or away from a certain point.
* It can be compared with an (inverted) gravitational force that pulls every object towards or away from its center.
* Note that you can limit the velocity modification to a specific area using the {@link ShapedModifier} wrapper.
*
* @param <O> The type of object the targeted velocity modifier accepts. This must extend {@link PhysicsObject}.
* @see PhysicsObject
* @see ShapedModifier
*/
public class TargetedVelocityModifier<O extends PhysicsObject> implements VelocityModifier<O> {
/**
* The different types of targeted velocity modifications.
* They have different behaviors depending on the distance of a {@link PhysicsObject} to the target point.
*
* @see TargetedVelocityModifier
*/
public static enum TargetedVelocityModificationType {
/**
* Takes the position {@link Vector} of the {@link PhysicsObject} to the target and multiplies it by the negative value of the provided factor.
* That means that the acceleration is bigger if the object is farther away from the target point.
* If the object is really close to the center, the acceleration is very close to zero.<br>
* <br>
* The default factor is 0.01.
*/
NEGATIVE_POSITION_VECTOR (0.01F),
/**
* Takes the position {@link Vector} of the {@link PhysicsObject} to the target, uniforms its length and multiplies it by the negative value of the provided factor.
* That means that the acceleration doesn't change if the object is farther away from or closer to the target point.<br>
* <br>
* The default factor is 0.01.
*/
UNIFORM (0.01F),
/**
* Takes the position {@link Vector} of the {@link PhysicsObject} to the target, uniforms its length and multiplies it by the result of a basic gravitational formula without mass.
* Basically, the vector is multiplied with the result of the following term:
*
* <pre>
* - (factor / distance ˆ 2)
* </pre>
*
* This type might be dangerous because the acceleration becomes really high when an object gets closer to the target.
* The velocity change is squared each time the distance between the object and the target halves.
* Actually, the acceleration is infinite when the object directly passes through center.
* However, realistic single body orbital mechanics can be simulated if all objects don't get to close.<br>
* <br>
* The default factor is 5.
*/
GRAVITY (5);
private final float defaultFactor;
private TargetedVelocityModificationType(float defaultFactor) {
this.defaultFactor = defaultFactor;
}
/**
* Returns the default factor that is used for the type if no factor is explicitly specified.
*
* @return The default factor of the type.
*/
public float getDefaultFactor() {
return defaultFactor;
}
}
private static final Vector ZERO_VECTOR = new Vector();
private TargetedVelocityModificationType type;
private Vector target;
private float factor;
private boolean[] ignoredComponenets = new boolean[3];
private Vector effectiveTarget;
/**
* Creates a new targeted velocity modifier that makes {@link PhysicsObject}s accelerate towards the given target {@link Vector}.
* This uses the {@link TargetedVelocityModificationType#NEGATIVE_POSITION_VECTOR} type and its default factor.
*
* @param target The target vector all objects accelerate towards.
* Note that this vector is always relative to the origin of any {@link ActiveObjectSystem} the modifier is used by.
*/
public TargetedVelocityModifier(Vector target) {
this(target, TargetedVelocityModificationType.NEGATIVE_POSITION_VECTOR);
}
/**
* Creates a new targeted velocity modifier that makes {@link PhysicsObject}s accelerate towards or away from the given target {@link Vector} with the given factor.
* This uses the {@link TargetedVelocityModificationType#NEGATIVE_POSITION_VECTOR} type.
*
* @param target The target vector all objects accelerate towards or away from.
* Note that this vector is always relative to the origin of any {@link ActiveObjectSystem} the modifier is used by.
* @param factor The factor that defines how fast an object accelerates towards or away from the target vector.
* By choosing this value to be negative, all objects accelerate away from the target.
* Note that the range of appropriate values highly depends on the modification type.
* You should look up the default factor of {@link TargetedVelocityModificationType#NEGATIVE_POSITION_VECTOR} to get a starting point for further experiments.
*/
public TargetedVelocityModifier(Vector target, float factor) {
this(target, TargetedVelocityModificationType.NEGATIVE_POSITION_VECTOR, factor);
}
/**
* Creates a new targeted velocity modifier that makes {@link PhysicsObject}s accelerate towards the given target {@link Vector} with the given {@link TargetedVelocityModificationType}.
* This uses the default factor of the given modification type.
*
* @param target The target vector all objects accelerate towards.
* Note that this vector is always relative to the origin of any {@link ActiveObjectSystem} the modifier is used by.
* @param type The modification type that defines how the acceleration changes when the distance between the object and the target changes
*/
public TargetedVelocityModifier(Vector target, TargetedVelocityModificationType type) {
this(target, type, type.getDefaultFactor());
}
/**
* Creates a new targeted velocity modifier that makes {@link PhysicsObject}s accelerate towards or away from the given target {@link Vector} with the given
* {@link TargetedVelocityModificationType} and factor.
*
* @param target The target vector all objects accelerate towards or away from.
* Note that this vector is always relative to the origin of any {@link ActiveObjectSystem} the modifier is used by.
* @param type The modification type that defines how the acceleration changes when the distance between the object and the target changes
* @param factor The factor that defines how fast an object accelerates towards or away from the target vector.
* By choosing this value to be negative, all objects accelerate away from the target.
* Note that the range of appropriate values highly depends on the modification type.
* You should look up the default factors of the different types to get a starting point for further experiments.
*/
public TargetedVelocityModifier(Vector target, TargetedVelocityModificationType type, float factor) {
setTarget(target);
this.factor = factor;
setType(type);
}
/**
* Returns the {@link TargetedVelocityModificationType} that defines how the acceleration changes when the distance between the {@link PhysicsObject} and the target changes.
*
* @return The modification type.
*/
public TargetedVelocityModificationType getType() {
return type;
}
/**
* Sets the {@link TargetedVelocityModificationType} that defines how the acceleration changes when the distance between the {@link PhysicsObject} and the target changes.
*
* @param type The new modification type.
* @return This object.
*/
public TargetedVelocityModifier<O> setType(TargetedVelocityModificationType type) {
Validate.notNull(target, "Type of targeted velocity modifier cannot be null");
this.type = type;
return this;
}
/**
* Returns the target {@link Vector} all objects accelerate towards or away from depending on the sign of the factor.
* Note that this vector is always relative to the origin of any {@link ActiveObjectSystem} the modifier is used by.
*
* @return The target vector.
*/
public Vector getTarget() {
return target;
}
/**
* Sets the target {@link Vector} all objects accelerate towards or away from depending on the sign of the factor.
* Note that this vector is always relative to the origin of any {@link ActiveObjectSystem} the modifier is used by.
*
* @param target The new target vector.
* @return This object.
*/
public TargetedVelocityModifier<O> setTarget(Vector target) {
Validate.notNull(target, "Target of targeted velocity modifier cannot be null");
this.target = target.clone();
updateEffectiveTarget();
return this;
}
/**
* Returns the factor that defines how fast an object accelerates towards or away from the target vector.
* By making this value negative, all objects accelerate away from the target.
* Note that the range of appropriate values highly depends on the {@link TargetedVelocityModificationType}.
* You should look up the default factors of the different types to get a starting point for further experiments.
*
* @return The acceleration factor.
*/
public float getFactor() {
return factor;
}
/**
* Sets the factor that defines how fast an object accelerates towards or away from the target vector.
* By making this value negative, all objects accelerate away from the target.
* Note that the range of appropriate values highly depends on the {@link TargetedVelocityModificationType}.
* You should look up the default factors of the different types to get a starting point for further experiments.
*
* @param factor The new acceleration factor.
* @return This object.
*/
public TargetedVelocityModifier<O> setFactor(float factor) {
this.factor = factor;
return this;
}
/**
* Returns which {@link Vector} components (x/y/z) aren't changed by the velocity modifier.
* For example, if the y component is ignored, the y component of any modification vector will be zero.
* That way, you can create objects that are dragged towards a specific line (1 ignored) or surface (2 ignored) instead of a point.
*
* @return The vector components that are ignored.
* The array has 3 elements.
* The first element represents the x component, the second one the y component, and the last one the z component.
*/
public boolean[] getIgnoredComponenets() {
return ignoredComponenets.clone();
}
/**
* Sets which {@link Vector} components (x/y/z) aren't changed by the velocity modifier.
* For example, if the y component is ignored, the y component of any modification vector will be zero.
* That way, you can create objects that are dragged towards a specific line (1 ignored) or surface (2 ignored) instead of a point.
*
* @param ignoreX Whether the x vector component should be ignored.
* @param ignoreY Whether the y vector component should be ignored.
* @param ignoreZ Whether the z vector component should be ignored.
* @return This object.
*/
public TargetedVelocityModifier<O> setIgnoredComponenets(boolean ignoreX, boolean ignoreY, boolean ignoreZ) {
ignoredComponenets = new boolean[] { ignoreX, ignoreY, ignoreZ };
updateEffectiveTarget();
return this;
}
@Override
public Vector getModification(O object) {
Vector relativePosition = removeIgnoredComponents(object.getPosition()).subtract(effectiveTarget);
if (!relativePosition.equals(ZERO_VECTOR)) {
if (type == TargetedVelocityModificationType.NEGATIVE_POSITION_VECTOR) {
return relativePosition.multiply(-1 * factor);
} else if (type == TargetedVelocityModificationType.UNIFORM) {
return relativePosition.normalize().multiply(-1 * factor);
} else if (type == TargetedVelocityModificationType.GRAVITY) {
double gravityAcceleration = factor / relativePosition.lengthSquared();
return relativePosition.normalize().multiply(-1 * gravityAcceleration);
}
}
return null;
}
private void updateEffectiveTarget() {
effectiveTarget = removeIgnoredComponents(target.clone());
}
private Vector removeIgnoredComponents(Vector vector) {
if (ignoredComponenets[0]) {
vector.setX(0);
}
if (ignoredComponenets[1]) {
vector.setY(0);
}
if (ignoredComponenets[2]) {
vector.setZ(0);
}
return vector;
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
@Override
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}