package com.revolsys.geometry.model; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import com.revolsys.geometry.algorithm.NotRepresentableException; import com.revolsys.geometry.model.coordinates.LineSegmentUtil; import com.revolsys.geometry.model.impl.Circle; import com.revolsys.geometry.model.impl.PointDoubleXY; import com.revolsys.geometry.model.impl.TriangleLinearRing; import com.revolsys.geometry.model.segment.LineSegment; import com.revolsys.geometry.model.segment.LineSegmentDoubleGF; import com.revolsys.geometry.util.Triangles; import com.revolsys.util.MathUtil; public interface Triangle extends Polygon { static void addIntersection(final GeometryFactory geometryFactory, final Set<Point> coordinates, final Point line1Start, final Point line1End, final Point line2Start, final Point line2End) { final LineString intersections = LineSegmentUtil.getIntersection(geometryFactory, line1Start, line1End, line2Start, line2End); for (int i = 0; i < intersections.getVertexCount(); i++) { final Point point = intersections.getPoint(i); coordinates.add(point); } } static boolean containsPoint(final double x1, final double y1, final double x2, final double y2, final double x3, final double y3, final double x, final double y) { final double y2y3 = y2 - y3; final double xx3 = x - x3; final double x3x2 = x3 - x2; final double yy3 = y - y3; final double x1x3 = x1 - x3; final double y1y3 = y1 - y3; final double a = (y2y3 * xx3 + x3x2 * yy3) / (y2y3 * x1x3 + x3x2 * y1y3); if (0 <= a && a <= 1) { final double y3y1 = y3 - y1; final double b = (y3y1 * xx3 + x1x3 * yy3) / (y2y3 * x1x3 + x3x2 * y1y3); if (0 <= b && b <= 1) { final double c = 1 - a - b; if (0 <= c && c <= 1) { return true; } } } return false; } static boolean containsPoint(final double scale, final double x1, final double y1, final double x2, final double y2, final double x3, final double y3, final double x, final double y) { final double y2y3 = y2 - y3; final double xx3 = x - x3; final double x3x2 = x3 - x2; final double yy3 = y - y3; final double x1x3 = x1 - x3; final double y1y3 = y1 - y3; final double a = (y2y3 * xx3 + x3x2 * yy3) / (y2y3 * x1x3 + x3x2 * y1y3); if (0 <= a && a <= 1) { final double y3y1 = y3 - y1; final double b = (y3y1 * xx3 + x1x3 * yy3) / (y2y3 * x1x3 + x3x2 * y1y3); if (0 <= b && b <= 1) { final double c = Math.round((1 - a - b) * scale) / scale; if (0 <= c && c <= 1) { return true; } } } return false; } static boolean containsPoint(final int x1, final int y1, final int x2, final int y2, final int x3, final int y3, final int x, final int y) { final long y2y3 = y2 - y3; final long xx3 = x - x3; final long x3x2 = x3 - x2; final long yy3 = y - y3; final long x1x3 = x1 - x3; final long y1y3 = y1 - y3; final long a = (y2y3 * xx3 + x3x2 * yy3) / (y2y3 * x1x3 + x3x2 * y1y3); if (0 <= a && a <= 1) { final long y3y1 = y3 - y1; final long b = (y3y1 * xx3 + x1x3 * yy3) / (y2y3 * x1x3 + x3x2 * y1y3); if (0 <= b && b <= 1) { final long c = 1 - a - b; if (0 <= c && c <= 1) { return true; } } } return false; } static double[] getCircumcentreCoordinates(final double x1, final double y1, final double x2, final double y2, final double x3, final double y3) { final double x1MinusX3 = x1 - x3; final double x1PlusX3 = x1 + x3; final double x2MinusX3 = x2 - x3; final double x2PlusX3 = x2 + x3; final double y1MinusY3 = y1 - y3; final double y1PlusY3 = y1 + y3; final double y2MinusY3 = y2 - y3; final double y2PlusY3 = y2 + y3; final double d = (x1MinusX3 * y2MinusY3 - x2MinusX3 * y1MinusY3) * 2; final double x1MinusX3TimesX1PlusX3 = x1MinusX3 * x1PlusX3; final double y1MinusY3TimesY1PlusY3 = y1MinusY3 * y1PlusY3; final double x2MinusX3TimesX2PlusX3 = x2MinusX3 * x2PlusX3; final double y2MinusY3Times2PlusY3 = y2MinusY3 * y2PlusY3; final double x1MinusX3TimesX1PlusX3Plus1MinusY3TimesY1PlusY3 = x1MinusX3TimesX1PlusX3 + y1MinusY3TimesY1PlusY3; final double x2MinusX3TimesX2PlusX3PlusY2MinusY3Times2PlusY3 = x2MinusX3TimesX2PlusX3 + y2MinusY3Times2PlusY3; final double centreX = (x1MinusX3TimesX1PlusX3Plus1MinusY3TimesY1PlusY3 * y2MinusY3 - x2MinusX3TimesX2PlusX3PlusY2MinusY3Times2PlusY3 * y1MinusY3) / d; final double centreY = (x2MinusX3TimesX2PlusX3PlusY2MinusY3Times2PlusY3 * x1MinusX3 - x1MinusX3TimesX1PlusX3Plus1MinusY3TimesY1PlusY3 * x2MinusX3) / d; if (Double.isFinite(centreX) && Double.isFinite(centreY)) { return new double[] { centreX, centreY }; } else { throw new NotRepresentableException("Cannot get circumcentre for TRIANGLE(" + x1 + "," + y1 + " " + x2 + "," + y2 + " " + x3 + "," + y3 + ")"); } } static Point getCircumcentrePoint(final GeometryFactory geometryFactory, final double x1, final double y1, final double x2, final double y2, final double x3, final double y3) { final double[] circumcentreCoordinates = getCircumcentreCoordinates(x1, y1, x2, y2, x3, y3); return geometryFactory.point(circumcentreCoordinates); } static double getCircumcircleRadius(final double centreX, final double centreY, final double x3, final double y3) { return MathUtil.distance(centreX, centreY, x3, y3); // final double xDistanceSquared = (x3 - centreX) * (x3 - centreX); // final double yDistanceSquared = (y3 - centreY) * (y3 - centreY); // final double radiusSquared = xDistanceSquared + yDistanceSquared; // return Math.sqrt(radiusSquared); } static double getElevation(// final double x1, final double y1, double z1, // final double x2, final double y2, double z2, // final double x3, final double y3, double z3, // final double x, final double y) { final boolean finite1 = Double.isFinite(z1); final boolean finite2 = Double.isFinite(z2); final boolean finite3 = Double.isFinite(z3); if (finite1) { if (finite2) { if (finite3) { } else { z3 = (z1 + z2) / 2; } } else { if (finite3) { z2 = (z1 + z3) / 2; } else { return z1; } } } else { if (finite2) { if (finite3) { z1 = (z2 + z3) / 2; } else { return z2; } } else { if (finite3) { return z3; } else { return Double.NaN; } } } if (x == x1 && y == y1) { return z1; } else if (x == x2 && y == y2) { return z2; } else if (x == x3 && y == y3) { return z3; } else if (z1 == z2 && z2 == z3) { return z1; } // https://en.wikipedia.org/wiki/Barycentric_coordinate_system // http://www.alecjacobson.com/weblog/?p=1596 final double invDET = 1. / ((y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3)); final double l1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) * invDET; final double l2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) * invDET; final double l3 = 1. - l1 - l2; final double z = l1 * z1 + l2 * z2 + l3 * z3; return z; } static int getElevation(// final int x1, final int y1, int z1, // final int x2, final int y2, int z2, // final int x3, final int y3, int z3, // final int x, final int y) { final boolean finite1 = z1 != Integer.MIN_VALUE; final boolean finite2 = z2 != Integer.MIN_VALUE; final boolean finite3 = z3 != Integer.MIN_VALUE; if (finite1) { if (finite2) { if (finite3) { } else { z3 = (z1 + z2) / 2; } } else { if (finite3) { z2 = (z1 + z3) / 2; } else { return z1; } } } else { if (finite2) { if (finite3) { z1 = (z2 + z3) / 2; } else { return z2; } } else { if (finite3) { return z3; } else { return Integer.MIN_VALUE; } } } if (x == x1 && y == y1) { return z1; } else if (x == x2 && y == y2) { return z2; } else if (x == x3 && y == y3) { return z3; } else if (z1 == z2 && z2 == z3) { return z1; } // https://en.wikipedia.org/wiki/Barycentric_coordinate_system // http://www.alecjacobson.com/weblog/?p=1596 final double invDET = 1. / ((y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3)); final double l1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) * invDET; final double l2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) * invDET; final double l3 = 1. - l1 - l2; final double z = l1 * z1 + l2 * z2 + l3 * z3; return (int)z; } default boolean circumcircleContains(final double x, final double y) { final double x1 = getCoordinate(0, X); final double y1 = getCoordinate(0, Y); final double x2 = getCoordinate(1, X); final double y2 = getCoordinate(1, Y); final double x3 = getCoordinate(2, X); final double y3 = getCoordinate(2, Y); try { final double[] centre = getCircumcentreCoordinates(x1, y1, x2, y2, x3, y3); final double centreX = centre[X]; final double centreY = centre[Y]; final double circumcircleRadius = getCircumcircleRadius(centreX, centreY, x3, y3); final double distanceFromCentre = MathUtil.distance(centreX, centreY, x, y); return distanceFromCentre < circumcircleRadius + 0.0001; } catch (final NotRepresentableException e) { return getBoundingBox().covers(x, y); } } @Override default boolean contains(final double x, final double y) { return containsPoint(x, y); } default boolean containsPoint(final double x, final double y) { final double x1 = getX(0); final double y1 = getY(0); final double x2 = getX(1); final double y2 = getY(1); final double x3 = getX(2); final double y3 = getY(2); return containsPoint(x1, y1, x2, y2, x3, y3, x, y); } /** * Returns true if the point lies inside or on the edge of the Triangle. * * @return True if the point lies inside or on the edge of the Triangle. */ default boolean containsPoint(final Point point) { final double x = point.getX(); final double y = point.getY(); return containsPoint(x, y); } default boolean equals(final Triangle triangle) { final HashSet<Point> coords = new HashSet<>(); coords.add(triangle.getP0()); coords.add(triangle.getP1()); coords.add(triangle.getP2()); coords.add(getP0()); coords.add(getP1()); coords.add(getP2()); return coords.size() == 3; } default boolean equalsVertex2d(final int vertexIndex, final double x, final double y) { final double x1 = getX(vertexIndex); if (x1 == x) { final double y1 = getY(vertexIndex); if (y1 == y) { return true; } } return false; } /** * Computes the 2D area of this triangle. The area value is always * non-negative. * * @return the area of this triangle * * @see #signedArea() */ @Override default double getArea() { final double x1 = getX(0); final double y1 = getY(0); final double x2 = getX(1); final double y2 = getY(1); final double x3 = getX(2); final double y3 = getY(2); return Triangles.area(x1, y1, x2, y2, x3, y3); } /** * Computes the 3D area of this triangle. The value computed is always * non-negative. * * @return the 3D area of this triangle */ default double getArea3D() { final double x1 = getX(0); final double y1 = getY(0); final double z1 = getZ(0); final double x2 = getX(1); final double y2 = getY(1); final double z2 = getZ(2); final double x3 = getX(2); final double y3 = getY(2); final double z3 = getZ(2); return Triangles.area3D(x1, y1, z1, x2, y2, z2, x3, y3, z3); } @Override default int getAxisCount() { return 3; } default Point getCircumcentre() { final double x1 = getCoordinate(0, X); final double y1 = getCoordinate(0, Y); final double x2 = getCoordinate(1, X); final double y2 = getCoordinate(1, Y); final double x3 = getCoordinate(2, X); final double y3 = getCoordinate(2, Y); final GeometryFactory geometryFactory = getGeometryFactory(); return getCircumcentrePoint(geometryFactory, x1, y1, x2, y2, x3, y3); } /** * Computes the circumcircle of a triangle. The circumcircle is the smallest * circle which encloses the triangle. * * @return The circumcircle of the triangle. */ default Circle getCircumcircle() { final double x1 = getCoordinate(0, X); final double y1 = getCoordinate(0, Y); final double x2 = getCoordinate(1, X); final double y2 = getCoordinate(1, Y); final double x3 = getCoordinate(2, X); final double y3 = getCoordinate(2, Y); final GeometryFactory geometryFactory = getGeometryFactory(); final Point centre = getCircumcentrePoint(geometryFactory, x1, y1, x2, y2, x3, y3); final double centreX = centre.getX(); final double centreY = centre.getY(); final double circumcircleRadius = getCircumcircleRadius(centreX, centreY, x3, y3); return new Circle(centre, circumcircleRadius); } default BoundingBox getCircumcircleBoundingBox() { final double x1 = getCoordinate(0, X); final double y1 = getCoordinate(0, Y); final double x2 = getCoordinate(1, X); final double y2 = getCoordinate(1, Y); final double x3 = getCoordinate(2, X); final double y3 = getCoordinate(2, Y); try { final double[] centre = getCircumcentreCoordinates(x1, y1, x2, y2, x3, y3); final double centreX = centre[X]; final double centreY = centre[Y]; final double radius = getCircumcircleRadius(centreX, centreY, x3, y3); if (Double.isFinite(radius)) { final double minX = centreX - radius; final double minY = centreY - radius; final double maxX = centreX + radius; final double maxY = centreY + radius; final GeometryFactory geometryFactory = getGeometryFactory(); final double[] bounds = { minX, minY, maxX, maxY }; return geometryFactory.newBoundingBox(2, bounds); } else { return getBoundingBox(); } } catch (final NotRepresentableException e) { return getBoundingBox(); } } default double getCircumcircleRadius() { final double x1 = getCoordinate(0, X); final double y1 = getCoordinate(0, Y); final double x2 = getCoordinate(1, X); final double y2 = getCoordinate(1, Y); final double x3 = getCoordinate(2, X); final double y3 = getCoordinate(2, Y); try { final double[] centre = getCircumcentreCoordinates(x1, y1, x2, y2, x3, y3); final double centreX = centre[X]; final double centreY = centre[Y]; final double circumcircleRadius = getCircumcircleRadius(centreX, centreY, x3, y3); return circumcircleRadius; } catch (final NotRepresentableException e) { return Double.NaN; } } double getCoordinate(int vertexIndex, int axisIndex); @Override default double getCoordinate(final int ringIndex, final int vertexIndex, final int axisIndex) { if (ringIndex == 0) { return getCoordinate(vertexIndex, axisIndex); } else { return Double.NaN; } } double[] getCoordinates(); default double getElevation(final double x, final double y) { final double x1 = getX(0); final double y1 = getY(0); final double z1 = getZ(0); final double x2 = getX(1); final double y2 = getY(1); final double z2 = getZ(1); final double x3 = getX(2); final double y3 = getY(2); final double z3 = getZ(2); return getElevation(x1, y1, z1, x2, y2, z2, x3, y3, z3, x, y); } @Override default GeometryFactory getGeometryFactory() { final int axisCount = getAxisCount(); return GeometryFactory.floating(0, axisCount); } default Point getInCentre() { final double x1 = getCoordinate(0, X); final double y1 = getCoordinate(0, Y); final double x2 = getCoordinate(1, X); final double y2 = getCoordinate(1, Y); final double x3 = getCoordinate(2, X); final double y3 = getCoordinate(2, Y); final double len0 = MathUtil.distance(x2, y2, x3, y3); final double len1 = MathUtil.distance(x1, y1, x3, y3); final double len2 = MathUtil.distance(x1, y1, x2, y2); final double circum = len0 + len1 + len2; final double inCentreX = (len0 * x1 + len1 * x2 + len2 * x3) / circum; final double inCentreY = (len0 * y1 + len1 * y2 + len2 * y3) / circum; return new PointDoubleXY(inCentreX, inCentreY); } default double getM(final int vertexIndex) { return getCoordinate(vertexIndex, M); } default Point getP0() { return getPoint(0); } default Point getP1() { return getPoint(1); } default Point getP2() { return getPoint(2); } default Point getPoint(int vertexIndex) { while (vertexIndex < 0) { vertexIndex += getVertexCount(); } if (vertexIndex > getVertexCount()) { return null; } else { final int axisCount = getAxisCount(); final double[] coordinates = new double[axisCount]; for (int axisIndex = 0; axisIndex < axisCount; axisIndex++) { coordinates[axisIndex] = getCoordinate(vertexIndex, axisIndex); } final GeometryFactory geometryFactory = getGeometryFactory(); return geometryFactory.point(coordinates); } } @Override default LinearRing getRing(final int ringIndex) { if (ringIndex == 0) { return new TriangleLinearRing(this); } else { return null; } } @Override default int getRingCount() { return 1; } @Override default List<LinearRing> getRings() { return Collections.singletonList(new TriangleLinearRing(this)); } @Override default int getVertexCount() { return 4; } default double getX(final int vertexIndex) { return getCoordinate(vertexIndex, X); } default double getY(final int vertexIndex) { return getCoordinate(vertexIndex, Y); } default double getZ(final int vertexIndex) { return getCoordinate(vertexIndex, Z); } default LineSegment intersection(final GeometryFactory geometryFactory, final LineSegment line) { final Point lc0 = line.getPoint(0); final Point lc1 = line.getPoint(1); final boolean lc0Contains = containsPoint(lc0); final boolean lc1Contains = containsPoint(lc1); if (lc0Contains && lc1Contains) { return line; } else { final Set<Point> coordinates = new HashSet<>(); addIntersection(geometryFactory, coordinates, lc0, lc1, getP0(), getP1()); addIntersection(geometryFactory, coordinates, lc0, lc1, getP1(), getP2()); addIntersection(geometryFactory, coordinates, lc0, lc1, getP2(), getP0()); final Iterator<Point> coordIterator = coordinates.iterator(); if (coordIterator.hasNext()) { final Point c1 = coordIterator.next(); if (coordIterator.hasNext()) { final Point c2 = coordIterator.next(); if (coordIterator.hasNext()) { // TODO Too many intersect } return new LineSegmentDoubleGF(c1, c2); } else { return new LineSegmentDoubleGF(c1, c1); } } else { return null; } } } default boolean intersectsCircumCircle(final Point point) { final double x = point.getX(); final double y = point.getY(); return circumcircleContains(x, y); } @Override default boolean isEmpty() { return false; } }