/* * Copyright (c) 2016 Vivid Solutions. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v. 1.0 which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * * http://www.eclipse.org/org/documents/edl-v10.php. */ package org.locationtech.jts.triangulate.quadedge; import org.locationtech.jts.algorithm.HCoordinate; import org.locationtech.jts.algorithm.NotRepresentableException; import org.locationtech.jts.geom.Coordinate; /** * Models a site (node) in a {@link QuadEdgeSubdivision}. * The sites can be points on a line string representing a * linear site. * <p> * The vertex can be considered as a vector with a norm, length, inner product, cross * product, etc. Additionally, point relations (e.g., is a point to the left of a line, the circle * defined by this point and two others, etc.) are also defined in this class. * <p> * It is common to want to attach user-defined data to * the vertices of a subdivision. * One way to do this is to subclass <tt>Vertex</tt> * to carry any desired information. * * @author David Skea * @author Martin Davis */ public class Vertex { public static final int LEFT = 0; public static final int RIGHT = 1; public static final int BEYOND = 2; public static final int BEHIND = 3; public static final int BETWEEN = 4; public static final int ORIGIN = 5; public static final int DESTINATION = 6; private Coordinate p; // private int edgeNumber = -1; public Vertex(double _x, double _y) { p = new Coordinate(_x, _y); } public Vertex(double _x, double _y, double _z) { p = new Coordinate(_x, _y, _z); } public Vertex(Coordinate _p) { p = new Coordinate(_p); } public double getX() { return p.x; } public double getY() { return p.y; } public double getZ() { return p.z; } public void setZ(double _z) { p.z = _z; } public Coordinate getCoordinate() { return p; } public String toString() { return "POINT (" + p.x + " " + p.y + ")"; } public boolean equals(Vertex _x) { if (p.x == _x.getX() && p.y == _x.getY()) { return true; } else { return false; } } public boolean equals(Vertex _x, double tolerance) { if (p.distance(_x.getCoordinate()) < tolerance) { return true; } else { return false; } } public int classify(Vertex p0, Vertex p1) { Vertex p2 = this; Vertex a = p1.sub(p0); Vertex b = p2.sub(p0); double sa = a.crossProduct(b); if (sa > 0.0) return LEFT; if (sa < 0.0) return RIGHT; if ((a.getX() * b.getX() < 0.0) || (a.getY() * b.getY() < 0.0)) return BEHIND; if (a.magn() < b.magn()) return BEYOND; if (p0.equals(p2)) return ORIGIN; if (p1.equals(p2)) return DESTINATION; return BETWEEN; } /** * Computes the cross product k = u X v. * * @param v a vertex * @return returns the magnitude of u X v */ double crossProduct(Vertex v) { return (p.x * v.getY() - p.y * v.getX()); } /** * Computes the inner or dot product * * @param v a vertex * @return returns the dot product u.v */ double dot(Vertex v) { return (p.x * v.getX() + p.y * v.getY()); } /** * Computes the scalar product c(v) * * @param v a vertex * @return returns the scaled vector */ Vertex times(double c) { return (new Vertex(c * p.x, c * p.y)); } /* Vector addition */ Vertex sum(Vertex v) { return (new Vertex(p.x + v.getX(), p.y + v.getY())); } /* and subtraction */ Vertex sub(Vertex v) { return (new Vertex(p.x - v.getX(), p.y - v.getY())); } /* magnitude of vector */ double magn() { return (Math.sqrt(p.x * p.x + p.y * p.y)); } /* returns k X v (cross product). this is a vector perpendicular to v */ Vertex cross() { return (new Vertex(p.y, -p.x)); } /** ************************************************************* */ /*********************************************************************************************** * Geometric primitives / **********************************************************************************************/ /** * Tests if the vertex is inside the circle defined by * the triangle with vertices a, b, c (oriented counter-clockwise). * * @param a a vertex of the triangle * @param b a vertex of the triangle * @param c a vertex of the triangle * @return true if this vertex is in the circumcircle of (a,b,c) */ public boolean isInCircle(Vertex a, Vertex b, Vertex c) { return TrianglePredicate.isInCircleRobust(a.p, b.p, c.p, this.p); // non-robust - best to not use //return TrianglePredicate.isInCircle(a.p, b.p, c.p, this.p); } /** * Tests whether the triangle formed by this vertex and two * other vertices is in CCW orientation. * * @param b a vertex * @param c a vertex * @return true if the triangle is oriented CCW */ public final boolean isCCW(Vertex b, Vertex c) { /* // test code used to check for robustness of triArea boolean isCCW = (b.p.x - p.x) * (c.p.y - p.y) - (b.p.y - p.y) * (c.p.x - p.x) > 0; //boolean isCCW = triArea(this, b, c) > 0; boolean isCCWRobust = CGAlgorithms.orientationIndex(p, b.p, c.p) == CGAlgorithms.COUNTERCLOCKWISE; if (isCCWRobust != isCCW) System.out.println("CCW failure"); //*/ // is equal to the signed area of the triangle return (b.p.x - p.x) * (c.p.y - p.y) - (b.p.y - p.y) * (c.p.x - p.x) > 0; // original rolled code //boolean isCCW = triArea(this, b, c) > 0; //return isCCW; } public final boolean rightOf(QuadEdge e) { return isCCW(e.dest(), e.orig()); } public final boolean leftOf(QuadEdge e) { return isCCW(e.orig(), e.dest()); } private HCoordinate bisector(Vertex a, Vertex b) { // returns the perpendicular bisector of the line segment ab double dx = b.getX() - a.getX(); double dy = b.getY() - a.getY(); HCoordinate l1 = new HCoordinate(a.getX() + dx / 2.0, a.getY() + dy / 2.0, 1.0); HCoordinate l2 = new HCoordinate(a.getX() - dy + dx / 2.0, a.getY() + dx + dy / 2.0, 1.0); return new HCoordinate(l1, l2); } private double distance(Vertex v1, Vertex v2) { return Math.sqrt(Math.pow(v2.getX() - v1.getX(), 2.0) + Math.pow(v2.getY() - v1.getY(), 2.0)); } /** * Computes the value of the ratio of the circumradius to shortest edge. If smaller than some * given tolerance B, the associated triangle is considered skinny. For an equal lateral * triangle this value is 0.57735. The ratio is related to the minimum triangle angle theta by: * circumRadius/shortestEdge = 1/(2sin(theta)). * * @param b second vertex of the triangle * @param c third vertex of the triangle * @return ratio of circumradius to shortest edge. */ public double circumRadiusRatio(Vertex b, Vertex c) { Vertex x = this.circleCenter(b, c); double radius = distance(x, b); double edgeLength = distance(this, b); double el = distance(b, c); if (el < edgeLength) { edgeLength = el; } el = distance(c, this); if (el < edgeLength) { edgeLength = el; } return radius / edgeLength; } /** * returns a new vertex that is mid-way between this vertex and another end point. * * @param a the other end point. * @return the point mid-way between this and that. */ public Vertex midPoint(Vertex a) { double xm = (p.x + a.getX()) / 2.0; double ym = (p.y + a.getY()) / 2.0; double zm = (p.z + a.getZ()) / 2.0; return new Vertex(xm, ym, zm); } /** * Computes the centre of the circumcircle of this vertex and two others. * * @param b * @param c * @return the Coordinate which is the circumcircle of the 3 points. */ public Vertex circleCenter(Vertex b, Vertex c) { Vertex a = new Vertex(this.getX(), this.getY()); // compute the perpendicular bisector of cord ab HCoordinate cab = bisector(a, b); // compute the perpendicular bisector of cord bc HCoordinate cbc = bisector(b, c); // compute the intersection of the bisectors (circle radii) HCoordinate hcc = new HCoordinate(cab, cbc); Vertex cc = null; try { cc = new Vertex(hcc.getX(), hcc.getY()); } catch (NotRepresentableException nre) { System.err.println("a: " + a + " b: " + b + " c: " + c); System.err.println(nre); } return cc; } /** * For this vertex enclosed in a triangle defined by three vertices v0, v1 and v2, interpolate * a z value from the surrounding vertices. */ public double interpolateZValue(Vertex v0, Vertex v1, Vertex v2) { double x0 = v0.getX(); double y0 = v0.getY(); double a = v1.getX() - x0; double b = v2.getX() - x0; double c = v1.getY() - y0; double d = v2.getY() - y0; double det = a * d - b * c; double dx = this.getX() - x0; double dy = this.getY() - y0; double t = (d * dx - b * dy) / det; double u = (-c * dx + a * dy) / det; double z = v0.getZ() + t * (v1.getZ() - v0.getZ()) + u * (v2.getZ() - v0.getZ()); return z; } /** * Interpolates the Z-value (height) of a point enclosed in a triangle * whose vertices all have Z values. * The containing triangle must not be degenerate * (in other words, the three vertices must enclose a * non-zero area). * * @param p the point to interpolate the Z value of * @param v0 a vertex of a triangle containing the p * @param v1 a vertex of a triangle containing the p * @param v2 a vertex of a triangle containing the p * @return the interpolated Z-value (height) of the point */ public static double interpolateZ(Coordinate p, Coordinate v0, Coordinate v1, Coordinate v2) { double x0 = v0.x; double y0 = v0.y; double a = v1.x - x0; double b = v2.x - x0; double c = v1.y - y0; double d = v2.y - y0; double det = a * d - b * c; double dx = p.x - x0; double dy = p.y - y0; double t = (d * dx - b * dy) / det; double u = (-c * dx + a * dy) / det; double z = v0.z + t * (v1.z - v0.z) + u * (v2.z - v0.z); return z; } /** * Computes the interpolated Z-value for a point p lying on the segment p0-p1 * * @param p * @param p0 * @param p1 * @return the interpolated Z value */ public static double interpolateZ(Coordinate p, Coordinate p0, Coordinate p1) { double segLen = p0.distance(p1); double ptLen = p.distance(p0); double dz = p1.z - p0.z; double pz = p0.z + dz * (ptLen / segLen); return pz; } }