package net.sf.openrocket.util;
import java.io.Serializable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An immutable class of weighted coordinates. The weights are non-negative.
*
* Can also be used as non-weighted coordinates with weight=0.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public final class Coordinate implements Cloneable, Serializable {
private static final Logger log = LoggerFactory.getLogger(Coordinate.class);
// Defined for backwards compatibility after adding clone().
static final long serialVersionUID = 585574649794259293L;
//////// Debug section
/*
* Debugging info. If openrocket.debug.coordinatecount is defined, a line is
* printed every 1000000 instantiations (or as many as defined).
*/
private static final boolean COUNT_DEBUG;
private static final int COUNT_DIFF;
static {
String str = System.getProperty("openrocket.debug.coordinatecount");
int diff = 0;
if (str == null) {
COUNT_DEBUG = false;
COUNT_DIFF = 0;
} else {
COUNT_DEBUG = true;
try {
diff = Integer.parseInt(str);
} catch (NumberFormatException ignore) {
}
if (diff < 1000)
diff = 1000000;
COUNT_DIFF = diff;
}
}
private static int count = 0;
{
// Debug count
if (COUNT_DEBUG) {
synchronized (Coordinate.class) {
count++;
if ((count % COUNT_DIFF) == 0) {
log.debug("Coordinate instantiated " + count + " times.");
}
}
}
}
//////// End debug section
public static final Coordinate NUL = new Coordinate(0, 0, 0, 0);
public static final Coordinate NaN = new Coordinate(Double.NaN, Double.NaN,
Double.NaN, Double.NaN);
public final double x, y, z;
public final double weight;
private double length = -1; /* Cached when calculated */
public Coordinate() {
this(0, 0, 0, 0);
}
public Coordinate(double x) {
this(x, 0, 0, 0);
}
public Coordinate(double x, double y) {
this(x, y, 0, 0);
}
public Coordinate(double x, double y, double z) {
this(x, y, z, 0);
}
public Coordinate(double x, double y, double z, double w) {
this.x = x;
this.y = y;
this.z = z;
this.weight = w;
}
public boolean isWeighted() {
return (weight != 0);
}
/**
* Check whether any of the coordinate values is NaN.
*
* @return true if the x, y, z or weight is NaN
*/
public boolean isNaN() {
return Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z) || Double.isNaN(weight);
}
public Coordinate setX(double x) {
return new Coordinate(x, this.y, this.z, this.weight);
}
public Coordinate setY(double y) {
return new Coordinate(this.x, y, this.z, this.weight);
}
public Coordinate setZ(double z) {
return new Coordinate(this.x, this.y, z, this.weight);
}
public Coordinate setWeight(double weight) {
return new Coordinate(this.x, this.y, this.z, weight);
}
public Coordinate setXYZ(Coordinate c) {
return new Coordinate(c.x, c.y, c.z, this.weight);
}
/**
* Add the coordinate and weight of two coordinates.
*
* @param other the other <code>Coordinate</code>
* @return the sum of the coordinates
*/
public Coordinate add(Coordinate other) {
return new Coordinate(this.x + other.x, this.y + other.y, this.z + other.z,
this.weight + other.weight);
}
public Coordinate add(double x1, double y1, double z1) {
return new Coordinate(this.x + x1, this.y + y1, this.z + z1, this.weight);
}
public Coordinate add(double x1, double y1, double z1, double w1) {
return new Coordinate(this.x + x1, this.y + y1, this.z + z1, this.weight + w1);
}
/**
* Subtract a Coordinate from this Coordinate. The weight of the resulting Coordinate
* is the same as of this Coordinate, the weight of the argument is ignored.
*
* @param other Coordinate to subtract from this.
* @return The result
*/
public Coordinate sub(Coordinate other) {
return new Coordinate(this.x - other.x, this.y - other.y, this.z - other.z, this.weight);
}
/**
* Subtract the specified values from this Coordinate. The weight of the result
* is the same as the weight of this Coordinate.
*
* @param x1 x value to subtract
* @param y1 y value to subtract
* @param z1 z value to subtract
* @return the result.
*/
public Coordinate sub(double x1, double y1, double z1) {
return new Coordinate(this.x - x1, this.y - y1, this.z - z1, this.weight);
}
/**
* Multiply the <code>Coordinate</code> with a scalar. All coordinates and the
* weight are multiplied by the given scalar.
* @param m Factor to multiply by.
* @return The product.
*/
public Coordinate multiply(double m) {
return new Coordinate(this.x * m, this.y * m, this.z * m, this.weight * m);
}
/**
* Dot product of two Coordinates, taken as vectors. Equal to
* x1*x2+y1*y2+z1*z2
* @param other Coordinate to take product with.
* @return The dot product.
*/
public double dot(Coordinate other) {
return this.x * other.x + this.y * other.y + this.z * other.z;
}
/**
* Dot product of two Coordinates.
*/
public static double dot(Coordinate v1, Coordinate v2) {
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}
/**
* Cross product of two Coordinates taken as vectors
*/
public Coordinate cross(Coordinate other) {
return cross(this, other);
}
/**
* Cross product of two Coordinates taken as vectors
*/
public static Coordinate cross(Coordinate a, Coordinate b) {
return new Coordinate(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
}
/**
* Distance from the origin to the Coordinate.
*/
public double length() {
if (length < 0) {
length = MathUtil.safeSqrt(x * x + y * y + z * z);
}
return length;
}
/**
* Square of the distance from the origin to the Coordinate.
*/
public double length2() {
return x * x + y * y + z * z;
}
/**
* Return the largest of the absolute values of the coordinates. This can be
* used as a norm of the vector that is faster to calculate than the
* 2-norm.
*
* @return the largest absolute value of (x,y,z)
*/
public double max() {
return MathUtil.max(Math.abs(x), Math.abs(y), Math.abs(z));
}
/**
* Returns a new coordinate which has the same direction from the origin as this
* coordinate but is at a distance of one. If this coordinate is the origin,
* this method throws an <code>IllegalStateException</code>. The weight of the
* coordinate is unchanged.
*
* @return the coordinate normalized to distance one of the origin.
* @throws IllegalStateException if this coordinate is the origin.
*/
public Coordinate normalize() {
double l = length();
if (l < 0.0000001) {
throw new IllegalStateException("Cannot normalize zero coordinate");
}
return new Coordinate(x / l, y / l, z / l, weight);
}
/**
* Weighted average of two coordinates. If either of the weights are positive,
* the result is the weighted average of the coordinates and the weight is the sum
* of the original weights. If the sum of the weights is zero (and especially if
* both of the weights are zero), the result is the unweighted average of the
* coordinates with weight zero.
* <p>
* If <code>other</code> is <code>null</code> then this <code>Coordinate</code> is
* returned.
*/
public Coordinate average(Coordinate other) {
double x1, y1, z1, w1;
if (other == null)
return this;
w1 = this.weight + other.weight;
if (Math.abs(w1) < MathUtil.pow2(MathUtil.EPSILON)) {
x1 = (this.x + other.x) / 2;
y1 = (this.y + other.y) / 2;
z1 = (this.z + other.z) / 2;
w1 = 0;
} else {
x1 = (this.x * this.weight + other.x * other.weight) / w1;
y1 = (this.y * this.weight + other.y * other.weight) / w1;
z1 = (this.z * this.weight + other.z * other.weight) / w1;
}
return new Coordinate(x1, y1, z1, w1);
}
/**
* Tests whether the coordinates are the equal.
*
* @param other Coordinate to compare to.
* @return true if the coordinates are equal (x, y, z and weight)
*/
@Override
public boolean equals(Object other) {
if (!(other instanceof Coordinate))
return false;
final Coordinate c = (Coordinate) other;
return (MathUtil.equals(this.x, c.x) &&
MathUtil.equals(this.y, c.y) &&
MathUtil.equals(this.z, c.z) && MathUtil.equals(this.weight, c.weight));
}
/**
* Hash code method compatible with {@link #equals(Object)}.
*/
@Override
public int hashCode() {
return (int) ((x + y + z) * 100000);
}
@Override
public String toString() {
if (isWeighted())
return String.format("(%.3f,%.3f,%.3f,w=%.3f)", x, y, z, weight);
else
return String.format("(%.3f,%.3f,%.3f)", x, y, z);
}
@Override
public Coordinate clone() {
return new Coordinate(this.x, this.y, this.z, this.weight);
}
}