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 * https://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 18 /* 19 * This is not the original file distributed by the Apache Software Foundation 20 * It has been modified by the Hipparchus project 21 */ 22 package org.hipparchus.geometry.euclidean.threed; 23 24 import org.hipparchus.exception.LocalizedCoreFormats; 25 import org.hipparchus.exception.MathRuntimeException; 26 import org.hipparchus.geometry.Point; 27 import org.hipparchus.geometry.Vector; 28 import org.hipparchus.geometry.euclidean.oned.Euclidean1D; 29 import org.hipparchus.geometry.euclidean.oned.Vector1D; 30 import org.hipparchus.geometry.euclidean.twod.Euclidean2D; 31 import org.hipparchus.geometry.euclidean.twod.PolygonsSet; 32 import org.hipparchus.geometry.euclidean.twod.Vector2D; 33 import org.hipparchus.geometry.partitioning.Embedding; 34 import org.hipparchus.geometry.partitioning.Hyperplane; 35 import org.hipparchus.geometry.partitioning.RegionFactory; 36 import org.hipparchus.util.FastMath; 37 38 /** The class represent planes in a three dimensional space. 39 */ 40 public class Plane implements Hyperplane<Euclidean3D>, Embedding<Euclidean3D, Euclidean2D> { 41 42 /** Offset of the origin with respect to the plane. */ 43 private double originOffset; 44 45 /** Origin of the plane frame. */ 46 private Vector3D origin; 47 48 /** First vector of the plane frame (in plane). */ 49 private Vector3D u; 50 51 /** Second vector of the plane frame (in plane). */ 52 private Vector3D v; 53 54 /** Third vector of the plane frame (plane normal). */ 55 private Vector3D w; 56 57 /** Tolerance below which points are considered identical. */ 58 private final double tolerance; 59 60 /** Build a plane normal to a given direction and containing the origin. 61 * @param normal normal direction to the plane 62 * @param tolerance tolerance below which points are considered identical 63 * @exception MathRuntimeException if the normal norm is too small 64 */ 65 public Plane(final Vector3D normal, final double tolerance) 66 throws MathRuntimeException { 67 setNormal(normal); 68 this.tolerance = tolerance; 69 originOffset = 0; 70 setFrame(); 71 } 72 73 /** Build a plane from a point and a normal. 74 * @param p point belonging to the plane 75 * @param normal normal direction to the plane 76 * @param tolerance tolerance below which points are considered identical 77 * @exception MathRuntimeException if the normal norm is too small 78 */ 79 public Plane(final Vector3D p, final Vector3D normal, final double tolerance) 80 throws MathRuntimeException { 81 setNormal(normal); 82 this.tolerance = tolerance; 83 originOffset = -p.dotProduct(w); 84 setFrame(); 85 } 86 87 /** Build a plane from three points. 88 * <p>The plane is oriented in the direction of 89 * {@code (p2-p1) ^ (p3-p1)}</p> 90 * @param p1 first point belonging to the plane 91 * @param p2 second point belonging to the plane 92 * @param p3 third point belonging to the plane 93 * @param tolerance tolerance below which points are considered identical 94 * @exception MathRuntimeException if the points do not constitute a plane 95 */ 96 public Plane(final Vector3D p1, final Vector3D p2, final Vector3D p3, final double tolerance) 97 throws MathRuntimeException { 98 this(p1, p2.subtract(p1).crossProduct(p3.subtract(p1)), tolerance); 99 } 100 101 /** Copy constructor. 102 * <p>The instance created is completely independent of the original 103 * one. A deep copy is used, none of the underlying object are 104 * shared.</p> 105 * @param plane plane to copy 106 */ 107 public Plane(final Plane plane) { 108 originOffset = plane.originOffset; 109 origin = plane.origin; 110 u = plane.u; 111 v = plane.v; 112 w = plane.w; 113 tolerance = plane.tolerance; 114 } 115 116 /** Copy the instance. 117 * <p>The instance created is completely independant of the original 118 * one. A deep copy is used, none of the underlying objects are 119 * shared (except for immutable objects).</p> 120 * @return a new hyperplane, copy of the instance 121 */ 122 @Override 123 public Plane copySelf() { 124 return new Plane(this); 125 } 126 127 /** Reset the instance as if built from a point and a normal. 128 * @param p point belonging to the plane 129 * @param normal normal direction to the plane 130 * @exception MathRuntimeException if the normal norm is too small 131 */ 132 public void reset(final Vector3D p, final Vector3D normal) throws MathRuntimeException { 133 setNormal(normal); 134 originOffset = -p.dotProduct(w); 135 setFrame(); 136 } 137 138 /** Reset the instance from another one. 139 * <p>The updated instance is completely independant of the original 140 * one. A deep reset is used none of the underlying object is 141 * shared.</p> 142 * @param original plane to reset from 143 */ 144 public void reset(final Plane original) { 145 originOffset = original.originOffset; 146 origin = original.origin; 147 u = original.u; 148 v = original.v; 149 w = original.w; 150 } 151 152 /** Set the normal vactor. 153 * @param normal normal direction to the plane (will be copied) 154 * @exception MathRuntimeException if the normal norm is too small 155 */ 156 private void setNormal(final Vector3D normal) throws MathRuntimeException { 157 final double norm = normal.getNorm(); 158 if (norm < 1.0e-10) { 159 throw new MathRuntimeException(LocalizedCoreFormats.ZERO_NORM); 160 } 161 w = new Vector3D(1.0 / norm, normal); 162 } 163 164 /** Reset the plane frame. 165 */ 166 private void setFrame() { 167 origin = new Vector3D(-originOffset, w); 168 u = w.orthogonal(); 169 v = Vector3D.crossProduct(w, u); 170 } 171 172 /** Get the origin point of the plane frame. 173 * <p>The point returned is the orthogonal projection of the 174 * 3D-space origin in the plane.</p> 175 * @return the origin point of the plane frame (point closest to the 176 * 3D-space origin) 177 */ 178 public Vector3D getOrigin() { 179 return origin; 180 } 181 182 /** Get the normalized normal vector. 183 * <p>The frame defined by ({@link #getU getU}, {@link #getV getV}, 184 * {@link #getNormal getNormal}) is a rigth-handed orthonormalized 185 * frame).</p> 186 * @return normalized normal vector 187 * @see #getU 188 * @see #getV 189 */ 190 public Vector3D getNormal() { 191 return w; 192 } 193 194 /** Get the plane first canonical vector. 195 * <p>The frame defined by ({@link #getU getU}, {@link #getV getV}, 196 * {@link #getNormal getNormal}) is a rigth-handed orthonormalized 197 * frame).</p> 198 * @return normalized first canonical vector 199 * @see #getV 200 * @see #getNormal 201 */ 202 public Vector3D getU() { 203 return u; 204 } 205 206 /** Get the plane second canonical vector. 207 * <p>The frame defined by ({@link #getU getU}, {@link #getV getV}, 208 * {@link #getNormal getNormal}) is a rigth-handed orthonormalized 209 * frame).</p> 210 * @return normalized second canonical vector 211 * @see #getU 212 * @see #getNormal 213 */ 214 public Vector3D getV() { 215 return v; 216 } 217 218 /** {@inheritDoc} 219 */ 220 @Override 221 public Point<Euclidean3D> project(Point<Euclidean3D> point) { 222 return toSpace(toSubSpace(point)); 223 } 224 225 /** {@inheritDoc} 226 */ 227 @Override 228 public double getTolerance() { 229 return tolerance; 230 } 231 232 /** Revert the plane. 233 * <p>Replace the instance by a similar plane with opposite orientation.</p> 234 * <p>The new plane frame is chosen in such a way that a 3D point that had 235 * {@code (x, y)} in-plane coordinates and {@code z} offset with 236 * respect to the plane and is unaffected by the change will have 237 * {@code (y, x)} in-plane coordinates and {@code -z} offset with 238 * respect to the new plane. This means that the {@code u} and {@code v} 239 * vectors returned by the {@link #getU} and {@link #getV} methods are exchanged, 240 * and the {@code w} vector returned by the {@link #getNormal} method is 241 * reversed.</p> 242 */ 243 public void revertSelf() { 244 final Vector3D tmp = u; 245 u = v; 246 v = tmp; 247 w = w.negate(); 248 originOffset = -originOffset; 249 } 250 251 /** Transform a space point into a sub-space point. 252 * @param vector n-dimension point of the space 253 * @return (n-1)-dimension point of the sub-space corresponding to 254 * the specified space point 255 */ 256 public Vector2D toSubSpace(Vector<Euclidean3D, Vector3D> vector) { 257 return toSubSpace((Point<Euclidean3D>) vector); 258 } 259 260 /** Transform a sub-space point into a space point. 261 * @param vector (n-1)-dimension point of the sub-space 262 * @return n-dimension point of the space corresponding to the 263 * specified sub-space point 264 */ 265 public Vector3D toSpace(Vector<Euclidean2D, Vector2D> vector) { 266 return toSpace((Point<Euclidean2D>) vector); 267 } 268 269 /** Transform a 3D space point into an in-plane point. 270 * @param point point of the space (must be a {@link Vector3D 271 * Vector3D} instance) 272 * @return in-plane point (really a {@link 273 * org.hipparchus.geometry.euclidean.twod.Vector2D Vector2D} instance) 274 * @see #toSpace 275 */ 276 @Override 277 public Vector2D toSubSpace(final Point<Euclidean3D> point) { 278 final Vector3D p3D = (Vector3D) point; 279 return new Vector2D(p3D.dotProduct(u), p3D.dotProduct(v)); 280 } 281 282 /** Transform an in-plane point into a 3D space point. 283 * @param point in-plane point (must be a {@link 284 * org.hipparchus.geometry.euclidean.twod.Vector2D Vector2D} instance) 285 * @return 3D space point (really a {@link Vector3D Vector3D} instance) 286 * @see #toSubSpace 287 */ 288 @Override 289 public Vector3D toSpace(final Point<Euclidean2D> point) { 290 final Vector2D p2D = (Vector2D) point; 291 return new Vector3D(p2D.getX(), u, p2D.getY(), v, -originOffset, w); 292 } 293 294 /** Get one point from the 3D-space. 295 * @param inPlane desired in-plane coordinates for the point in the 296 * plane 297 * @param offset desired offset for the point 298 * @return one point in the 3D-space, with given coordinates and offset 299 * relative to the plane 300 */ 301 public Vector3D getPointAt(final Vector2D inPlane, final double offset) { 302 return new Vector3D(inPlane.getX(), u, inPlane.getY(), v, offset - originOffset, w); 303 } 304 305 /** Check if the instance is similar to another plane. 306 * <p>Planes are considered similar if they contain the same 307 * points. This does not mean they are equal since they can have 308 * opposite normals.</p> 309 * @param plane plane to which the instance is compared 310 * @return true if the planes are similar 311 */ 312 public boolean isSimilarTo(final Plane plane) { 313 final double angle = Vector3D.angle(w, plane.w); 314 return ((angle < 1.0e-10) && (FastMath.abs(originOffset - plane.originOffset) < tolerance)) || 315 ((angle > (FastMath.PI - 1.0e-10)) && (FastMath.abs(originOffset + plane.originOffset) < tolerance)); 316 } 317 318 /** Rotate the plane around the specified point. 319 * <p>The instance is not modified, a new instance is created.</p> 320 * @param center rotation center 321 * @param rotation vectorial rotation operator 322 * @return a new plane 323 */ 324 public Plane rotate(final Vector3D center, final Rotation rotation) { 325 326 final Vector3D delta = origin.subtract(center); 327 final Plane plane = new Plane(center.add(rotation.applyTo(delta)), 328 rotation.applyTo(w), tolerance); 329 330 // make sure the frame is transformed as desired 331 plane.u = rotation.applyTo(u); 332 plane.v = rotation.applyTo(v); 333 334 return plane; 335 336 } 337 338 /** Translate the plane by the specified amount. 339 * <p>The instance is not modified, a new instance is created.</p> 340 * @param translation translation to apply 341 * @return a new plane 342 */ 343 public Plane translate(final Vector3D translation) { 344 345 final Plane plane = new Plane(origin.add(translation), w, tolerance); 346 347 // make sure the frame is transformed as desired 348 plane.u = u; 349 plane.v = v; 350 351 return plane; 352 353 } 354 355 /** Get the intersection of a line with the instance. 356 * @param line line intersecting the instance 357 * @return intersection point between between the line and the 358 * instance (null if the line is parallel to the instance) 359 */ 360 public Vector3D intersection(final Line line) { 361 final Vector3D direction = line.getDirection(); 362 final double dot = w.dotProduct(direction); 363 if (FastMath.abs(dot) < 1.0e-10) { 364 return null; 365 } 366 final Vector3D point = line.toSpace((Point<Euclidean1D>) Vector1D.ZERO); 367 final double k = -(originOffset + w.dotProduct(point)) / dot; 368 return new Vector3D(1.0, point, k, direction); 369 } 370 371 /** Build the line shared by the instance and another plane. 372 * @param other other plane 373 * @return line at the intersection of the instance and the 374 * other plane (really a {@link Line Line} instance) 375 */ 376 public Line intersection(final Plane other) { 377 final Vector3D direction = Vector3D.crossProduct(w, other.w); 378 if (direction.getNorm() < tolerance) { 379 return null; 380 } 381 final Vector3D point = intersection(this, other, new Plane(direction, tolerance)); 382 return new Line(point, point.add(direction), tolerance); 383 } 384 385 /** Get the intersection point of three planes. 386 * @param plane1 first plane1 387 * @param plane2 second plane2 388 * @param plane3 third plane2 389 * @return intersection point of three planes, null if some planes are parallel 390 */ 391 public static Vector3D intersection(final Plane plane1, final Plane plane2, final Plane plane3) { 392 393 // coefficients of the three planes linear equations 394 final double a1 = plane1.w.getX(); 395 final double b1 = plane1.w.getY(); 396 final double c1 = plane1.w.getZ(); 397 final double d1 = plane1.originOffset; 398 399 final double a2 = plane2.w.getX(); 400 final double b2 = plane2.w.getY(); 401 final double c2 = plane2.w.getZ(); 402 final double d2 = plane2.originOffset; 403 404 final double a3 = plane3.w.getX(); 405 final double b3 = plane3.w.getY(); 406 final double c3 = plane3.w.getZ(); 407 final double d3 = plane3.originOffset; 408 409 // direct Cramer resolution of the linear system 410 // (this is still feasible for a 3x3 system) 411 final double a23 = b2 * c3 - b3 * c2; 412 final double b23 = c2 * a3 - c3 * a2; 413 final double c23 = a2 * b3 - a3 * b2; 414 final double determinant = a1 * a23 + b1 * b23 + c1 * c23; 415 if (FastMath.abs(determinant) < 1.0e-10) { 416 return null; 417 } 418 419 final double r = 1.0 / determinant; 420 return new Vector3D( 421 (-a23 * d1 - (c1 * b3 - c3 * b1) * d2 - (c2 * b1 - c1 * b2) * d3) * r, 422 (-b23 * d1 - (c3 * a1 - c1 * a3) * d2 - (c1 * a2 - c2 * a1) * d3) * r, 423 (-c23 * d1 - (b1 * a3 - b3 * a1) * d2 - (b2 * a1 - b1 * a2) * d3) * r); 424 425 } 426 427 /** Build a region covering the whole hyperplane. 428 * @return a region covering the whole hyperplane 429 */ 430 @Override 431 public SubPlane wholeHyperplane() { 432 return new SubPlane(this, new PolygonsSet(tolerance)); 433 } 434 435 /** {@inheritDoc} */ 436 @Override 437 public SubPlane emptyHyperplane() { 438 return new SubPlane(this, new RegionFactory<Euclidean2D>().getComplement(new PolygonsSet(tolerance))); 439 } 440 441 /** Build a region covering the whole space. 442 * @return a region containing the instance (really a {@link 443 * PolyhedronsSet PolyhedronsSet} instance) 444 */ 445 @Override 446 public PolyhedronsSet wholeSpace() { 447 return new PolyhedronsSet(tolerance); 448 } 449 450 /** Check if the instance contains a point. 451 * @param p point to check 452 * @return true if p belongs to the plane 453 */ 454 public boolean contains(final Vector3D p) { 455 return FastMath.abs(getOffset(p)) < tolerance; 456 } 457 458 /** Get the offset (oriented distance) of a parallel plane. 459 * <p>This method should be called only for parallel planes otherwise 460 * the result is not meaningful.</p> 461 * <p>The offset is 0 if both planes are the same, it is 462 * positive if the plane is on the plus side of the instance and 463 * negative if it is on the minus side, according to its natural 464 * orientation.</p> 465 * @param plane plane to check 466 * @return offset of the plane 467 */ 468 public double getOffset(final Plane plane) { 469 return originOffset + (sameOrientationAs(plane) ? -plane.originOffset : plane.originOffset); 470 } 471 472 /** Get the offset (oriented distance) of a vector. 473 * @param vector vector to check 474 * @return offset of the vector 475 */ 476 public double getOffset(Vector<Euclidean3D, Vector3D> vector) { 477 return getOffset((Point<Euclidean3D>) vector); 478 } 479 480 /** Get the offset (oriented distance) of a point. 481 * <p>The offset is 0 if the point is on the underlying hyperplane, 482 * it is positive if the point is on one particular side of the 483 * hyperplane, and it is negative if the point is on the other side, 484 * according to the hyperplane natural orientation.</p> 485 * @param point point to check 486 * @return offset of the point 487 */ 488 @Override 489 public double getOffset(final Point<Euclidean3D> point) { 490 return ((Vector3D) point).dotProduct(w) + originOffset; 491 } 492 493 /** Check if the instance has the same orientation as another hyperplane. 494 * @param other other hyperplane to check against the instance 495 * @return true if the instance and the other hyperplane have 496 * the same orientation 497 */ 498 @Override 499 public boolean sameOrientationAs(final Hyperplane<Euclidean3D> other) { 500 return (((Plane) other).w).dotProduct(w) > 0.0; 501 } 502 503 }