/** * */ package cz.cuni.mff.peckam.java.origamist.math; import static cz.cuni.mff.peckam.java.origamist.math.MathHelper.EPSILON; import static java.lang.Math.abs; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.vecmath.Point3d; import javax.vecmath.Vector3d; import org.apache.log4j.Logger; import cz.cuni.mff.peckam.java.origamist.modelstate.ModelTriangle; /** * A representation of a 3D triangle. * * @author Martin Pecka */ public class Triangle3d implements Cloneable { protected Point3d p1; protected Point3d p2; protected Point3d p3; protected HalfSpace3d hs1; protected HalfSpace3d hs2; protected HalfSpace3d hs3; protected Plane3d plane; protected Segment3d s1; protected Segment3d s2; protected Segment3d s3; /** The list of neighbors. */ protected List<Triangle3d> neighbors = new LinkedList<Triangle3d>(); /** The read-only view on the neighbors list. */ protected List<Triangle3d> neighborsRO = Collections.unmodifiableList(neighbors); /** * @return The list of neighbor triangles. The returned list is read-only. Triangles with just a single common point * aren't considered as neighbors. */ public List<? extends Triangle3d> getNeighbors() { return neighborsRO; } /** * Create a triangle with the given vertices. * * @param p1 A vertex. * @param p2 A vertex. * @param p3 A vertex. * * @throws IllegalArgumentException If the given points lie in one line. */ public Triangle3d(Point3d p1, Point3d p2, Point3d p3) throws IllegalArgumentException { this.p1 = p1; this.p2 = p2; this.p3 = p3; if (new Line3d(p1, p2).contains(p3)) { throw new IllegalArgumentException("Trying to create a triangle from colinear points."); } recomputeDerivedItems(); } /** * Create a triangle with the given vertices. * * @param p1x A vertex's x coordinate. * @param p1y A vertex's y coordinate. * @param p1z A vertex's z coordinate. * @param p2x A vertex's x coordinate. * @param p2y A vertex's y coordinate. * @param p2z A vertex's z coordinate. * @param p3x A vertex's x coordinate. * @param p3y A vertex's y coordinate. * @param p3z A vertex's z coordinate. * * @throws IllegalArgumentException If the given points lie in one line. */ public Triangle3d(double p1x, double p1y, double p1z, double p2x, double p2y, double p2z, double p3x, double p3y, double p3z) throws IllegalArgumentException { this(new Point3d(p1x, p1y, p1z), new Point3d(p2x, p2y, p2z), new Point3d(p3x, p3y, p3z)); } /** * Compute new values of all the helper fields such as plane, s1, s2, s3 and so... */ protected void recomputeDerivedItems() { plane = new Plane3d(p1, p2, p3); hs1 = HalfSpace3d.createPerpendicularToTriangle(p1, p2, p3); hs2 = HalfSpace3d.createPerpendicularToTriangle(p2, p3, p1); hs3 = HalfSpace3d.createPerpendicularToTriangle(p3, p1, p2); s1 = new Segment3d(p1, p2); s2 = new Segment3d(p2, p3); s3 = new Segment3d(p3, p1); } /** * Change the points of this triangle. Only non-null points will be changed. * * @param p1 * @param p2 * @param p3 */ public void setPoints(Point3d p1, Point3d p2, Point3d p3) { if (p1 != null) this.p1 = p1; if (p2 != null) this.p2 = p2; if (p3 != null) this.p3 = p3; recomputeDerivedItems(); } /** * Return the coordinates of the first point. * * @return The coordinates of the first point. */ public Point3d getP1() { return p1; } /** * Return the coordinates of the second point. * * @return The coordinates of the second point. */ public Point3d getP2() { return p2; } /** * Return the coordinates of the third point. * * @return The coordinates of the third point. */ public Point3d getP3() { return p3; } /** * @return An array of vertices of the triangle. Further modifications to this array will have no effect on the * triangle. */ public Point3d[] getVertices() { return new Point3d[] { p1, p2, p3 }; } /** * @return The first side of the triangle. */ public Segment3d getS1() { return s1; } /** * @return The second side of the triangle. */ public Segment3d getS2() { return s2; } /** * @return The third side of the triangle. */ public Segment3d getS3() { return s3; } /** * @return An array of all edges of the triangle. Further modifications to this array will have no effect on the * triangle. */ public Segment3d[] getEdges() { return new Segment3d[] { s1, s2, s3 }; } /** * @return The plane the triangle lies in. */ public Plane3d getPlane() { return plane; } /** * Returns true if this triangle contains the given point. * * @param point The point to check. * @return Whether this triangle contains the given point. */ public boolean contains(Point3d point) { return plane.contains(point) && hs1.contains(point) && hs2.contains(point) && hs3.contains(point); } /** * Return <code>true</code> if the given point lies in one of the sides of this triangle. * * @param point The point to check. * @return <code>true</code> if the given point lies in one of the sides of this triangle. */ public boolean sidesContain(Point3d point) { return s1.contains(point) || s2.contains(point) || s3.contains(point); } /** * Returns true if the given triangle has a common edge with this triangle. * * If <code>strict</code> is true, then the edges must match exactly. If it is false, it is sufficient that the * edges overlap. * * @param t The triangle to try to find common edge with. * @param strict If true, then the edges must match exactly. If it is false, it is sufficient that the edges * overlap. * @return true if the given triangle has a common edge with this triangle. */ public boolean hasCommonEdge(Triangle3d t, boolean strict) { return getCommonEdge(t, strict) != null; } /** * Returns the common part of edges of this and the given triangle. If they have just a common point, a segment * with zero direction vector will be returned. If the triangles overlay by more than an edge, the result is * undefined. If the triangles do not have a common segment, <code>null</code> will be returned. * * @param t The segment to find common edge with. * @param strict If true, then the edges must match exactly. If it is false, it is sufficient that the edges * overlap. * @return The common part of edges of this and the given triangle. */ public Segment3d getCommonEdge(Triangle3d t, boolean strict) { Segment3d result = null; for (Segment3d edge1 : getEdges()) { for (Segment3d edge2 : t.getEdges()) { if (strict) { if (edge1.epsilonEquals(edge2, true)) return new Segment3d(edge1); } else { if (edge1.overlaps(edge2)) { Segment3d intersection = edge1.getIntersection(edge2); // if the intersection isn't just a point, we can surely return if (intersection != null && !intersection.isSinglePoint()) return intersection; if (intersection != null) result = intersection; } } } } return result; } /** * Return <code>true</code> if the given point is a vertex of this triangle. * * @param point The point to check. * @return <code>true</code> if the given point is a vertex of this triangle. */ public boolean isVertex(Point3d point) { if (p1.epsilonEquals(point, EPSILON) || p2.epsilonEquals(point, EPSILON) || p3.epsilonEquals(point, EPSILON)) return true; // the following shouldn't be needed, but for robustness it is there for (Segment3d s : getEdges()) { Double param = s.getParameterForPoint(point); if (param != null && (param < EPSILON || param > 1 - EPSILON)) return true; } return false; } /** * Return the nearest vertex to the given point. * * @param point The point to find nearest vertex for. * @return The nearest vertex. */ public Point3d getNearestVertex(Point3d point) { double dist1 = p1.distance(point); double dist2 = p2.distance(point); double dist3 = p3.distance(point); if (dist1 <= dist2 && dist1 <= dist3) return p1; else if (dist2 <= dist1 && dist2 <= dist3) return p2; else return p3; } /** * Return the edges that contain the given vertex. * * @param vertex The vertex to find edges for. * @return Either a 2-element array of edges, or <code>null</code>, if the given point isn't a vertex. */ public Segment3d[] getVertexEdges(Point3d vertex) { if (vertex.epsilonEquals(p1, EPSILON)) { return new Segment3d[] { s1, s3 }; } else if (vertex.epsilonEquals(p2, EPSILON)) { return new Segment3d[] { s1, s2 }; } else if (vertex.epsilonEquals(p3, EPSILON)) { return new Segment3d[] { s2, s3 }; } else { return null; } } /** * Return the point corresponding to the given barycentric coordinates in this triangle. * * @param b The barycentric coordinates to convert. * @return The point corresponding to the given barycentric coordinates in this triangle. */ public Point3d interpolatePointFromBarycentric(Vector3d b) { Point3d result = new Point3d(); result.x = b.x * p1.x + b.y * p2.x + b.z * p3.x; result.y = b.x * p1.y + b.y * p2.y + b.z * p3.y; result.z = b.x * p1.z + b.y * p2.z + b.z * p3.z; return result; } /** * Return the barycentric coordinates of the given point. * * @param p The point to compute coordinates of. * @return The barycentric coordinates of the given point. * * @see http://facultyfp.salisbury.edu/despickler/personal/C482/Resources/barycentric.pdf */ public Vector3d getBarycentricCoordinates(Point3d p) { Vector3d c_a = new Vector3d(p3); c_a.sub(p1); Vector3d a_c = new Vector3d(p1); a_c.sub(p3); Vector3d c_b = new Vector3d(p3); c_b.sub(p2); Vector3d b_a = new Vector3d(p2); b_a.sub(p1); Vector3d p_a = new Vector3d(p); p_a.sub(p1); Vector3d p_b = new Vector3d(p); p_b.sub(p2); Vector3d p_c = new Vector3d(p); p_c.sub(p3); Vector3d n = new Vector3d(); Vector3d na = new Vector3d(); Vector3d nb = new Vector3d(); Vector3d nc = new Vector3d(); n.cross(b_a, c_a); na.cross(c_b, p_b); nb.cross(a_c, p_c); nc.cross(b_a, p_a); double nLengthSq = n.lengthSquared(); return new Vector3d(n.dot(na) / nLengthSq, n.dot(nb) / nLengthSq, n.dot(nc) / nLengthSq); } /** * Return the intersection points of this triangle and the given line. * <p> * Note that some heuristic is performed (such as "magnetizing" edges and vertices), so it generally doesn't hold * that the returned segment is a subsegment of the argument (it can be probably 10*EPSILON-different). * * @param line The line to get intersections with. * @return A segment that defines the intersection of the given line (or segment) and this triangle (the segment can * be zero-length), or <code>null</code>, if no intersection exists. A segment start or end inside the * triangle is also taken as an intersection. */ public Segment3d getIntersection(Line3d line) { if (abs(line.v.dot(getNormal())) < EPSILON) { // the line is parallel to the triangle's plane if (!plane.contains(line.p)) return null; // the line doesn't lie in the triangle's plane Segment3d intersection = null; List<Point3d> intersections = new ArrayList<Point3d>(3); // non-vertex intersections will be added directly; vertex intersections would appear twice (for both // edges), so we rather handle vertices specially Set<Point3d> verticesToAdd = new HashSet<Point3d>(); for (Segment3d s : getEdges()) { // find intersections with edges intersection = s.getIntersection(line); if (intersection != null && intersection.isSinglePoint()) { // the point->param->point conversion is done to be as close to the edge as possible double param = s.getParameterForPoint(intersection.p); // make vertices magnetic if (param < 100 * EPSILON || param > 1 - 100 * EPSILON) { verticesToAdd.add(getNearestVertex(intersection.p)); } else { intersections.add(s.getPointForParameter(param)); } } else if (intersection != null) { // the line lies on the same line as the edge and they have nonempty intersection - we can return return intersection.getIntersection(s); } } intersections.addAll(verticesToAdd); if (intersections.size() < 2 && line instanceof Segment3d) { // a segment can start or end inside the triangle for (Point3d p : ((Segment3d) line).getPoints()) { if (this.contains(p) && !sidesContain(p)) { intersections.add(p); } } } // rounding errors may affect the method a lot, so ensure it is a little more tolerant MathHelper.removeEpsilonEqualPoints(intersections, 2d * EPSILON); // rounding errors may cause that we get three points lying on one line if (intersections.size() > 2 && new Line3d(intersections.get(0), intersections.get(1)).contains(intersections.get(2))) { Segment3d seg1 = new Segment3d(intersections.get(0), intersections.get(1)); Segment3d seg2 = new Segment3d(intersections.get(0), intersections.get(2)); Segment3d seg3 = new Segment3d(intersections.get(1), intersections.get(2)); double seg1l = seg1.getLength(), seg2l = seg2.getLength(), seg3l = seg3.getLength(); if (seg1l >= seg2l && seg1l >= seg3l) return seg1; else if (seg2l >= seg1l && seg2l >= seg3l) return seg2; else return seg3; } // if there is a nearby point contained in an edge of the triangle, then retain only that on-edge point and // remove all nearby points for (int i = 0; i < intersections.size(); i++) { if (!sidesContain(intersections.get(i))) { Point3d substitution = null; for (int j = 0; j < intersections.size(); j++) { if (j == i) continue; if (sidesContain(intersections.get(j)) && intersections.get(i).distance(intersections.get(j)) < 10d * EPSILON) { substitution = intersections.get(j); break; } } if (substitution != null) { intersections.remove(i--); } } } double i = 2d; while (intersections.size() > 2 && i < 10d) { MathHelper.removeEpsilonEqualPoints(intersections, i++ * EPSILON); } if (i > 2d) Logger.getLogger(getClass()).warn( "Used " + (i - 1) + "*EPSILON for joining intersection points. The resulting intersection points are " + intersections); if (intersections.size() == 2) { return new Segment3d(intersections.get(0), intersections.get(1)); } else if (intersections.size() == 1) { return new Segment3d(intersections.get(0), intersections.get(0)); } else if (intersections.size() == 0) { return null; } else { throw new IllegalStateException("Illegal count of intersections of a line and triangle: " + intersections.size()); } } else { // the line isn't parallel to the triangle's plane Line3d intersection = plane.getIntersection(line); // line.contains(...) is being called because the line can be also a Segment3d if (intersection != null && intersection.isSinglePoint()) { if (contains(intersection.p)) return new Segment3d(intersection.p, intersection.p); else return null; } else { assert false : "Triangle3d#getIntersection(Line3d): line not parallel to the triangle's plane, but its intersection with the plane isn't a single point"; return null; } } } /** * "Cut" this triangle by the given segment and return the triangles that are created by the cut. * * @param segment The segment to cut with. * @return The newly created triangles. * * @throws IllegalArgumentException If the segment doesn't define a cut of this triangle or if it doesn't relate to * this triangle. */ @SuppressWarnings("unchecked") public <T extends Triangle3d> List<T> subdivideTriangle(IntersectionWithTriangle<T> segment) throws IllegalArgumentException { if (!this.epsilonEquals(segment.triangle)) { throw new IllegalArgumentException( "Triangle3d#subdivideTriangle(): The given intersection segment doesn't relate to this triangle."); } if (!this.sidesContain(segment.p) || !this.sidesContain(segment.p2)) { throw new IllegalArgumentException( "Triangle3d#subdivideTriangle(): Trying to subdivide a triangle by an invalid cut segment."); } List<T> triangles = new LinkedList<T>(); // a cut along a side doesn't subdivide the triangle if (segment.isWholeSideIntersection()) { triangles.add((T) this); return triangles; } // cache the two points of intersection - p1, p2 Point3d p1 = segment.getP1(); Point3d p2 = segment.getP2(); Line3d segmentAsLine = new Line3d(p1, p2); // not a case where one of the intersection points is a vertex (but two distinct intersection points exist); // if the line is a segment, neither the start point nor the end point lie inside the triangle if (!p1.epsilonEquals(p2, EPSILON) && !this.isVertex(p1) && !this.isVertex(p2) && this.sidesContain(p1) && this.sidesContain(p2) && !segmentAsLine.contains(getP1()) && !segmentAsLine.contains(getP2()) && !segmentAsLine.contains(getP3())) { // the checks with segmentAsLine may seem redundant, but it it shows it is needed due to the // non-transitivity of floating point arithmetics /* * _________________________|_p1______________________________________________________________________ * __________________v*-----*-------------------*tv1__________________________________________________ * ___________________\_____|.................../_____________________________________________________ * ____________________\____|................/,/______________________________________________________ * _____________________\___|............/,,,,/_______________________________________________________ * ______________________\__|......../,,,,,,,/________________________________________________________ * _______________________\_|..../,,,,,,,,,,/_________________________________________________________ * ________________________\|/,,,,,,,,,,,,,/__________________________________________________________ * _________________________*,p2,,,,,,,,,,/___________________________________________________________ * _________________________|\,,,,,,,,,,,/____________________________________________________________ * _________________________|_\,,,,,,,,,/_____________________________________________________________ * _________________________|__\,,,,,,,/______________________________________________________________ * _____________________________\,,,,,/_______________________________________________________________ * ______________________________\,,,/________________________________________________________________ * _______________________________\,/_________________________________________________________________ * ________________________________*tv2_______________________________________________________________ * Please view this ASCII graphics without line-breaking (or break lines at minimum 120 characters) */ // find the sides of 3D triangle which contain the intersection points p1, p2 - save them into "sides" List<Segment3d> sides = new ArrayList<Segment3d>(2); for (Segment3d edge : this.getEdges()) { if (edge.contains(p1) || edge.contains(p2)) sides.add(edge); } // set v to the vertex of 3D triangle which lies alone in the halfplane defined by the triangle's plane // and line p1p2 Point3d v = sides.get(0).getIntersection(sides.get(1)).p; // we can assume the intersection is a single // point // tv1 is a vertex of 3D triangle such that p1 lies on the segment tv1v // tv2 is a vertex of 3D triangle such that p2 lies on the segment tv2v List<Point3d> triangleVertices = new ArrayList<Point3d>(2); for (Point3d p : this.getVertices()) { if (!p.epsilonEquals(v, EPSILON)) triangleVertices.add(p); } Point3d tv1 = triangleVertices.get(0); Point3d tv2 = triangleVertices.get(1); if (!new Line3d(v, tv1).contains(p1)) { Point3d tmp = tv2; tv2 = tv1; tv1 = tmp; } // construct the three newly defined triangles Vector3d tNormal = this.getNormal(); Vector3d normal = new Triangle3d(p1, p2, v).getNormal(); if (tNormal.angle(normal) < EPSILON) triangles.add((T) createSubtriangle(p1, p2, v)); else triangles.add((T) createSubtriangle(p1, v, p2)); normal = new Triangle3d(p1, p2, tv1).getNormal(); if (tNormal.angle(normal) < EPSILON) triangles.add((T) createSubtriangle(p1, p2, tv1)); else triangles.add((T) createSubtriangle(p1, tv1, p2)); normal = new Triangle3d(p2, tv1, tv2).getNormal(); if (tNormal.angle(normal) < EPSILON) triangles.add((T) createSubtriangle(p2, tv1, tv2)); else triangles.add((T) createSubtriangle(p2, tv2, tv1)); } else if (!p1.epsilonEquals(p2, EPSILON) && (this.isVertex(p1) || this.isVertex(p2))) { // one of the intersection points is a vertex; the other inters. point is distinct from that one /* * ________________________________|__________________________________________________________________ * ________________tv1*------------*p-----------*tv2__________________________________________________ * ___________________\............|,,,,,,,,,,,,/_____________________________________________________ * ____________________\...........|,,,,,,,,,,,/______________________________________________________ * _____________________\..........|,,,,,,,,,,/_______________________________________________________ * ______________________\.........|,,,,,,,,,/________________________________________________________ * _______________________\........|,,,,,,,,/_________________________________________________________ * ________________________\.......|,,,,,,,/__________________________________________________________ * _________________________\......|,,,,,,/___________________________________________________________ * __________________________\.....|,,,,,/____________________________________________________________ * ___________________________\....|,,,,/_____________________________________________________________ * ____________________________\...|,,,/______________________________________________________________ * _____________________________\..|,,/_______________________________________________________________ * ______________________________\.|,/________________________________________________________________ * _______________________________\|/_________________________________________________________________ * ________________________________*v_________________________________________________________________ * Please view this ASCII graphics without line-breaking (or break lines at minimum 120 characters) */ // v is the intersection point which is also a vertex; p is the other intersection point Point3d v, p; if (this.isVertex(p1)) { v = getNearestVertex(p1); // robustness // v = p1; p = p2; } else { v = getNearestVertex(p2); // v = p2; p = p1; } // tv1, tv2 are the other vertices (v is the third one) of the 3D triangle List<Point3d> triangleVertices = new ArrayList<Point3d>(2); for (Point3d vert : this.getVertices()) { if (!vert.epsilonEquals(v, EPSILON)) triangleVertices.add(vert); } Point3d tv1 = triangleVertices.get(0); Point3d tv2 = triangleVertices.get(1); Vector3d tNormal = this.getNormal(); Vector3d normal = new Triangle3d(v, p, tv1).getNormal(); // add two new triangles if (tNormal.angle(normal) < EPSILON) triangles.add((T) createSubtriangle(v, p, tv1)); else triangles.add((T) createSubtriangle(v, tv1, p)); normal = new Triangle3d(v, p, tv2).getNormal(); if (tNormal.angle(normal) < EPSILON) triangles.add((T) createSubtriangle(v, p, tv2)); else triangles.add((T) createSubtriangle(v, tv2, p)); } else if (p1.epsilonEquals(p2, EPSILON) && this.isVertex(p1)) { // the line intersects the triangle in a single vertex, no need to divide the triangle triangles.add((T) this); } else if (p1.epsilonEquals(p2, EPSILON) && sidesContain(p2)) { // the segment starts in the interior of a side, no need to divide the triangle triangles.add((T) this); } else if (p1.epsilonEquals(p2, EPSILON)) { // the fold isn't parallel to the triangle's plane - something's weird throw new IllegalArgumentException( "Triangle3d#subdivideTriangle(): a cut segment not parallel to the triangle's plane"); } else { assert false : "Triangle3d#subdivideTriangle(): unexpected branch taken."; } if (triangles.size() > 1) { // remove this triangle from the neighbors' neighbors lists and add new triangles as neighbors for (Triangle3d n : neighbors) { n.neighbors.remove(this); for (T t : triangles) { Segment3d commonEdge; if (t instanceof ModelTriangle && n instanceof ModelTriangle) { commonEdge = ((ModelTriangle) n).getCommonEdge((ModelTriangle) t, false); } else { commonEdge = n.getCommonEdge(t, false); } // don't consider triangles with single common point as neighbors if (commonEdge != null && commonEdge.isNonTrivial()) { n.neighbors.add(t); t.neighbors.add(n); } } } // find and add neighbors among the new triangles int i = 0; for (T t1 : triangles) { if (i + 1 <= triangles.size() - 1) { for (T t2 : triangles.subList(i + 1, triangles.size())) { Segment3d commonEdge = t1.getCommonEdge(t2, false); // don't consider triangles with single common point as neighbors if (commonEdge != null && commonEdge.isNonTrivial()) { t1.neighbors.add(t2); t2.neighbors.add(t1); } } } i++; } } return triangles; } /** * Creates a triangle from the given points. * * The given points should define a triangle that is whole contained in this triangle. * <b>This method should be overriden in all subclasses and must return a triangle castable to this triangle's * type.</b> * * @param p1 A vertex of the triangle. * @param p2 A vertex of the triangle. * @param p3 A vertex of the triangle. * @return A triangle from the given points. */ protected Triangle3d createSubtriangle(Point3d p1, Point3d p2, Point3d p3) { return new Triangle3d(p1, p2, p3); } /** * @return The normalized normal vector to the triangle's plane. */ public Vector3d getNormal() { Vector3d normal = new Vector3d(); normal.normalize(plane.getNormal()); return normal; } /** * <p> * <b>The list of neighbors is not cloned, just the same references are copied.</b> * </p> * * {@inheritDoc} */ @Override public Triangle3d clone() { try { Triangle3d result = (Triangle3d) super.clone(); result.p1 = (Point3d) this.p1.clone(); result.p2 = (Point3d) this.p2.clone(); result.p3 = (Point3d) this.p3.clone(); result.recomputeDerivedItems(); result.neighbors.addAll(this.neighbors); return result; } catch (CloneNotSupportedException e) { return null; } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((p1 == null) ? 0 : p1.hashCode()); result = prime * result + ((p2 == null) ? 0 : p2.hashCode()); result = prime * result + ((p3 == null) ? 0 : p3.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Triangle3d other = (Triangle3d) obj; if (p1 == null) { if (other.p1 != null) return false; } else if (!p1.equals(other.p1)) return false; if (p2 == null) { if (other.p2 != null) return false; } else if (!p2.equals(other.p2)) return false; if (p3 == null) { if (other.p3 != null) return false; } else if (!p3.equals(other.p3)) return false; return true; } /** * Return <code>true</code> if the given triangle is almost equal to this one. * * @param other The triangle to compare. * @return <code>true</code> if the given triangle is almost equal to this one. */ public boolean epsilonEquals(Triangle3d other) { if (other == null) return false; if (p1 == null) { if (other.p1 != null) return false; } else if (!p1.epsilonEquals(other.p1, EPSILON)) return false; if (p2 == null) { if (other.p2 != null) return false; } else if (!p2.epsilonEquals(other.p2, EPSILON)) return false; if (p3 == null) { if (other.p3 != null) return false; } else if (!p3.epsilonEquals(other.p3, EPSILON)) return false; return true; } @Override public String toString() { return "Triangle3d [" + p1 + ", " + p2 + ", " + p3 + "]"; } }