/* * Copyright (C) 2011 Google Inc. * * 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 org.ros.rosjava_geometry; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import java.util.List; /** * A quaternion. * * @author damonkohler@google.com (Damon Kohler) * @author moesenle@google.com (Lorenz Moesenlechner) */ public class Quaternion { private final double x; private final double y; private final double z; private final double w; public static Quaternion fromAxisAngle(Vector3 axis, double angle) { Vector3 normalized = axis.normalize(); double sin = Math.sin(angle / 2.0d); double cos = Math.cos(angle / 2.0d); return new Quaternion(normalized.getX() * sin, normalized.getY() * sin, normalized.getZ() * sin, cos); } public static Quaternion fromQuaternionMessage(geometry_msgs.Quaternion message) { return new Quaternion(message.getX(), message.getY(), message.getZ(), message.getW()); } public static Quaternion rotationBetweenVectors(Vector3 vector1, Vector3 vector2) { Preconditions.checkArgument(vector1.getMagnitude() > 0, "Cannot calculate rotation between zero-length vectors."); Preconditions.checkArgument(vector2.getMagnitude() > 0, "Cannot calculate rotation between zero-length vectors."); if (vector1.normalize().equals(vector2.normalize())) { return identity(); } double angle = Math.acos(vector1.dotProduct(vector2) / (vector1.getMagnitude() * vector2.getMagnitude())); double axisX = vector1.getY() * vector2.getZ() - vector1.getZ() * vector2.getY(); double axisY = vector1.getZ() * vector2.getX() - vector1.getX() * vector2.getZ(); double axisZ = vector1.getX() * vector2.getY() - vector1.getY() * vector2.getX(); return fromAxisAngle(new Vector3(axisX, axisY, axisZ), angle); } public static Quaternion identity() { return new Quaternion(0, 0, 0, 1); } public Quaternion(double x, double y, double z, double w) { this.x = x; this.y = y; this.z = z; this.w = w; } public Quaternion scale(double factor) { return new Quaternion(x * factor, y * factor, z * factor, w * factor); } public Quaternion conjugate() { return new Quaternion(-x, -y, -z, w); } public Quaternion invert() { double mm = getMagnitudeSquared(); Preconditions.checkState(mm != 0); return conjugate().scale(1 / mm); } public Quaternion normalize() { return scale(1 / getMagnitude()); } public Quaternion multiply(Quaternion other) { return new Quaternion(w * other.x + x * other.w + y * other.z - z * other.y, w * other.y + y * other.w + z * other.x - x * other.z, w * other.z + z * other.w + x * other.y - y * other.x, w * other.w - x * other.x - y * other.y - z * other.z); } public Vector3 rotateAndScaleVector(Vector3 vector) { Quaternion vectorQuaternion = new Quaternion(vector.getX(), vector.getY(), vector.getZ(), 0); Quaternion rotatedQuaternion = multiply(vectorQuaternion.multiply(conjugate())); return new Vector3(rotatedQuaternion.getX(), rotatedQuaternion.getY(), rotatedQuaternion.getZ()); } public double getX() { return x; } public double getY() { return y; } public double getZ() { return z; } public double getW() { return w; } public double getMagnitudeSquared() { return x * x + y * y + z * z + w * w; } public double getMagnitude() { return Math.sqrt(getMagnitudeSquared()); } public boolean isAlmostNeutral(double epsilon) { return Math.abs(1 - x * x - y * y - z * z - w * w) < epsilon; } public geometry_msgs.Quaternion toQuaternionMessage(geometry_msgs.Quaternion result) { result.setX(x); result.setY(y); result.setZ(z); result.setW(w); return result; } public boolean almostEquals(Quaternion other, double epsilon) { List<Double> epsilons = Lists.newArrayList(); epsilons.add(x - other.x); epsilons.add(y - other.y); epsilons.add(z - other.z); epsilons.add(w - other.w); for (double e : epsilons) { if (Math.abs(e) > epsilon) { return false; } } return true; } @Override public String toString() { return String.format("Quaternion<x: %.4f, y: %.4f, z: %.4f, w: %.4f>", x, y, z, w); } @Override public int hashCode() { // Ensure that -0 and 0 are considered equal. double w = this.w == 0 ? 0 : this.w; double x = this.x == 0 ? 0 : this.x; double y = this.y == 0 ? 0 : this.y; double z = this.z == 0 ? 0 : this.z; final int prime = 31; int result = 1; long temp; temp = Double.doubleToLongBits(w); result = prime * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(x); result = prime * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(y); result = prime * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(z); result = prime * result + (int) (temp ^ (temp >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Quaternion other = (Quaternion) obj; // Ensure that -0 and 0 are considered equal. double w = this.w == 0 ? 0 : this.w; double x = this.x == 0 ? 0 : this.x; double y = this.y == 0 ? 0 : this.y; double z = this.z == 0 ? 0 : this.z; double otherW = other.w == 0 ? 0 : other.w; double otherX = other.x == 0 ? 0 : other.x; double otherY = other.y == 0 ? 0 : other.y; double otherZ = other.z == 0 ? 0 : other.z; if (Double.doubleToLongBits(w) != Double.doubleToLongBits(otherW)) return false; if (Double.doubleToLongBits(x) != Double.doubleToLongBits(otherX)) return false; if (Double.doubleToLongBits(y) != Double.doubleToLongBits(otherY)) return false; if (Double.doubleToLongBits(z) != Double.doubleToLongBits(otherZ)) return false; return true; } }