001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.geometry.euclidean.twod.rotation; 018 019import org.apache.commons.geometry.euclidean.EuclideanTransform; 020import org.apache.commons.geometry.euclidean.internal.Vectors; 021import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D; 022import org.apache.commons.geometry.euclidean.twod.Vector2D; 023 024/** Class representing a rotation in 2 dimensional Euclidean space. Positive 025 * rotations are in a <em>counter-clockwise</em> direction. 026 */ 027public final class Rotation2D implements EuclideanTransform<Vector2D> { 028 029 /** Instance representing a rotation of zero radians. */ 030 private static final Rotation2D IDENTITY = new Rotation2D(0); 031 032 /** The angle of the rotation in radians. */ 033 private final double angle; 034 035 /** The cosine of the angle of rotation, cached to avoid repeated computation. */ 036 private final double cosAngle; 037 038 /** The sine of the angle of rotation, cached to avoid repeated computation. */ 039 private final double sinAngle; 040 041 /** Create a new instance representing the given angle. 042 * @param angle the angle of rotation, in radians 043 */ 044 private Rotation2D(final double angle) { 045 this.angle = angle; 046 this.cosAngle = Math.cos(angle); 047 this.sinAngle = Math.sin(angle); 048 } 049 050 /** Get the angle of rotation in radians. 051 * @return the angle of rotation in radians 052 */ 053 public double getAngle() { 054 return angle; 055 } 056 057 /** {@inheritDoc} */ 058 @Override 059 public Rotation2D inverse() { 060 return new Rotation2D(-angle); 061 } 062 063 /** {@inheritDoc} 064 * 065 * <p>This method simply returns true since rotations always preserve the orientation 066 * of the space.</p> 067 */ 068 @Override 069 public boolean preservesOrientation() { 070 return true; 071 } 072 073 /** {@inheritDoc} */ 074 @Override 075 public Vector2D apply(final Vector2D pt) { 076 final double x = pt.getX(); 077 final double y = pt.getY(); 078 079 return Vector2D.of( 080 (x * cosAngle) - (y * sinAngle), 081 (x * sinAngle) + (y * cosAngle) 082 ); 083 } 084 085 /** {@inheritDoc} 086 * 087 * <p>This method simply calls {@code apply(vec)} since rotations treat 088 * points and vectors similarly.</p> 089 * */ 090 @Override 091 public Vector2D applyVector(final Vector2D vec) { 092 return apply(vec); 093 } 094 095 /** Return an {@link AffineTransformMatrix2D} representing the same rotation 096 * as this instance. 097 * @return a transform matrix representing the same rotation 098 */ 099 public AffineTransformMatrix2D toMatrix() { 100 return AffineTransformMatrix2D.of( 101 cosAngle, -sinAngle, 0.0, 102 sinAngle, cosAngle, 0.0 103 ); 104 } 105 106 /** {@inheritDoc} */ 107 @Override 108 public int hashCode() { 109 return Double.hashCode(angle); 110 } 111 112 /** {@inheritDoc} */ 113 @Override 114 public boolean equals(final Object obj) { 115 if (this == obj) { 116 return true; 117 } 118 if (!(obj instanceof Rotation2D)) { 119 return false; 120 } 121 122 final Rotation2D other = (Rotation2D) obj; 123 124 return Double.compare(this.angle, other.angle) == 0; 125 } 126 127 /** {@inheritDoc} */ 128 @Override 129 public String toString() { 130 final StringBuilder sb = new StringBuilder(); 131 sb.append(this.getClass().getSimpleName()) 132 .append("[angle=") 133 .append(angle) 134 .append(']'); 135 136 return sb.toString(); 137 } 138 139 /** Create a new instance with the given angle of rotation. 140 * @param angle the angle of rotation in radians 141 * @return a new instance with the given angle of rotation 142 */ 143 public static Rotation2D of(final double angle) { 144 return new Rotation2D(angle); 145 } 146 147 /** Return an instance representing the identity rotation, ie a rotation 148 * of zero radians. 149 * @return an instance representing a rotation of zero radians 150 */ 151 public static Rotation2D identity() { 152 return IDENTITY; 153 } 154 155 /** Create a rotation instance that rotates the vector {@code u} to point in the direction of 156 * vector {@code v}. 157 * @param u input vector 158 * @param v target vector 159 * @return a rotation instance that rotates {@code u} to point in the direction of {@code v} 160 * @throws IllegalArgumentException if either vector cannot be normalized 161 */ 162 public static Rotation2D createVectorRotation(final Vector2D u, final Vector2D v) { 163 // make sure that the vectors are real-valued and of non-zero length; we don't 164 // actually need to use the norm value; we just need to check its properties 165 Vectors.checkedNorm(u); 166 Vectors.checkedNorm(v); 167 168 final double uAzimuth = Math.atan2(u.getY(), u.getX()); 169 final double vAzimuth = Math.atan2(v.getY(), v.getX()); 170 171 return of(vAzimuth - uAzimuth); 172 } 173}