/*
* Copyright (C) 2011 Jason von Nieda <jason@vonnieda.org>
*
* This file is part of OpenPnP.
*
* OpenPnP 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.
*
* OpenPnP 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 OpenPnP. If not, see
* <http://www.gnu.org/licenses/>.
*
* For more information about OpenPnP visit http://openpnp.org
*/
package org.openpnp.model;
import java.util.Locale;
import org.simpleframework.xml.Attribute;
/**
* A Location is a an immutable 3D point in X, Y, Z space with a rotation component. The rotation is
* applied about the Z axis.
*/
public class Location {
/*
* The fields on this class would be final in a perfect world, but that doesn't work correctly
* with the XML serialization.
*/
@Attribute
private LengthUnit units;
@Attribute(required = false)
private double x;
@Attribute(required = false)
private double y;
@Attribute(required = false)
private double z;
@Attribute(required = false)
private double rotation;
/**
* Only used by XML serialization.
*/
@SuppressWarnings("unused")
private Location() {
this(null);
}
public Location(LengthUnit units) {
this(units, 0, 0, 0, 0);
}
public Location(LengthUnit units, double x, double y, double z, double rotation) {
this.units = units;
this.x = x;
this.y = y;
this.z = z;
this.rotation = rotation;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getZ() {
return z;
}
public double getRotation() {
return rotation;
}
public LengthUnit getUnits() {
return units;
}
public Location convertToUnits(LengthUnit units) {
Location location =
new Location(units, new Length(x, this.units).convertToUnits(units).getValue(),
new Length(y, this.units).convertToUnits(units).getValue(),
new Length(z, this.units).convertToUnits(units).getValue(), rotation);
return location;
}
public Length getLinearLengthTo(Location location) {
double distance = getLinearDistanceTo(location);
return new Length(distance, getUnits());
}
/**
* Returns the distance between this Location and the specified Location in the units of this
* Location.
*
* @param location
* @return
*/
public double getLinearDistanceTo(Location location) {
location = location.convertToUnits(getUnits());
return getLinearDistanceTo(location.getX(), location.getY());
}
public double getLinearDistanceTo(double x, double y) {
return (Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2)));
}
public double getXyzDistanceTo(Location location) {
location = location.convertToUnits(getUnits());
return (Math.sqrt(Math.pow(this.x - location.getX(), 2)
+ Math.pow(this.y - location.getY(), 2) + Math.pow(this.z - location.getZ(), 2)));
}
public Length getLengthX() {
return new Length(x, units);
}
public Length getLengthY() {
return new Length(y, units);
}
public Length getLengthZ() {
return new Length(z, units);
}
/**
* Returns a new Location with the given Location's X, Y, and Z components subtracted from this
* Location's X, Y, and Z components. Rotation is left unchanged.
*
* @param l
* @return
*/
public Location subtract(Location l) {
l = l.convertToUnits(getUnits());
return new Location(l.getUnits(), x - l.getX(), y - l.getY(), z - l.getZ(), getRotation());
}
/**
* Same as {@link Location#subtract(Location)} but also subtracts rotation.
*
* @param l
* @return
*/
public Location subtractWithRotation(Location l) {
l = l.convertToUnits(getUnits());
return new Location(l.getUnits(), x - l.getX(), y - l.getY(), z - l.getZ(),
rotation - l.getRotation());
}
/**
* Returns a new Location with the given Location's X, Y, and Z components added to this
* Location's X, Y, and Z components. Rotation is left unchanged.
*
* @param l
* @return
*/
public Location add(Location l) {
l = l.convertToUnits(getUnits());
return new Location(l.getUnits(), x + l.getX(), y + l.getY(), z + l.getZ(), rotation);
}
/**
* Returns a new Location with the given Location's X, Y, and Z components added to this
* Location's X, Y, and Z components. Rotation is included.
*
* @param l
* @return
*/
public Location addWithRotation(Location l) {
l = l.convertToUnits(getUnits());
return new Location(l.getUnits(), x + l.getX(), y + l.getY(), z + l.getZ(),
rotation + l.getRotation());
}
/**
* Returns a new Location with the given Location's X, Y and Z components multiplied by this
* Location's X, Y and Z components. Rotation is left unchanged.
*
* @param l
* @return
*/
public Location multiply(Location l) {
l = l.convertToUnits(getUnits());
return new Location(l.getUnits(), x * l.getX(), y * l.getY(), z * l.getZ(), getRotation());
}
/**
* Returns a new Location based on this Location with values multiplied by the specified values.
* Units are the same as this Location.
*
* @param x
* @param y
* @param z
* @param rotation
* @return
*/
public Location multiply(double x, double y, double z, double rotation) {
return new Location(getUnits(), x * getX(), y * getY(), z * getZ(),
rotation * getRotation());
}
/**
* Returns a new Location with the same units as this one and with any of fields specified as
* true inverted from the values of this one. Specifically, if one of the x, y, z or rotation
* fields are specified true in the method call, that field will be multipled by -1 in the
* returned Location.
*
* @param x
* @param y
* @param z
* @param rotation
* @return
*/
public Location invert(boolean x, boolean y, boolean z, boolean rotation) {
return new Location(getUnits(), getX() * (x ? -1 : 1), getY() * (y ? -1 : 1),
getZ() * (z ? -1 : 1), getRotation() * (rotation ? -1 : 1));
}
/**
* Returns a new Location with the same units as this one but with values updated to the passed
* in values. A caveat is that if a specified value is null, the new Location will contain the
* value from this object instead of the new value.
*
* This is intended as a utility method, useful for creating new Locations based on existing
* ones with one or more values changed.
*
* @param x
* @param y
* @param z
* @param rotation
* @return
*/
public Location derive(Double x, Double y, Double z, Double rotation) {
return new Location(units, x == null ? this.x : x, y == null ? this.y : y,
z == null ? this.z : z, rotation == null ? this.rotation : rotation);
}
/**
* Returns a new Location with this Location's X and Y rotated by angle. Z and Rotation are
* unchanged.
*
* @param angle
* @return
*/
public Location rotateXy(double angle) {
if (angle == 0.0) {
return this;
}
while (angle < 180.) {
angle += 360;
}
while (angle > 180.) {
angle -= 360;
}
angle = Math.toRadians(angle);
return new Location(getUnits(), getX() * Math.cos(angle) - getY() * Math.sin(angle),
getX() * Math.sin(angle) + getY() * Math.cos(angle), getZ(), getRotation());
}
public Location rotateXyCenterPoint(Location center, double angle) {
Location location = this.subtract(center);
location = location.rotateXy(angle);
location = location.add(center);
return location;
}
@Override
public String toString() {
return String.format(Locale.US, "(%f, %f, %f, %f %s)", x, y, z, rotation,
units.getShortName());
}
public Point getXyPoint() {
return new Point(getX(), getY());
}
/**
* Performs a unit agnostic equality check. If the Object being tested is a Location in a
* different unit, it is first converted to the units of this Location and then each value field
* is compared.
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Location)) {
return false;
}
Location that = (Location) obj;
that = that.convertToUnits(this.units);
return this.units == that.units && this.x == that.x && this.y == that.y && this.z == that.z
&& this.rotation == that.rotation;
}
@Override
public int hashCode() {
int result;
long temp;
result = this.units != null ? this.units.hashCode() : 0;
temp = Double.doubleToLongBits(this.x);
result = 31 * result + (int) (temp ^ temp >>> 32);
temp = Double.doubleToLongBits(this.y);
result = 31 * result + (int) (temp ^ temp >>> 32);
temp = Double.doubleToLongBits(this.z);
result = 31 * result + (int) (temp ^ temp >>> 32);
temp = Double.doubleToLongBits(this.rotation);
result = 31 * result + (int) (temp ^ temp >>> 32);
return result;
}
}