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 */ 017 package org.apache.commons.math3.geometry.euclidean.threed; 018 019 import org.apache.commons.math3.exception.MathArithmeticException; 020 import org.apache.commons.math3.exception.util.LocalizedFormats; 021 import org.apache.commons.math3.geometry.Vector; 022 import org.apache.commons.math3.geometry.euclidean.oned.Vector1D; 023 import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D; 024 import org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet; 025 import org.apache.commons.math3.geometry.euclidean.twod.Vector2D; 026 import org.apache.commons.math3.geometry.partitioning.Embedding; 027 import org.apache.commons.math3.geometry.partitioning.Hyperplane; 028 import org.apache.commons.math3.util.FastMath; 029 030 /** The class represent planes in a three dimensional space. 031 * @version $Id: Plane.java 1416643 2012-12-03 19:37:14Z tn $ 032 * @since 3.0 033 */ 034 public class Plane implements Hyperplane<Euclidean3D>, Embedding<Euclidean3D, Euclidean2D> { 035 036 /** Offset of the origin with respect to the plane. */ 037 private double originOffset; 038 039 /** Origin of the plane frame. */ 040 private Vector3D origin; 041 042 /** First vector of the plane frame (in plane). */ 043 private Vector3D u; 044 045 /** Second vector of the plane frame (in plane). */ 046 private Vector3D v; 047 048 /** Third vector of the plane frame (plane normal). */ 049 private Vector3D w; 050 051 /** Build a plane normal to a given direction and containing the origin. 052 * @param normal normal direction to the plane 053 * @exception MathArithmeticException if the normal norm is too small 054 */ 055 public Plane(final Vector3D normal) throws MathArithmeticException { 056 setNormal(normal); 057 originOffset = 0; 058 setFrame(); 059 } 060 061 /** Build a plane from a point and a normal. 062 * @param p point belonging to the plane 063 * @param normal normal direction to the plane 064 * @exception MathArithmeticException if the normal norm is too small 065 */ 066 public Plane(final Vector3D p, final Vector3D normal) throws MathArithmeticException { 067 setNormal(normal); 068 originOffset = -p.dotProduct(w); 069 setFrame(); 070 } 071 072 /** Build a plane from three points. 073 * <p>The plane is oriented in the direction of 074 * {@code (p2-p1) ^ (p3-p1)}</p> 075 * @param p1 first point belonging to the plane 076 * @param p2 second point belonging to the plane 077 * @param p3 third point belonging to the plane 078 * @exception MathArithmeticException if the points do not constitute a plane 079 */ 080 public Plane(final Vector3D p1, final Vector3D p2, final Vector3D p3) 081 throws MathArithmeticException { 082 this(p1, p2.subtract(p1).crossProduct(p3.subtract(p1))); 083 } 084 085 /** Copy constructor. 086 * <p>The instance created is completely independant of the original 087 * one. A deep copy is used, none of the underlying object are 088 * shared.</p> 089 * @param plane plane to copy 090 */ 091 public Plane(final Plane plane) { 092 originOffset = plane.originOffset; 093 origin = plane.origin; 094 u = plane.u; 095 v = plane.v; 096 w = plane.w; 097 } 098 099 /** Copy the instance. 100 * <p>The instance created is completely independant of the original 101 * one. A deep copy is used, none of the underlying objects are 102 * shared (except for immutable objects).</p> 103 * @return a new hyperplane, copy of the instance 104 */ 105 public Plane copySelf() { 106 return new Plane(this); 107 } 108 109 /** Reset the instance as if built from a point and a normal. 110 * @param p point belonging to the plane 111 * @param normal normal direction to the plane 112 * @exception MathArithmeticException if the normal norm is too small 113 */ 114 public void reset(final Vector3D p, final Vector3D normal) throws MathArithmeticException { 115 setNormal(normal); 116 originOffset = -p.dotProduct(w); 117 setFrame(); 118 } 119 120 /** Reset the instance from another one. 121 * <p>The updated instance is completely independant of the original 122 * one. A deep reset is used none of the underlying object is 123 * shared.</p> 124 * @param original plane to reset from 125 */ 126 public void reset(final Plane original) { 127 originOffset = original.originOffset; 128 origin = original.origin; 129 u = original.u; 130 v = original.v; 131 w = original.w; 132 } 133 134 /** Set the normal vactor. 135 * @param normal normal direction to the plane (will be copied) 136 * @exception MathArithmeticException if the normal norm is too small 137 */ 138 private void setNormal(final Vector3D normal) throws MathArithmeticException { 139 final double norm = normal.getNorm(); 140 if (norm < 1.0e-10) { 141 throw new MathArithmeticException(LocalizedFormats.ZERO_NORM); 142 } 143 w = new Vector3D(1.0 / norm, normal); 144 } 145 146 /** Reset the plane frame. 147 */ 148 private void setFrame() { 149 origin = new Vector3D(-originOffset, w); 150 u = w.orthogonal(); 151 v = Vector3D.crossProduct(w, u); 152 } 153 154 /** Get the origin point of the plane frame. 155 * <p>The point returned is the orthogonal projection of the 156 * 3D-space origin in the plane.</p> 157 * @return the origin point of the plane frame (point closest to the 158 * 3D-space origin) 159 */ 160 public Vector3D getOrigin() { 161 return origin; 162 } 163 164 /** Get the normalized normal vector. 165 * <p>The frame defined by ({@link #getU getU}, {@link #getV getV}, 166 * {@link #getNormal getNormal}) is a rigth-handed orthonormalized 167 * frame).</p> 168 * @return normalized normal vector 169 * @see #getU 170 * @see #getV 171 */ 172 public Vector3D getNormal() { 173 return w; 174 } 175 176 /** Get the plane first canonical vector. 177 * <p>The frame defined by ({@link #getU getU}, {@link #getV getV}, 178 * {@link #getNormal getNormal}) is a rigth-handed orthonormalized 179 * frame).</p> 180 * @return normalized first canonical vector 181 * @see #getV 182 * @see #getNormal 183 */ 184 public Vector3D getU() { 185 return u; 186 } 187 188 /** Get the plane second canonical vector. 189 * <p>The frame defined by ({@link #getU getU}, {@link #getV getV}, 190 * {@link #getNormal getNormal}) is a rigth-handed orthonormalized 191 * frame).</p> 192 * @return normalized second canonical vector 193 * @see #getU 194 * @see #getNormal 195 */ 196 public Vector3D getV() { 197 return v; 198 } 199 200 /** Revert the plane. 201 * <p>Replace the instance by a similar plane with opposite orientation.</p> 202 * <p>The new plane frame is chosen in such a way that a 3D point that had 203 * {@code (x, y)} in-plane coordinates and {@code z} offset with 204 * respect to the plane and is unaffected by the change will have 205 * {@code (y, x)} in-plane coordinates and {@code -z} offset with 206 * respect to the new plane. This means that the {@code u} and {@code v} 207 * vectors returned by the {@link #getU} and {@link #getV} methods are exchanged, 208 * and the {@code w} vector returned by the {@link #getNormal} method is 209 * reversed.</p> 210 */ 211 public void revertSelf() { 212 final Vector3D tmp = u; 213 u = v; 214 v = tmp; 215 w = w.negate(); 216 originOffset = -originOffset; 217 } 218 219 /** Transform a 3D space point into an in-plane point. 220 * @param point point of the space (must be a {@link Vector3D 221 * Vector3D} instance) 222 * @return in-plane point (really a {@link 223 * org.apache.commons.math3.geometry.euclidean.twod.Vector2D Vector2D} instance) 224 * @see #toSpace 225 */ 226 public Vector2D toSubSpace(final Vector<Euclidean3D> point) { 227 return new Vector2D(point.dotProduct(u), point.dotProduct(v)); 228 } 229 230 /** Transform an in-plane point into a 3D space point. 231 * @param point in-plane point (must be a {@link 232 * org.apache.commons.math3.geometry.euclidean.twod.Vector2D Vector2D} instance) 233 * @return 3D space point (really a {@link Vector3D Vector3D} instance) 234 * @see #toSubSpace 235 */ 236 public Vector3D toSpace(final Vector<Euclidean2D> point) { 237 final Vector2D p2D = (Vector2D) point; 238 return new Vector3D(p2D.getX(), u, p2D.getY(), v, -originOffset, w); 239 } 240 241 /** Get one point from the 3D-space. 242 * @param inPlane desired in-plane coordinates for the point in the 243 * plane 244 * @param offset desired offset for the point 245 * @return one point in the 3D-space, with given coordinates and offset 246 * relative to the plane 247 */ 248 public Vector3D getPointAt(final Vector2D inPlane, final double offset) { 249 return new Vector3D(inPlane.getX(), u, inPlane.getY(), v, offset - originOffset, w); 250 } 251 252 /** Check if the instance is similar to another plane. 253 * <p>Planes are considered similar if they contain the same 254 * points. This does not mean they are equal since they can have 255 * opposite normals.</p> 256 * @param plane plane to which the instance is compared 257 * @return true if the planes are similar 258 */ 259 public boolean isSimilarTo(final Plane plane) { 260 final double angle = Vector3D.angle(w, plane.w); 261 return ((angle < 1.0e-10) && (FastMath.abs(originOffset - plane.originOffset) < 1.0e-10)) || 262 ((angle > (FastMath.PI - 1.0e-10)) && (FastMath.abs(originOffset + plane.originOffset) < 1.0e-10)); 263 } 264 265 /** Rotate the plane around the specified point. 266 * <p>The instance is not modified, a new instance is created.</p> 267 * @param center rotation center 268 * @param rotation vectorial rotation operator 269 * @return a new plane 270 */ 271 public Plane rotate(final Vector3D center, final Rotation rotation) { 272 273 final Vector3D delta = origin.subtract(center); 274 final Plane plane = new Plane(center.add(rotation.applyTo(delta)), 275 rotation.applyTo(w)); 276 277 // make sure the frame is transformed as desired 278 plane.u = rotation.applyTo(u); 279 plane.v = rotation.applyTo(v); 280 281 return plane; 282 283 } 284 285 /** Translate the plane by the specified amount. 286 * <p>The instance is not modified, a new instance is created.</p> 287 * @param translation translation to apply 288 * @return a new plane 289 */ 290 public Plane translate(final Vector3D translation) { 291 292 final Plane plane = new Plane(origin.add(translation), w); 293 294 // make sure the frame is transformed as desired 295 plane.u = u; 296 plane.v = v; 297 298 return plane; 299 300 } 301 302 /** Get the intersection of a line with the instance. 303 * @param line line intersecting the instance 304 * @return intersection point between between the line and the 305 * instance (null if the line is parallel to the instance) 306 */ 307 public Vector3D intersection(final Line line) { 308 final Vector3D direction = line.getDirection(); 309 final double dot = w.dotProduct(direction); 310 if (FastMath.abs(dot) < 1.0e-10) { 311 return null; 312 } 313 final Vector3D point = line.toSpace(Vector1D.ZERO); 314 final double k = -(originOffset + w.dotProduct(point)) / dot; 315 return new Vector3D(1.0, point, k, direction); 316 } 317 318 /** Build the line shared by the instance and another plane. 319 * @param other other plane 320 * @return line at the intersection of the instance and the 321 * other plane (really a {@link Line Line} instance) 322 */ 323 public Line intersection(final Plane other) { 324 final Vector3D direction = Vector3D.crossProduct(w, other.w); 325 if (direction.getNorm() < 1.0e-10) { 326 return null; 327 } 328 final Vector3D point = intersection(this, other, new Plane(direction)); 329 return new Line(point, point.add(direction)); 330 } 331 332 /** Get the intersection point of three planes. 333 * @param plane1 first plane1 334 * @param plane2 second plane2 335 * @param plane3 third plane2 336 * @return intersection point of three planes, null if some planes are parallel 337 */ 338 public static Vector3D intersection(final Plane plane1, final Plane plane2, final Plane plane3) { 339 340 // coefficients of the three planes linear equations 341 final double a1 = plane1.w.getX(); 342 final double b1 = plane1.w.getY(); 343 final double c1 = plane1.w.getZ(); 344 final double d1 = plane1.originOffset; 345 346 final double a2 = plane2.w.getX(); 347 final double b2 = plane2.w.getY(); 348 final double c2 = plane2.w.getZ(); 349 final double d2 = plane2.originOffset; 350 351 final double a3 = plane3.w.getX(); 352 final double b3 = plane3.w.getY(); 353 final double c3 = plane3.w.getZ(); 354 final double d3 = plane3.originOffset; 355 356 // direct Cramer resolution of the linear system 357 // (this is still feasible for a 3x3 system) 358 final double a23 = b2 * c3 - b3 * c2; 359 final double b23 = c2 * a3 - c3 * a2; 360 final double c23 = a2 * b3 - a3 * b2; 361 final double determinant = a1 * a23 + b1 * b23 + c1 * c23; 362 if (FastMath.abs(determinant) < 1.0e-10) { 363 return null; 364 } 365 366 final double r = 1.0 / determinant; 367 return new Vector3D( 368 (-a23 * d1 - (c1 * b3 - c3 * b1) * d2 - (c2 * b1 - c1 * b2) * d3) * r, 369 (-b23 * d1 - (c3 * a1 - c1 * a3) * d2 - (c1 * a2 - c2 * a1) * d3) * r, 370 (-c23 * d1 - (b1 * a3 - b3 * a1) * d2 - (b2 * a1 - b1 * a2) * d3) * r); 371 372 } 373 374 /** Build a region covering the whole hyperplane. 375 * @return a region covering the whole hyperplane 376 */ 377 public SubPlane wholeHyperplane() { 378 return new SubPlane(this, new PolygonsSet()); 379 } 380 381 /** Build a region covering the whole space. 382 * @return a region containing the instance (really a {@link 383 * PolyhedronsSet PolyhedronsSet} instance) 384 */ 385 public PolyhedronsSet wholeSpace() { 386 return new PolyhedronsSet(); 387 } 388 389 /** Check if the instance contains a point. 390 * @param p point to check 391 * @return true if p belongs to the plane 392 */ 393 public boolean contains(final Vector3D p) { 394 return FastMath.abs(getOffset(p)) < 1.0e-10; 395 } 396 397 /** Get the offset (oriented distance) of a parallel plane. 398 * <p>This method should be called only for parallel planes otherwise 399 * the result is not meaningful.</p> 400 * <p>The offset is 0 if both planes are the same, it is 401 * positive if the plane is on the plus side of the instance and 402 * negative if it is on the minus side, according to its natural 403 * orientation.</p> 404 * @param plane plane to check 405 * @return offset of the plane 406 */ 407 public double getOffset(final Plane plane) { 408 return originOffset + (sameOrientationAs(plane) ? -plane.originOffset : plane.originOffset); 409 } 410 411 /** Get the offset (oriented distance) of a point. 412 * <p>The offset is 0 if the point is on the underlying hyperplane, 413 * it is positive if the point is on one particular side of the 414 * hyperplane, and it is negative if the point is on the other side, 415 * according to the hyperplane natural orientation.</p> 416 * @param point point to check 417 * @return offset of the point 418 */ 419 public double getOffset(final Vector<Euclidean3D> point) { 420 return point.dotProduct(w) + originOffset; 421 } 422 423 /** Check if the instance has the same orientation as another hyperplane. 424 * @param other other hyperplane to check against the instance 425 * @return true if the instance and the other hyperplane have 426 * the same orientation 427 */ 428 public boolean sameOrientationAs(final Hyperplane<Euclidean3D> other) { 429 return (((Plane) other).w).dotProduct(w) > 0.0; 430 } 431 432 }