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.threed; 018 019import java.util.Objects; 020 021import org.apache.commons.geometry.core.Transform; 022import org.apache.commons.geometry.core.partitioning.EmbeddingHyperplane; 023import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation; 024import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D; 025import org.apache.commons.geometry.euclidean.twod.Vector2D; 026import org.apache.commons.numbers.core.Precision; 027 028/** Extension of the {@link Plane} class that supports embedding of 2D subspaces in the plane. 029 * This is accomplished by defining two additional vectors, {@link #getU() u} and {@link #getV() v}, 030 * that define the {@code x} and {@code y} axes respectively of the embedded subspace. For completeness, 031 * an additional vector {@link #getW()} is defined, which is simply an alias for the plane normal. 032 * Together, the vectors {@code u}, {@code v}, and {@code w} form a right-handed orthonormal basis. 033 * 034 * <p>The additional {@code u} and {@code v} vectors are not required to fulfill the contract of 035 * {@link org.apache.commons.geometry.core.partitioning.Hyperplane Hyperplane}. Therefore, they 036 * are not considered when using instances of this type purely as a hyperplane. For example, the 037 * {@link Plane#eq(Plane, Precision.DoubleEquivalence) eq} and 038 * {@link Plane#similarOrientation(org.apache.commons.geometry.core.partitioning.Hyperplane) similiarOrientation} 039 * methods do not consider them.</p> 040 */ 041public final class EmbeddingPlane extends Plane implements EmbeddingHyperplane<Vector3D, Vector2D> { 042 /** First normalized vector of the plane frame (in plane). */ 043 private final Vector3D.Unit u; 044 045 /** Second normalized vector of the plane frame (in plane). */ 046 private final Vector3D.Unit v; 047 048 /** Construct a new instance from an orthonormal set of basis vectors and an origin offset. 049 * @param u first vector of the basis (in plane) 050 * @param v second vector of the basis (in plane) 051 * @param w third vector of the basis (plane normal) 052 * @param originOffset offset of the origin with respect to the plane. 053 * @param precision precision context used for floating point comparisons 054 */ 055 EmbeddingPlane(final Vector3D.Unit u, final Vector3D.Unit v, final Vector3D.Unit w, final double originOffset, 056 final Precision.DoubleEquivalence precision) { 057 super(w, originOffset, precision); 058 059 this.u = u; 060 this.v = v; 061 } 062 063 /** Get the plane first canonical vector. 064 * <p> 065 * The frame defined by ({@link #getU u}, {@link #getV v}, 066 * {@link #getW w}) is a right-handed orthonormalized frame). 067 * </p> 068 * @return normalized first canonical vector 069 * @see #getV 070 * @see #getW 071 * @see #getNormal 072 */ 073 public Vector3D.Unit getU() { 074 return u; 075 } 076 077 /** Get the plane second canonical vector. 078 * <p> 079 * The frame defined by ({@link #getU u}, {@link #getV v}, 080 * {@link #getW w}) is a right-handed orthonormalized frame). 081 * </p> 082 * @return normalized second canonical vector 083 * @see #getU 084 * @see #getW 085 * @see #getNormal 086 */ 087 public Vector3D.Unit getV() { 088 return v; 089 } 090 091 /** Get the plane third canonical vector, ie, the plane normal. This 092 * method is simply an alias for {@link #getNormal()}. 093 * <p> 094 * The frame defined by {@link #getU() u}, {@link #getV() v}, 095 * {@link #getW() w} is a right-handed orthonormalized frame. 096 * </p> 097 * @return normalized normal vector 098 * @see #getU() 099 * @see #getV() 100 * @see #getNormal() 101 */ 102 public Vector3D.Unit getW() { 103 return getNormal(); 104 } 105 106 /** Return the current instance. 107 */ 108 @Override 109 public EmbeddingPlane getEmbedding() { 110 return this; 111 } 112 113 /** Transform a 3D space point into an in-plane point. 114 * @param point point of the space 115 * @return in-plane point 116 * @see #toSpace 117 */ 118 @Override 119 public Vector2D toSubspace(final Vector3D point) { 120 return Vector2D.of(point.dot(u), point.dot(v)); 121 } 122 123 /** Transform an in-plane point into a 3D space point. 124 * @param point in-plane point 125 * @return 3D space point 126 * @see #toSubspace(Vector3D) 127 */ 128 @Override 129 public Vector3D toSpace(final Vector2D point) { 130 return Vector3D.Sum.create() 131 .addScaled(point.getX(), u) 132 .addScaled(point.getY(), v) 133 .addScaled(-getOriginOffset(), getNormal()).get(); 134 } 135 136 /** Get one point from the 3D-space. 137 * @param inPlane desired in-plane coordinates for the point in the plane 138 * @param offset desired offset for the point 139 * @return one point in the 3D-space, with given coordinates and offset relative 140 * to the plane 141 */ 142 public Vector3D pointAt(final Vector2D inPlane, final double offset) { 143 return Vector3D.Sum.create() 144 .addScaled(inPlane.getX(), u) 145 .addScaled(inPlane.getY(), v) 146 .addScaled(offset - getOriginOffset(), getNormal()).get(); 147 } 148 149 /** Build a new reversed version of this plane, with opposite orientation. 150 * <p> 151 * The new plane frame is chosen in such a way that a 3D point that had 152 * {@code (x, y)} in-plane coordinates and {@code z} offset with respect to the 153 * plane and is unaffected by the change will have {@code (y, x)} in-plane 154 * coordinates and {@code -z} offset with respect to the new plane. This means 155 * that the {@code u} and {@code v} vectors returned by the {@link #getU} and 156 * {@link #getV} methods are exchanged, and the {@code w} vector returned by the 157 * {@link #getNormal} method is reversed. 158 * </p> 159 * @return a new reversed plane 160 */ 161 @Override 162 public EmbeddingPlane reverse() { 163 return new EmbeddingPlane(v, u, getNormal().negate(), -getOriginOffset(), getPrecision()); 164 } 165 166 /** {@inheritDoc} */ 167 @Override 168 public EmbeddingPlane transform(final Transform<Vector3D> transform) { 169 final Vector3D origin = getOrigin(); 170 final Vector3D plusU = origin.add(u); 171 final Vector3D plusV = origin.add(v); 172 173 final Vector3D tOrigin = transform.apply(origin); 174 final Vector3D tPlusU = transform.apply(plusU); 175 final Vector3D tPlusV = transform.apply(plusV); 176 177 final Vector3D.Unit tU = tOrigin.directionTo(tPlusU); 178 final Vector3D.Unit tV = tOrigin.directionTo(tPlusV); 179 final Vector3D.Unit tW = tU.cross(tV).normalize(); 180 181 final double tOriginOffset = -tOrigin.dot(tW); 182 183 return new EmbeddingPlane(tU, tV, tW, tOriginOffset, getPrecision()); 184 } 185 186 /** Translate the plane by the specified amount. 187 * @param translation translation to apply 188 * @return a new plane 189 */ 190 @Override 191 public EmbeddingPlane translate(final Vector3D translation) { 192 final Vector3D tOrigin = getOrigin().add(translation); 193 194 return Planes.fromPointAndPlaneVectors(tOrigin, u, v, getPrecision()); 195 } 196 197 /** Rotate the plane around the specified point. 198 * @param center rotation center 199 * @param rotation 3-dimensional rotation 200 * @return a new rotated plane 201 */ 202 @Override 203 public EmbeddingPlane rotate(final Vector3D center, final QuaternionRotation rotation) { 204 final Vector3D delta = getOrigin().subtract(center); 205 final Vector3D tOrigin = center.add(rotation.apply(delta)); 206 final Vector3D.Unit tU = rotation.apply(u).normalize(); 207 final Vector3D.Unit tV = rotation.apply(v).normalize(); 208 209 return Planes.fromPointAndPlaneVectors(tOrigin, tU, tV, getPrecision()); 210 } 211 212 /** {@inheritDoc} */ 213 @Override 214 public int hashCode() { 215 return Objects.hash(getNormal(), getOriginOffset(), u, v, getPrecision()); 216 } 217 218 /** {@inheritDoc} */ 219 @Override 220 public boolean equals(final Object obj) { 221 if (this == obj) { 222 return true; 223 } else if (obj == null || obj.getClass() != EmbeddingPlane.class) { 224 return false; 225 } 226 227 final EmbeddingPlane other = (EmbeddingPlane) obj; 228 229 return Objects.equals(this.getNormal(), other.getNormal()) && 230 Double.compare(this.getOriginOffset(), other.getOriginOffset()) == 0 && 231 Objects.equals(this.u, other.u) && 232 Objects.equals(this.v, other.v) && 233 Objects.equals(this.getPrecision(), other.getPrecision()); 234 } 235 236 /** {@inheritDoc} */ 237 @Override 238 public String toString() { 239 final StringBuilder sb = new StringBuilder(); 240 sb.append(getClass().getSimpleName()) 241 .append("[origin= ") 242 .append(getOrigin()) 243 .append(", u= ") 244 .append(u) 245 .append(", v= ") 246 .append(v) 247 .append(", w= ") 248 .append(getNormal()) 249 .append(']'); 250 251 return sb.toString(); 252 } 253 254 /** Get an object containing the current plane transformed by the argument along with a 255 * 2D transform that can be applied to subspace points. The subspace transform transforms 256 * subspace points such that their 3D location in the transformed plane is the same as their 257 * 3D location in the original plane after the 3D transform is applied. For example, consider 258 * the code below: 259 * <pre> 260 * SubspaceTransform st = plane.subspaceTransform(transform); 261 * 262 * Vector2D subPt = Vector2D.of(1, 1); 263 * 264 * Vector3D a = transform.apply(plane.toSpace(subPt)); // transform in 3D space 265 * Vector3D b = st.getPlane().toSpace(st.getTransform().apply(subPt)); // transform in 2D space 266 * </pre> 267 * At the end of execution, the points {@code a} (which was transformed using the original 268 * 3D transform) and {@code b} (which was transformed in 2D using the subspace transform) 269 * are equivalent. 270 * 271 * @param transform the transform to apply to this instance 272 * @return an object containing the transformed plane along with a transform that can be applied 273 * to subspace points 274 * @see #transform(Transform) 275 */ 276 public SubspaceTransform subspaceTransform(final Transform<Vector3D> transform) { 277 final Vector3D origin = getOrigin(); 278 279 final Vector3D tOrigin = transform.apply(origin); 280 final Vector3D tPlusU = transform.apply(origin.add(u)); 281 final Vector3D tPlusV = transform.apply(origin.add(v)); 282 283 final EmbeddingPlane tPlane = Planes.fromPointAndPlaneVectors( 284 tOrigin, 285 tOrigin.vectorTo(tPlusU), 286 tOrigin.vectorTo(tPlusV), 287 getPrecision()); 288 289 final Vector2D tSubspaceOrigin = tPlane.toSubspace(tOrigin); 290 final Vector2D tSubspaceU = tSubspaceOrigin.vectorTo(tPlane.toSubspace(tPlusU)); 291 final Vector2D tSubspaceV = tSubspaceOrigin.vectorTo(tPlane.toSubspace(tPlusV)); 292 293 final AffineTransformMatrix2D subspaceTransform = 294 AffineTransformMatrix2D.fromColumnVectors(tSubspaceU, tSubspaceV, tSubspaceOrigin); 295 296 return new SubspaceTransform(tPlane, subspaceTransform); 297 } 298 299 /** Class containing a transformed plane instance along with a subspace (2D) transform. The subspace 300 * transform produces the equivalent of the 3D transform in 2D. 301 */ 302 public static final class SubspaceTransform { 303 /** The transformed plane. */ 304 private final EmbeddingPlane plane; 305 306 /** The subspace transform instance. */ 307 private final AffineTransformMatrix2D transform; 308 309 /** Simple constructor. 310 * @param plane the transformed plane 311 * @param transform 2D transform that can be applied to subspace points 312 */ 313 public SubspaceTransform(final EmbeddingPlane plane, final AffineTransformMatrix2D transform) { 314 this.plane = plane; 315 this.transform = transform; 316 } 317 318 /** Get the transformed plane instance. 319 * @return the transformed plane instance 320 */ 321 public EmbeddingPlane getPlane() { 322 return plane; 323 } 324 325 /** Get the 2D transform that can be applied to subspace points. This transform can be used 326 * to perform the equivalent of the 3D transform in 2D space. 327 * @return the subspace transform instance 328 */ 329 public AffineTransformMatrix2D getTransform() { 330 return transform; 331 } 332 } 333}