/*
* Copyright 2009 Hao Nguyen
*
* 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 gwt.g2d.shared.math;
import static gwt.g2d.client.math.MathHelper.square;
import gwt.g2d.client.math.MathHelper;
import java.io.Serializable;
import java.util.Arrays;
import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
/**
* Represents a 2D point or vector.
*
* @author hao1300@gmail.com
*/
public class Vector2 implements Serializable {
/** A vector at (0, 0). */
public static final Vector2 ZERO = new ImmutableVector2(0.0, 0.0);
/** A vector at (1, 1). */
public static final Vector2 ONE = new ImmutableVector2(1.0, 1.0);
/** A vector at (1, 0). */
public static final Vector2 UNIT_X = new ImmutableVector2(1.0, 0.0);
/** A vector at (0, 1). */
public static final Vector2 UNIT_Y = new ImmutableVector2(0.0, 1.0);
/** Any value below this is considered zero. */
public static final double EPS = 0.0000001;
private static final long serialVersionUID = 5543191998324226983L;
public double x, y;
/**
* Creates a default vector2 at (0, 0).
* Uses Vector2.ZERO to represents (0, 0) instead.
*/
public Vector2() {
}
public Vector2(Vector2 rhs) {
this.x = rhs.getX();
this.y = rhs.getY();
}
public Vector2(double x, double y) {
this.x = x;
this.y = y;
}
/**
* Creates a vector at (value, value).
*/
public Vector2(double value) {
this(value, value);
}
/**
* Catmull-rom interpolation between two vectors.
*
* @param value1
* @param value2
* @param value3
* @param value4
* @param amount the amount to interpolate [0.0, 1.0].
* @return a new interpolated vector.
*/
public static final Vector2 catmullRom(Vector2 value1, Vector2 value2, Vector2 value3,
Vector2 value4, double amount) {
return new Vector2().mutableCatmullRom(value1, value2, value3, value4, amount);
}
/**
* Hermite interpolation between two vectors.
*
* @param value1
* @param tangent1
* @param value2
* @param tangent2
* @param amount the amount to interpolate [0.0, 1.0].
* @return a new interpolated vector.
*/
public static final Vector2 hermite(Vector2 value1, Vector2 tangent1, Vector2 value2,
Vector2 tangent2, double amount) {
return new Vector2().mutableHermite(value1, tangent1, value2, tangent2, amount);
}
/**
* Linear interpolation between two vectors.
*
* @param value1
* @param value2
* @param amount the amount to interpolate [0.0, 1.0].
* @return a new interpolated vector.
*/
public static final Vector2 lerp(Vector2 value1, Vector2 value2, double amount) {
return new Vector2().mutableLerp(value1, value2, amount);
}
/**
* Creates a vector within the given radius.
*
* @param radius
* @return a random vector that lies within a circle with (0, 0) as the
* origin and the radius as given.
*/
public static final Vector2 random(int radius) {
Vector2 vector = new Vector2(Math.random(), Math.random());
double radiusSquared = MathHelper.square(radius);
while (vector.lengthSquared() > radiusSquared) {
vector.set(Math.random(), Math.random());
}
return vector;
}
/**
* Creates a random vector2 inside the given range.
*
* @param maxX
* @param maxY
* @return a random vector whose x value is in [0, maxX), and y value is
* in [0, maxY)
*/
public static final Vector2 random(double maxX, double maxY) {
return new Vector2(Math.random() * maxX, Math.random() * maxY);
}
/**
* Creates a random vector2 inside the given range.
*
* @param minX
* @param minY
* @param maxX
* @param maxY
* @return a random vector whose x value is in [minX, maxX), and y value is
* in [minY, maxY)
*/
public static final Vector2 random(double minX, double minY, double maxX,
double maxY) {
return new Vector2(Math.random() * (maxX - minX) + minX,
Math.random() * (maxY - minY) + minY);
}
/**
* Creates a vector inside the given rectangle.
*
* @param rectangle
* @return a new vector that lies inside the given rectangle.
*/
public static final Vector2 random(Rectangle rectangle) {
return new Vector2(Math.random() * rectangle.getWidth() + rectangle.getX(),
Math.random() * rectangle.getHeight() + rectangle.getY());
}
/**
* Creates a normalized vector.
*
* @return a random normalized vector.
*/
public static final Vector2 randomNormalize() {
Vector2 vector = random(1);
while (vector.getX() == 0 && vector.getY() == 0) {
vector = random(1);
}
return vector;
}
/**
* Smooth interpolation between two vectors.
*
* @param value1
* @param value2
* @param amount the amount to interpolate [0.0, 1.0].
* @return a new interpolated vector.
*/
public static final Vector2 smoothStep(Vector2 value1, Vector2 value2,
double amount) {
return new Vector2().mutableSmoothStep(value1, value2, amount);
}
/**
* Gets the x-coordinate.
*/
public final double getX() {
return x;
}
/**
* Gets the x-coordinate as an integer.
*/
public final int getIntX() {
return (int) getX();
}
/**
* Gets the y-coordinate.
*/
public final double getY() {
return y;
}
/**
* Gets the y-coordinate as an integer.
*/
public final int getIntY() {
return (int) getY();
}
/**
* Sets the x-coordinate.
*/
public void setX(double x) {
this.x = x;
}
/**
* Sets the y-coordinate.
*/
public void setY(double y) {
this.y = y;
}
/**
* Sets the x and y coordinates of the vector.
*
* @param x
* @param y
*/
public final void set(double x, double y) {
this.x = x;
this.y = y;
}
/**
* Calculates whether this vector is zero (using machine epsilon so that very small vectors are also counted as zero.
*
* @return whether this vector is zero.
*/
public final boolean isZero() {
return this.lengthSquared() < EPS*EPS;
}
/**
* Gets (this.x + rhs.x, this.y + rhs.y).
*
* @param rhs the vector to add.
* @return a new vector that is this + rhs.
*/
public final Vector2 add(Vector2 rhs) {
return new Vector2(getX() + rhs.getX(), getY() + rhs.getY());
}
/**
* Gets (this.x + x, this.y + y).
*
* @param x the x coordinate
* @param y the y coordinate
* @return a new vector that is this + rhs.
*/
public final Vector2 add(double x, double y) {
return new Vector2(getX() + x, getY() + y);
}
/**
* Adds rhs to this.
* Unlike {@link #add(Vector2)}, the returned vector is this, so no new
* vector is allocated.
*
* @param rhs the vector to add.
* @return self to support chaining.
*/
public final Vector2 mutableAdd(Vector2 rhs) {
this.x += rhs.x;
this.y += rhs.y;
return this;
}
/**
* Adds rhs to this.
* Unlike {@link #add(Vector2)}, the returned vector is this, so no new
* vector is allocated.
*
* @param rhs the vector to add.
* @return self to support chaining.
*/
public final Vector2 mutableAdd(double x, double y) {
this.x += x;
this.y += y;
return this;
}
/**
* Gets (this.x - rhs.x, this.y - rhs.y).
*
* @param rhs the vector to subtract from this.
* @return a new vector that is this - rhs.
*/
public final Vector2 subtract(Vector2 rhs) {
return new Vector2(this.x - rhs.x, this.y - rhs.y);
}
/**
* Gets (this.x - rhs.x, this.y - rhs.y).
*
* @param rhs the vector to subtract from this.
* @return a new vector that is this - rhs.
*/
public final Vector2 subtract(double x, double y) {
return new Vector2(this.x - x, this.y - y);
}
/**
* Subtract rhs from this.
* Unlike {@link #subtract(Vector2)}, the returned vector is this, so no new
* vector is allocated.
*
* @param rhs the vector to subtract from this.
* @return self to support chaining.
*/
public final Vector2 mutableSubtract(Vector2 rhs) {
this.x -= rhs.x;
this.y -= rhs.y;
return this;
}
/**
* Subtract rhs from this.
* Unlike {@link #subtract(Vector2)}, the returned vector is this, so no new
* vector is allocated.
*
* @param rhs the vector to subtract from this.
* @return self to support chaining.
*/
public final Vector2 mutableSubtract(double x, double y) {
this.x -= x;
this.y -= y;
return this;
}
/**
* Gets (this.x * rhs.x, this.y * rhs.y).
*
* @param rhs the vector to multiply.
* @return a new vector that is this * rhs.
*/
public final Vector2 multiply(Vector2 rhs) {
return new Vector2(this.x * rhs.x, this.y * rhs.y);
}
/**
* Multiply this by rhs.
* Unlike {@link #multiply(Vector2)}, the returned vector is this, so no new
* vector is allocated.
*
* @param rhs the vector to multiply.
* @return self to support chaining.
*/
public final Vector2 mutableMultiply(Vector2 rhs) {
this.x *= rhs.x;
this.y *= rhs.y;
return this;
}
/**
* Gets (this.x * rhs.x, this.y * rhs.y).
*
* @param rhs the vector to multiply.
* @return a new vector that is this * rhs.
*/
public final Vector2 multiply(double x, double y) {
return new Vector2(this.x * x, this.y * y);
}
/**
* Multiply this by rhs.
* Unlike {@link #multiply(Vector2)}, the returned vector is this, so no new
* vector is allocated.
*
* @param rhs the vector to multiply.
* @return self to support chaining.
*/
public final Vector2 mutableMultiply(double x, double y) {
this.x *= x;
this.y *= y;
return this;
}
/**
* Gets (this.x / rhs.x, this.y / rhs.y).
*
* @param rhs the vector by which this is to be divided.
* @return a new vector that is this / rhs.
*/
public final Vector2 divide(Vector2 rhs) {
return new Vector2(this.x / rhs.x, this.y / rhs.y);
}
/**
* Divide this by rhs.
* Unlike {@link #divide(Vector2)}, the returned vector is this, so no new
* vector is allocated.
*
* @param rhs the vector by which this is to be divided.
* @return self to support chaining.
*/
public final Vector2 mutableDivide(Vector2 rhs) {
this.x /= rhs.x;
this.y /= rhs.y;
return this;
}
/**
* Gets (this.x / rhs.x, this.y / rhs.y).
*
* @param rhs the vector by which this is to be divided.
* @return a new vector that is this / rhs.
*/
public final Vector2 divide(double x, double y) {
return new Vector2(this.x / x, this.y / y);
}
/**
* Divide this by rhs.
* Unlike {@link #divide(Vector2)}, the returned vector is this, so no new
* vector is allocated.
*
* @param rhs the vector by which this is to be divided.
* @return self to support chaining.
*/
public final Vector2 mutableDivide(double x, double y) {
this.x /= x;
this.y /= y;
return this;
}
/**
* Clamp this vector between min and max, that is, the new vector's x is
* between min's x and max's x, and its y is between min's y and max's y.
*
* @param min
* @param max
* @return a new vector that is inside [min, max]
*/
public final Vector2 clamp(Vector2 min, Vector2 max) {
return new Vector2(
MathHelper.clamp(getX(), min.getX(), max.getX()),
MathHelper.clamp(getY(), min.getY(), max.getY()));
}
/**
* Similar to clamp except the vector returned is this vector whose x and y
* have been clamped to between min and max.
*
* @param min
* @param max
* @return self to support chaining.
*/
public final Vector2 mutatableClamp(Vector2 min, Vector2 max) {
setX(MathHelper.clamp(getX(), min.getX(), max.getX()));
setY(MathHelper.clamp(getY(), min.getY(), max.getY()));
return this;
}
/**
* Gets the dot product for this and rhs.
*
* @param rhs
* @return the dot product of this and rhs.
*/
public final double dot(Vector2 rhs) {
return getX() * rhs.getX() + getY() * rhs.getY();
}
/**
* Gets the distance squared from this to rhs.
*
* @param rhs
* @return the distance squared from this to rhs.
*/
public final double distanceSquared(Vector2 rhs) {
return square(getX() - rhs.getX()) + square(getY() - rhs.getY());
}
/**
* Gets the distance from this to rhs.
*
* @param rhs
* @return the distance from this to rhs.
*/
public final double distance(Vector2 rhs) {
return Math.sqrt(distanceSquared(rhs));
}
/**
* Gets the length of this vector squared.
*
* @return the length of this vector squared.
*/
public final double lengthSquared() {
return square(getX()) + square(getY());
}
/**
* Gets the length of this vector.
*
* @return the length of this vector.
*/
public final double length() {
return Math.sqrt(lengthSquared());
}
/**
* Gets a vector whose x is the max of this's x and rhs's x, and y is the
* max of this'y and rhs's y.
*
* @param rhs
* @return a new vector whose x and y values are the max of this and rhs.
*/
public final Vector2 max(Vector2 rhs) {
return new Vector2(
Math.max(getX(), rhs.getX()),
Math.max(getY(), rhs.getY()));
}
/**
* Similar to max() except the vector returned is this vector whose x and y
* values have been set to the result of max().
*
* @param rhs
* @return self to support chaining.
*/
public final Vector2 mutableMax(Vector2 rhs) {
setX(Math.max(getX(), rhs.getX()));
setY(Math.max(getY(), rhs.getY()));
return this;
}
/**
* Gets a vector whose x is the min of this's x and rhs's x, and y is the
* min of this'y and rhs's y.
*
* @param rhs
* @return a new vector whose x and y values are the min of this and rhs.
*/
public final Vector2 min(Vector2 rhs) {
return new Vector2(
Math.min(getX(), rhs.getX()),
Math.min(getY(), rhs.getY()));
}
/**
* Similar to min() except the vector returned is this vector whose x and y
* values have been set to the result of min().
*
* @param rhs
* @return self to support chaining.
*/
public final Vector2 mutableMin(Vector2 rhs) {
setX(Math.min(getX(), rhs.getX()));
setY(Math.min(getY(), rhs.getY()));
return this;
}
/**
* Gets the negate of this vector.
*
* @return a new vector whose x and y values have the opposite sign of this
* vector's x and y values.
*/
public final Vector2 negate() {
return new Vector2(-getX(), -getY());
}
/**
* Similar to negate() except the vector returned is this vector whose x and
* y values have been set to be the result of negate().
*
* @return self to support chaining.
*/
public final Vector2 mutableNegate() {
setX(-getX());
setY(-getY());
return this;
}
/**
* Gets the normal of this vector.
*
* @return a new vector that is the unit vector of this vector.
*/
public final Vector2 normalize() {
return this.scale(1.0 / length());
}
/**
* Similar to normalize() except the vector returned is this vector whose
* x and y values have been set to be the result of normalize().
*
* @return self to support chaining.
*/
public final Vector2 mutableNormalize() {
return this.mutableScale(1.0 / length());
}
/**
* Gets (this.x * rhs, this.y * rhs).
*
* @param rhs the value to scale this vector by.
* @return a new vector that is this * rhs.
*/
public final Vector2 scale(double rhs) {
return new Vector2(getX() * rhs, getY() * rhs);
}
/**
* Scale this by rhs.
* Unlike scale(), the returned vector is this, so no new vector is allocated.
*
* @param rhs the value to scale this vector by.
* @return self to support chaining.
*/
public final Vector2 mutableScale(double rhs) {
setX(getX() * rhs);
setY(getY() * rhs);
return this;
}
/**
* Similar to catmullRom() except the vector returned is this vector whose
* x and y have been set to the result of catmullRom().
*
* @param value1
* @param value2
* @param value3
* @param value4
* @param amount the amount to interpolate [0.0, 1.0].
* @return self to support chaining.
*/
public final Vector2 mutableCatmullRom(Vector2 value1, Vector2 value2,
Vector2 value3, Vector2 value4, double amount) {
setX(MathHelper.catmullRom(value1.getX(), value2.getX(), value3.getX(),
value4.getX(), amount));
setY(MathHelper.catmullRom(value1.getY(), value2.getY(), value3.getY(),
value4.getY(), amount));
return this;
}
/**
* Similar to hermit() except the vector returned is this vector whose
* x and y have been set to the result of hermite().
*
* @param value1
* @param tangent1
* @param value2
* @param tangent2
* @param amount the amount to interpolate [0.0, 1.0].
* @return self to support chaining.
*/
public final Vector2 mutableHermite(Vector2 value1, Vector2 tangent1,
Vector2 value2, Vector2 tangent2, double amount) {
setX(MathHelper.hermite(value1.getX(), tangent1.getX(), value2.getX(),
tangent2.getX(), amount));
setY(MathHelper.hermite(value1.getY(), tangent1.getY(), value2.getY(),
tangent2.getY(), amount));
return this;
}
/**
* Similar to lerp() except the vector returned is this vector whose
* x and y have been set to the result of lerp().
*
* @param value1
* @param value2
* @param amount the amount to interpolate [0.0, 1.0].
* @return self to support chaining.
*/
public final Vector2 mutableLerp(Vector2 value1, Vector2 value2, double amount) {
setX(MathHelper.lerp(value1.getX(), value2.getX(), amount));
setY(MathHelper.lerp(value1.getY(), value2.getY(), amount));
return this;
}
/**
* Similar to smoothStep() except the vector returned is this vector whose
* x and y have been set to the result of smoothStep().
*
* @param value1
* @param value2
* @param amount the amount to interpolate [0.0, 1.0].
* @return a new interpolated vector.
*/
public final Vector2 mutableSmoothStep(Vector2 value1, Vector2 value2, double amount) {
setX(MathHelper.smoothStep(value1.getX(), value2.getX(), amount));
setY(MathHelper.smoothStep(value1.getY(), value2.getY(), amount));
return this;
}
@Override
public final boolean equals(Object obj) {
return (obj instanceof Vector2) ? equals((Vector2) obj) : false;
}
public final boolean equals(Vector2 rhs) {
return (this.x - rhs.x) < EPS && (this.y - rhs.y) < EPS;
}
@Override
public final int hashCode() {
return Arrays.hashCode(new double[]{getX(), getY()});
}
@Override
public final String toString() {
return "[" + this.x + ", " + this.y + "]";
}
// regular expression for parsing a vector
private static RegExp Pattern = RegExp.compile("\\[\\s*([0-9]*[\\.]?[0-9]+)\\s*,\\s*([0-9]*[\\.]?[0-9]+)\\s*\\]");
// parse from String
public static Vector2 parseVector2(String s) {
MatchResult res = Pattern.exec(s);
if (res == null) return null;
return new Vector2(Double.parseDouble(res.getGroup(1)), Double.parseDouble(res.getGroup(2)));
}
// clone a vector
public final Vector2 copy() {
return new Vector2(this.x, this.y);
}
/**
* An unmodifiable vector2.
*/
private static class ImmutableVector2 extends Vector2 {
private static final long serialVersionUID = -4931305479279295158L;
private static final String MODIFICATION_ERROR_MESSAGE =
"Cannot modify an immutable vector";
public ImmutableVector2(double x, double y) {
super(x, y);
}
@Override
public void setX(double x) {
throw new UnsupportedOperationException(MODIFICATION_ERROR_MESSAGE);
}
@Override
public void setY(double y) {
throw new UnsupportedOperationException(MODIFICATION_ERROR_MESSAGE);
}
}
}