1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.geometry.euclidean.threed;
18
19 import java.util.Objects;
20
21 import org.apache.commons.geometry.core.Transform;
22 import org.apache.commons.geometry.core.partitioning.EmbeddingHyperplane;
23 import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
24 import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D;
25 import org.apache.commons.geometry.euclidean.twod.Vector2D;
26 import org.apache.commons.numbers.core.Precision;
27
28 /** Extension of the {@link Plane} class that supports embedding of 2D subspaces in the plane.
29 * This is accomplished by defining two additional vectors, {@link #getU() u} and {@link #getV() v},
30 * that define the {@code x} and {@code y} axes respectively of the embedded subspace. For completeness,
31 * an additional vector {@link #getW()} is defined, which is simply an alias for the plane normal.
32 * Together, the vectors {@code u}, {@code v}, and {@code w} form a right-handed orthonormal basis.
33 *
34 * <p>The additional {@code u} and {@code v} vectors are not required to fulfill the contract of
35 * {@link org.apache.commons.geometry.core.partitioning.Hyperplane Hyperplane}. Therefore, they
36 * are not considered when using instances of this type purely as a hyperplane. For example, the
37 * {@link Plane#eq(Plane, Precision.DoubleEquivalence) eq} and
38 * {@link Plane#similarOrientation(org.apache.commons.geometry.core.partitioning.Hyperplane) similiarOrientation}
39 * methods do not consider them.</p>
40 */
41 public final class EmbeddingPlane extends Plane implements EmbeddingHyperplane<Vector3D, Vector2D> {
42 /** First normalized vector of the plane frame (in plane). */
43 private final Vector3D.Unit u;
44
45 /** Second normalized vector of the plane frame (in plane). */
46 private final Vector3D.Unit v;
47
48 /** Construct a new instance from an orthonormal set of basis vectors and an origin offset.
49 * @param u first vector of the basis (in plane)
50 * @param v second vector of the basis (in plane)
51 * @param w third vector of the basis (plane normal)
52 * @param originOffset offset of the origin with respect to the plane.
53 * @param precision precision context used for floating point comparisons
54 */
55 EmbeddingPlane(final Vector3D.Unit u, final Vector3D.Unit v, final Vector3D.Unit w, final double originOffset,
56 final Precision.DoubleEquivalence precision) {
57 super(w, originOffset, precision);
58
59 this.u = u;
60 this.v = v;
61 }
62
63 /** Get the plane first canonical vector.
64 * <p>
65 * The frame defined by ({@link #getU u}, {@link #getV v},
66 * {@link #getW w}) is a right-handed orthonormalized frame).
67 * </p>
68 * @return normalized first canonical vector
69 * @see #getV
70 * @see #getW
71 * @see #getNormal
72 */
73 public Vector3D.Unit getU() {
74 return u;
75 }
76
77 /** Get the plane second canonical vector.
78 * <p>
79 * The frame defined by ({@link #getU u}, {@link #getV v},
80 * {@link #getW w}) is a right-handed orthonormalized frame).
81 * </p>
82 * @return normalized second canonical vector
83 * @see #getU
84 * @see #getW
85 * @see #getNormal
86 */
87 public Vector3D.Unit getV() {
88 return v;
89 }
90
91 /** Get the plane third canonical vector, ie, the plane normal. This
92 * method is simply an alias for {@link #getNormal()}.
93 * <p>
94 * The frame defined by {@link #getU() u}, {@link #getV() v},
95 * {@link #getW() w} is a right-handed orthonormalized frame.
96 * </p>
97 * @return normalized normal vector
98 * @see #getU()
99 * @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 }