From 3aa338492de1780e8730f206eb9957f4ac442ac4 Mon Sep 17 00:00:00 2001 From: Thomas <thomas.musset@pasteur.fr> Date: Mon, 3 Jul 2023 16:14:56 +0200 Subject: [PATCH] updated pom, optimized code for java 11 --- pom.xml | 2 +- .../java/plugins/adufour/quickhull/Face.java | 999 +++--- .../plugins/adufour/quickhull/FaceList.java | 85 +- .../plugins/adufour/quickhull/HalfEdge.java | 461 ++- .../plugins/adufour/quickhull/QuickHull.java | 28 +- .../adufour/quickhull/QuickHull2D.java | 127 +- .../adufour/quickhull/QuickHull3D.java | 2670 ++++++++--------- .../plugins/adufour/quickhull/Vertex.java | 102 +- .../plugins/adufour/quickhull/VertexList.java | 256 +- 9 files changed, 2236 insertions(+), 2494 deletions(-) diff --git a/pom.xml b/pom.xml index 8e9aeec..b5454c5 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ </parent> <artifactId>quickhull</artifactId> - <version>1.0.3</version> + <version>2.0.0</version> <packaging>jar</packaging> diff --git a/src/main/java/plugins/adufour/quickhull/Face.java b/src/main/java/plugins/adufour/quickhull/Face.java index e38ee98..042eea7 100644 --- a/src/main/java/plugins/adufour/quickhull/Face.java +++ b/src/main/java/plugins/adufour/quickhull/Face.java @@ -1,539 +1,486 @@ +/* + * Copyright (c) 2010-2023. Institut Pasteur. + * + * This file is part of Icy. + * Icy is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Icy is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Icy. If not, see <https://www.gnu.org/licenses/>. + */ + package plugins.adufour.quickhull; import javax.vecmath.Vector3d; /** * Basic triangular face used to form the hull. - * + * * <p> * The information stored for each face consists of a planar normal, a planar offset, and a * doubly-linked list of three <a href=HalfEdge>HalfEdges</a> which surround the face in a * counter-clockwise direction. - * + * * @author John E. Lloyd, Fall 2004 */ -class Face -{ - HalfEdge he0; - private Vector3d normal; - double area; - private Vector3d centroid; - double planeOffset; - int index; - int numVerts; - - Face next; - - static final int VISIBLE = 1; - static final int NON_CONVEX = 2; - static final int DELETED = 3; - - int mark = VISIBLE; - - Vertex outside; - - public void computeCentroid(Vector3d centroid) - { - centroid.set(0, 0, 0); - HalfEdge he = he0; - do - { - centroid.add(he.head().pnt); - he = he.next; - } while (he != he0); - centroid.scale(1 / (double) numVerts); - } - - public void computeNormal(Vector3d normal, double minArea) - { - computeNormal(normal); - - if (area < minArea) - { - System.out.println("area=" + area); - // make the normal more robust by removing - // components parallel to the longest edge - - HalfEdge hedgeMax = null; - double lenSqrMax = 0; - HalfEdge hedge = he0; - do - { - double lenSqr = hedge.lengthSquared(); - if (lenSqr > lenSqrMax) - { - hedgeMax = hedge; - lenSqrMax = lenSqr; - } - hedge = hedge.next; - } while (hedge != he0); - - Vector3d p2 = hedgeMax.head().pnt; - Vector3d p1 = hedgeMax.tail().pnt; - double lenMax = Math.sqrt(lenSqrMax); - double ux = (p2.x - p1.x) / lenMax; - double uy = (p2.y - p1.y) / lenMax; - double uz = (p2.z - p1.z) / lenMax; - double dot = normal.x * ux + normal.y * uy + normal.z * uz; - normal.x -= dot * ux; - normal.y -= dot * uy; - normal.z -= dot * uz; - - normal.normalize(); - } - } - - public void computeNormal(Vector3d normal) - { - HalfEdge he1 = he0.next; - HalfEdge he2 = he1.next; - - Vector3d p0 = he0.head().pnt; - Vector3d p2 = he1.head().pnt; - - double d2x = p2.x - p0.x; - double d2y = p2.y - p0.y; - double d2z = p2.z - p0.z; - - normal.set(0, 0, 0); - - numVerts = 2; - - while (he2 != he0) - { - double d1x = d2x; - double d1y = d2y; - double d1z = d2z; - - p2 = he2.head().pnt; - d2x = p2.x - p0.x; - d2y = p2.y - p0.y; - d2z = p2.z - p0.z; - - normal.x += d1y * d2z - d1z * d2y; - normal.y += d1z * d2x - d1x * d2z; - normal.z += d1x * d2y - d1y * d2x; - - he1 = he2; - he2 = he2.next; - numVerts++; - } - area = normal.length(); - normal.scale(1 / area); - } - - private void computeNormalAndCentroid() - { - computeNormal(normal); - computeCentroid(centroid); - planeOffset = normal.dot(centroid); - int numv = 0; - HalfEdge he = he0; - do - { - numv++; - he = he.next; - } while (he != he0); - if (numv != numVerts) - { - throw new RuntimeException("face " + getVertexString() + " numVerts=" + numVerts + " should be " + numv); - } - } - - private void computeNormalAndCentroid(double minArea) - { - computeNormal(normal, minArea); - computeCentroid(centroid); - planeOffset = normal.dot(centroid); - } - - public static Face createTriangle(Vertex v0, Vertex v1, Vertex v2) - { - return createTriangle(v0, v1, v2, 0); - } - - /** - * Constructs a triangule Face from vertices v0, v1, and v2. - * - * @param v0 - * first vertex - * @param v1 - * second vertex - * @param v2 - * third vertex - */ - public static Face createTriangle(Vertex v0, Vertex v1, Vertex v2, double minArea) - { - Face face = new Face(); - HalfEdge he0 = new HalfEdge(v0, face); - HalfEdge he1 = new HalfEdge(v1, face); - HalfEdge he2 = new HalfEdge(v2, face); - - he0.prev = he2; - he0.next = he1; - he1.prev = he0; - he1.next = he2; - he2.prev = he1; - he2.next = he0; - - face.he0 = he0; - - // compute the normal and offset - face.computeNormalAndCentroid(minArea); - return face; - } - - public static Face create(Vertex[] vtxArray, int[] indices) - { - Face face = new Face(); - HalfEdge hePrev = null; - for (int i = 0; i < indices.length; i++) - { - HalfEdge he = new HalfEdge(vtxArray[indices[i]], face); - if (hePrev != null) - { - he.setPrev(hePrev); - hePrev.setNext(he); - } - else - { - face.he0 = he; - } - hePrev = he; - } - face.he0.setPrev(hePrev); - hePrev.setNext(face.he0); - - // compute the normal and offset - face.computeNormalAndCentroid(); - return face; - } - - public Face() - { - normal = new Vector3d(); - centroid = new Vector3d(); - mark = VISIBLE; - } - - /** - * Gets the i-th half-edge associated with the face. - * - * @param i - * the half-edge index, in the range 0-2. - * @return the half-edge - */ - public HalfEdge getEdge(int i) - { - HalfEdge he = he0; - while (i > 0) - { - he = he.next; - i--; - } - while (i < 0) - { - he = he.prev; - i++; - } - return he; - } - - public HalfEdge getFirstEdge() - { - return he0; - } - - /** - * Finds the half-edge within this face which has tail <code>vt</code> and head - * <code>vh</code>. - * - * @param vt - * tail point - * @param vh - * head point - * @return the half-edge, or null if none is found. - */ - public HalfEdge findEdge(Vertex vt, Vertex vh) - { - HalfEdge he = he0; - do - { - if (he.head() == vh && he.tail() == vt) - { - return he; - } - he = he.next; - } while (he != he0); - return null; - } - - /** - * Computes the distance from a point p to the plane of this face. - * - * @param p - * the point - * @return distance from the point to the plane - */ - public double distanceToPlane(Vector3d p) - { - return normal.x * p.x + normal.y * p.y + normal.z * p.z - planeOffset; - } - - /** - * Returns the normal of the plane associated with this face. - * - * @return the planar normal - */ - public Vector3d getNormal() - { - return normal; - } - - public Vector3d getCentroid() - { - return centroid; - } - - public int numVertices() - { - return numVerts; - } - - public String getVertexString() - { - String s = null; - HalfEdge he = he0; - do - { - if (s == null) - { - s = "" + he.head().index; - } - else - { - s += " " + he.head().index; - } - he = he.next; - } while (he != he0); - return s; - } - - public void getVertexIndices(int[] idxs) - { - HalfEdge he = he0; - int i = 0; - do - { - idxs[i++] = he.head().index; - he = he.next; - } while (he != he0); - } - - private Face connectHalfEdges(HalfEdge hedgePrev, HalfEdge hedge) - { - Face discardedFace = null; - - if (hedgePrev.oppositeFace() == hedge.oppositeFace()) - { // then there is a redundant edge that we can get rid off - - Face oppFace = hedge.oppositeFace(); - HalfEdge hedgeOpp; - - if (hedgePrev == he0) - { - he0 = hedge; - } - if (oppFace.numVertices() == 3) - { // then we can get rid of the opposite face altogether - hedgeOpp = hedge.getOpposite().prev.getOpposite(); - - oppFace.mark = DELETED; - discardedFace = oppFace; - } - else - { - hedgeOpp = hedge.getOpposite().next; - - if (oppFace.he0 == hedgeOpp.prev) - { - oppFace.he0 = hedgeOpp; - } - hedgeOpp.prev = hedgeOpp.prev.prev; - hedgeOpp.prev.next = hedgeOpp; - } - hedge.prev = hedgePrev.prev; - hedge.prev.next = hedge; - - hedge.opposite = hedgeOpp; - hedgeOpp.opposite = hedge; - - // oppFace was modified, so need to recompute - oppFace.computeNormalAndCentroid(); - } - else - { - hedgePrev.next = hedge; - hedge.prev = hedgePrev; - } - return discardedFace; - } - - void checkConsistency() - { - // do a sanity check on the face - HalfEdge hedge = he0; - double maxd = 0; - int numv = 0; - - if (numVerts < 3) - { - throw new RuntimeException("degenerate face: " + getVertexString()); - } - do - { - HalfEdge hedgeOpp = hedge.getOpposite(); - if (hedgeOpp == null) - { - throw new RuntimeException("face " + getVertexString() + ": " + "unreflected half edge " + hedge.getVertexString()); - } - else if (hedgeOpp.getOpposite() != hedge) - { - throw new RuntimeException("face " + getVertexString() + ": " + "opposite half edge " + hedgeOpp.getVertexString() + " has opposite " + hedgeOpp.getOpposite().getVertexString()); - } - if (hedgeOpp.head() != hedge.tail() || hedge.head() != hedgeOpp.tail()) - { - throw new RuntimeException("face " + getVertexString() + ": " + "half edge " + hedge.getVertexString() + " reflected by " + hedgeOpp.getVertexString()); - } - Face oppFace = hedgeOpp.face; - if (oppFace == null) - { - throw new RuntimeException("face " + getVertexString() + ": " + "no face on half edge " + hedgeOpp.getVertexString()); - } - else if (oppFace.mark == DELETED) - { - throw new RuntimeException("face " + getVertexString() + ": " + "opposite face " + oppFace.getVertexString() + " not on hull"); - } - double d = Math.abs(distanceToPlane(hedge.head().pnt)); - if (d > maxd) - { - maxd = d; - } - numv++; - hedge = hedge.next; - } while (hedge != he0); - - if (numv != numVerts) - { - throw new RuntimeException("face " + getVertexString() + " numVerts=" + numVerts + " should be " + numv); - } - - } - - public int mergeAdjacentFace(HalfEdge hedgeAdj, Face[] discarded) - { - Face oppFace = hedgeAdj.oppositeFace(); - int numDiscarded = 0; - - discarded[numDiscarded++] = oppFace; - oppFace.mark = DELETED; - - HalfEdge hedgeOpp = hedgeAdj.getOpposite(); - - HalfEdge hedgeAdjPrev = hedgeAdj.prev; - HalfEdge hedgeAdjNext = hedgeAdj.next; - HalfEdge hedgeOppPrev = hedgeOpp.prev; - HalfEdge hedgeOppNext = hedgeOpp.next; - - while (hedgeAdjPrev.oppositeFace() == oppFace) - { - hedgeAdjPrev = hedgeAdjPrev.prev; - hedgeOppNext = hedgeOppNext.next; - } - - while (hedgeAdjNext.oppositeFace() == oppFace) - { - hedgeOppPrev = hedgeOppPrev.prev; - hedgeAdjNext = hedgeAdjNext.next; - } - - HalfEdge hedge; - - for (hedge = hedgeOppNext; hedge != hedgeOppPrev.next; hedge = hedge.next) - { - hedge.face = this; - } - - if (hedgeAdj == he0) - { - he0 = hedgeAdjNext; - } - - // handle the half edges at the head - Face discardedFace; - - discardedFace = connectHalfEdges(hedgeOppPrev, hedgeAdjNext); - if (discardedFace != null) - { - discarded[numDiscarded++] = discardedFace; - } - - // handle the half edges at the tail - discardedFace = connectHalfEdges(hedgeAdjPrev, hedgeOppNext); - if (discardedFace != null) - { - discarded[numDiscarded++] = discardedFace; - } - - computeNormalAndCentroid(); - checkConsistency(); - - return numDiscarded; - } - - public void triangulate(FaceList newFaces, double minArea) - { - HalfEdge hedge; - - if (numVertices() < 4) - { - return; - } - - Vertex v0 = he0.head(); - - hedge = he0.next; - HalfEdge oppPrev = hedge.opposite; - Face face0 = null; - - for (hedge = hedge.next; hedge != he0.prev; hedge = hedge.next) - { - Face face = createTriangle(v0, hedge.prev.head(), hedge.head(), minArea); - face.he0.next.setOpposite(oppPrev); - face.he0.prev.setOpposite(hedge.opposite); - oppPrev = face.he0; - newFaces.add(face); - if (face0 == null) - { - face0 = face; - } - } - hedge = new HalfEdge(he0.prev.prev.head(), this); - hedge.setOpposite(oppPrev); - - hedge.prev = he0; - hedge.prev.next = hedge; - - hedge.next = he0.prev; - hedge.next.prev = hedge; - - computeNormalAndCentroid(minArea); - checkConsistency(); - - for (Face face = face0; face != null; face = face.next) - { - face.checkConsistency(); - } - - } +class Face { + HalfEdge he0; + private final Vector3d normal; + double area; + private final Vector3d centroid; + double planeOffset; + int index; + int numVerts; + + Face next; + + static final int VISIBLE = 1; + static final int NON_CONVEX = 2; + static final int DELETED = 3; + + int mark = VISIBLE; + + Vertex outside; + + public void computeCentroid(final Vector3d centroid) { + centroid.set(0, 0, 0); + HalfEdge he = he0; + do { + centroid.add(he.head().pnt); + he = he.next; + } while (he != he0); + centroid.scale(1 / (double) numVerts); + } + + public void computeNormal(final Vector3d normal, final double minArea) { + computeNormal(normal); + + if (area < minArea) { + System.out.println("area=" + area); + // make the normal more robust by removing + // components parallel to the longest edge + + HalfEdge hedgeMax = null; + double lenSqrMax = 0; + HalfEdge hedge = he0; + do { + final double lenSqr = hedge.lengthSquared(); + if (lenSqr > lenSqrMax) { + hedgeMax = hedge; + lenSqrMax = lenSqr; + } + hedge = hedge.next; + } while (hedge != he0); + + assert hedgeMax != null; + final Vector3d p2 = hedgeMax.head().pnt; + final Vector3d p1 = hedgeMax.tail().pnt; + final double lenMax = Math.sqrt(lenSqrMax); + final double ux = (p2.x - p1.x) / lenMax; + final double uy = (p2.y - p1.y) / lenMax; + final double uz = (p2.z - p1.z) / lenMax; + final double dot = normal.x * ux + normal.y * uy + normal.z * uz; + normal.x -= dot * ux; + normal.y -= dot * uy; + normal.z -= dot * uz; + + normal.normalize(); + } + } + + public void computeNormal(final Vector3d normal) { + HalfEdge he1 = he0.next; + HalfEdge he2 = he1.next; + + final Vector3d p0 = he0.head().pnt; + Vector3d p2 = he1.head().pnt; + + double d2x = p2.x - p0.x; + double d2y = p2.y - p0.y; + double d2z = p2.z - p0.z; + + normal.set(0, 0, 0); + + numVerts = 2; + + while (he2 != he0) { + final double d1x = d2x; + final double d1y = d2y; + final double d1z = d2z; + + p2 = he2.head().pnt; + d2x = p2.x - p0.x; + d2y = p2.y - p0.y; + d2z = p2.z - p0.z; + + normal.x += d1y * d2z - d1z * d2y; + normal.y += d1z * d2x - d1x * d2z; + normal.z += d1x * d2y - d1y * d2x; + + he1 = he2; + he2 = he2.next; + numVerts++; + } + area = normal.length(); + normal.scale(1 / area); + } + + private void computeNormalAndCentroid() { + computeNormal(normal); + computeCentroid(centroid); + planeOffset = normal.dot(centroid); + int numv = 0; + HalfEdge he = he0; + do { + numv++; + he = he.next; + } while (he != he0); + if (numv != numVerts) { + throw new RuntimeException("face " + getVertexString() + " numVerts=" + numVerts + " should be " + numv); + } + } + + private void computeNormalAndCentroid(final double minArea) { + computeNormal(normal, minArea); + computeCentroid(centroid); + planeOffset = normal.dot(centroid); + } + + public static Face createTriangle(final Vertex v0, final Vertex v1, final Vertex v2) { + return createTriangle(v0, v1, v2, 0); + } + + /** + * Constructs a triangule Face from vertices v0, v1, and v2. + * + * @param v0 first vertex + * @param v1 second vertex + * @param v2 third vertex + */ + public static Face createTriangle(final Vertex v0, final Vertex v1, final Vertex v2, final double minArea) { + final Face face = new Face(); + final HalfEdge he0 = new HalfEdge(v0, face); + final HalfEdge he1 = new HalfEdge(v1, face); + final HalfEdge he2 = new HalfEdge(v2, face); + + he0.prev = he2; + he0.next = he1; + he1.prev = he0; + he1.next = he2; + he2.prev = he1; + he2.next = he0; + + face.he0 = he0; + + // compute the normal and offset + face.computeNormalAndCentroid(minArea); + return face; + } + + public static Face create(final Vertex[] vtxArray, final int[] indices) { + final Face face = new Face(); + HalfEdge hePrev = null; + for (final int j : indices) { + final HalfEdge he = new HalfEdge(vtxArray[j], face); + if (hePrev != null) { + he.setPrev(hePrev); + hePrev.setNext(he); + } + else { + face.he0 = he; + } + hePrev = he; + } + assert hePrev != null; + face.he0.setPrev(hePrev); + hePrev.setNext(face.he0); + + // compute the normal and offset + face.computeNormalAndCentroid(); + return face; + } + + public Face() { + normal = new Vector3d(); + centroid = new Vector3d(); + mark = VISIBLE; + } + + /** + * Gets the i-th half-edge associated with the face. + * + * @param i the half-edge index, in the range 0-2. + * @return the half-edge + */ + public HalfEdge getEdge(int i) { + HalfEdge he = he0; + while (i > 0) { + he = he.next; + i--; + } + while (i < 0) { + he = he.prev; + i++; + } + return he; + } + + public HalfEdge getFirstEdge() { + return he0; + } + + /** + * Finds the half-edge within this face which has tail <code>vt</code> and head + * <code>vh</code>. + * + * @param vt tail point + * @param vh head point + * @return the half-edge, or null if none is found. + */ + public HalfEdge findEdge(final Vertex vt, final Vertex vh) { + HalfEdge he = he0; + do { + if (he.head() == vh && he.tail() == vt) { + return he; + } + he = he.next; + } while (he != he0); + return null; + } + + /** + * Computes the distance from a point p to the plane of this face. + * + * @param p the point + * @return distance from the point to the plane + */ + public double distanceToPlane(final Vector3d p) { + return normal.x * p.x + normal.y * p.y + normal.z * p.z - planeOffset; + } + + /** + * Returns the normal of the plane associated with this face. + * + * @return the planar normal + */ + public Vector3d getNormal() { + return normal; + } + + public Vector3d getCentroid() { + return centroid; + } + + public int numVertices() { + return numVerts; + } + + public String getVertexString() { + final StringBuilder s = new StringBuilder(); + HalfEdge he = he0; + do { + if (s.length() == 0) { + s.append(he.head().index); + } + else { + s.append(" ").append(he.head().index); + } + he = he.next; + } while (he != he0); + return s.toString(); + } + + public void getVertexIndices(final int[] idxs) { + HalfEdge he = he0; + int i = 0; + do { + idxs[i++] = he.head().index; + he = he.next; + } while (he != he0); + } + + private Face connectHalfEdges(final HalfEdge hedgePrev, final HalfEdge hedge) { + Face discardedFace = null; + + if (hedgePrev.oppositeFace() == hedge.oppositeFace()) { // then there is a redundant edge that we can get rid off + + final Face oppFace = hedge.oppositeFace(); + final HalfEdge hedgeOpp; + + if (hedgePrev == he0) { + he0 = hedge; + } + if (oppFace.numVertices() == 3) { // then we can get rid of the opposite face altogether + hedgeOpp = hedge.getOpposite().prev.getOpposite(); + + oppFace.mark = DELETED; + discardedFace = oppFace; + } + else { + hedgeOpp = hedge.getOpposite().next; + + if (oppFace.he0 == hedgeOpp.prev) { + oppFace.he0 = hedgeOpp; + } + hedgeOpp.prev = hedgeOpp.prev.prev; + hedgeOpp.prev.next = hedgeOpp; + } + hedge.prev = hedgePrev.prev; + hedge.prev.next = hedge; + + hedge.opposite = hedgeOpp; + hedgeOpp.opposite = hedge; + + // oppFace was modified, so need to recompute + oppFace.computeNormalAndCentroid(); + } + else { + hedgePrev.next = hedge; + hedge.prev = hedgePrev; + } + return discardedFace; + } + + void checkConsistency() { + // do a sanity check on the face + HalfEdge hedge = he0; + double maxd = 0; + int numv = 0; + + if (numVerts < 3) { + throw new RuntimeException("degenerate face: " + getVertexString()); + } + do { + final HalfEdge hedgeOpp = hedge.getOpposite(); + if (hedgeOpp == null) { + throw new RuntimeException("face " + getVertexString() + ": " + "unreflected half edge " + hedge.getVertexString()); + } + else if (hedgeOpp.getOpposite() != hedge) { + throw new RuntimeException("face " + getVertexString() + ": " + "opposite half edge " + hedgeOpp.getVertexString() + " has opposite " + hedgeOpp.getOpposite().getVertexString()); + } + if (hedgeOpp.head() != hedge.tail() || hedge.head() != hedgeOpp.tail()) { + throw new RuntimeException("face " + getVertexString() + ": " + "half edge " + hedge.getVertexString() + " reflected by " + hedgeOpp.getVertexString()); + } + final Face oppFace = hedgeOpp.face; + if (oppFace == null) { + throw new RuntimeException("face " + getVertexString() + ": " + "no face on half edge " + hedgeOpp.getVertexString()); + } + else if (oppFace.mark == DELETED) { + throw new RuntimeException("face " + getVertexString() + ": " + "opposite face " + oppFace.getVertexString() + " not on hull"); + } + final double d = Math.abs(distanceToPlane(hedge.head().pnt)); + if (d > maxd) { + maxd = d; + } + numv++; + hedge = hedge.next; + } while (hedge != he0); + + if (numv != numVerts) { + throw new RuntimeException("face " + getVertexString() + " numVerts=" + numVerts + " should be " + numv); + } + + } + + public int mergeAdjacentFace(final HalfEdge hedgeAdj, final Face[] discarded) { + final Face oppFace = hedgeAdj.oppositeFace(); + int numDiscarded = 0; + + discarded[numDiscarded++] = oppFace; + oppFace.mark = DELETED; + + final HalfEdge hedgeOpp = hedgeAdj.getOpposite(); + + HalfEdge hedgeAdjPrev = hedgeAdj.prev; + HalfEdge hedgeAdjNext = hedgeAdj.next; + HalfEdge hedgeOppPrev = hedgeOpp.prev; + HalfEdge hedgeOppNext = hedgeOpp.next; + + while (hedgeAdjPrev.oppositeFace() == oppFace) { + hedgeAdjPrev = hedgeAdjPrev.prev; + hedgeOppNext = hedgeOppNext.next; + } + + while (hedgeAdjNext.oppositeFace() == oppFace) { + hedgeOppPrev = hedgeOppPrev.prev; + hedgeAdjNext = hedgeAdjNext.next; + } + + HalfEdge hedge; + + for (hedge = hedgeOppNext; hedge != hedgeOppPrev.next; hedge = hedge.next) { + hedge.face = this; + } + + if (hedgeAdj == he0) { + he0 = hedgeAdjNext; + } + + // handle the half edges at the head + Face discardedFace; + + discardedFace = connectHalfEdges(hedgeOppPrev, hedgeAdjNext); + if (discardedFace != null) { + discarded[numDiscarded++] = discardedFace; + } + + // handle the half edges at the tail + discardedFace = connectHalfEdges(hedgeAdjPrev, hedgeOppNext); + if (discardedFace != null) { + discarded[numDiscarded++] = discardedFace; + } + + computeNormalAndCentroid(); + checkConsistency(); + + return numDiscarded; + } + + public void triangulate(final FaceList newFaces, final double minArea) { + HalfEdge hedge; + + if (numVertices() < 4) { + return; + } + + final Vertex v0 = he0.head(); + + hedge = he0.next; + HalfEdge oppPrev = hedge.opposite; + Face face0 = null; + + for (hedge = hedge.next; hedge != he0.prev; hedge = hedge.next) { + final Face face = createTriangle(v0, hedge.prev.head(), hedge.head(), minArea); + face.he0.next.setOpposite(oppPrev); + face.he0.prev.setOpposite(hedge.opposite); + oppPrev = face.he0; + newFaces.add(face); + if (face0 == null) { + face0 = face; + } + } + hedge = new HalfEdge(he0.prev.prev.head(), this); + hedge.setOpposite(oppPrev); + + hedge.prev = he0; + hedge.prev.next = hedge; + + hedge.next = he0.prev; + hedge.next.prev = hedge; + + computeNormalAndCentroid(minArea); + checkConsistency(); + + for (Face face = face0; face != null; face = face.next) { + face.checkConsistency(); + } + + } } diff --git a/src/main/java/plugins/adufour/quickhull/FaceList.java b/src/main/java/plugins/adufour/quickhull/FaceList.java index 3bcc85b..1713a12 100644 --- a/src/main/java/plugins/adufour/quickhull/FaceList.java +++ b/src/main/java/plugins/adufour/quickhull/FaceList.java @@ -1,46 +1,59 @@ +/* + * Copyright (c) 2010-2023. Institut Pasteur. + * + * This file is part of Icy. + * Icy is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Icy is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Icy. If not, see <https://www.gnu.org/licenses/>. + */ + package plugins.adufour.quickhull; /** * Maintains a single-linked list of faces for use by QuickHull3D */ -class FaceList -{ - private Face head; - private Face tail; +class FaceList { + private Face head; + private Face tail; - /** - * Clears this list. - */ - public void clear() - { - head = tail = null; - } + /** + * Clears this list. + */ + public void clear() { + head = tail = null; + } - /** - * Adds a vertex to the end of this list. - */ - public void add (Face vtx) - { - if (head == null) - { head = vtx; - } - else - { tail.next = vtx; - } - vtx.next = null; - tail = vtx; - } + /** + * Adds a vertex to the end of this list. + */ + public void add(final Face vtx) { + if (head == null) { + head = vtx; + } + else { + tail.next = vtx; + } + vtx.next = null; + tail = vtx; + } - public Face first() - { - return head; - } + public Face first() { + return head; + } - /** - * Returns true if this list is empty. - */ - public boolean isEmpty() - { - return head == null; - } + /** + * Returns true if this list is empty. + */ + public boolean isEmpty() { + return head == null; + } } diff --git a/src/main/java/plugins/adufour/quickhull/HalfEdge.java b/src/main/java/plugins/adufour/quickhull/HalfEdge.java index 22c6af2..1290f57 100644 --- a/src/main/java/plugins/adufour/quickhull/HalfEdge.java +++ b/src/main/java/plugins/adufour/quickhull/HalfEdge.java @@ -1,15 +1,19 @@ /* - * Copyright John E. Lloyd, 2003. All rights reserved. Permission - * to use, copy, and modify, without fee, is granted for non-commercial - * and research purposes, provided that this copyright notice appears - * in all copies. + * Copyright (c) 2010-2023. Institut Pasteur. * - * This software is distributed "as is", without any warranty, including - * any implied warranty of merchantability or fitness for a particular - * use. The authors assume no responsibility for, and shall not be liable - * for, any special, indirect, or consequential damages, or any damages - * whatsoever, arising out of or in connection with the use of this - * software. + * This file is part of Icy. + * Icy is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Icy is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Icy. If not, see <https://www.gnu.org/licenses/>. */ package plugins.adufour.quickhull; @@ -18,238 +22,211 @@ import javax.vecmath.Vector3d; /** * Represents the half-edges that surround each face in a counter-clockwise direction. - * + * * @author John E. Lloyd, Fall 2004 */ -class HalfEdge -{ - /** - * The vertex associated with the head of this half-edge. - */ - Vertex vertex; - - /** - * Triangular face associated with this half-edge. - */ - Face face; - - /** - * Next half-edge in the triangle. - */ - HalfEdge next; - - /** - * Previous half-edge in the triangle. - */ - HalfEdge prev; - - /** - * Half-edge associated with the opposite triangle adjacent to this edge. - */ - HalfEdge opposite; - - /** - * Constructs a HalfEdge with head vertex <code>v</code> and left-hand triangular face - * <code>f</code>. - * - * @param v - * head vertex - * @param f - * left-hand triangular face - */ - public HalfEdge(Vertex v, Face f) - { - vertex = v; - face = f; - } - - public HalfEdge() - { - } - - /** - * Sets the value of the next edge adjacent (counter-clockwise) to this one within the triangle. - * - * @param edge - * next adjacent edge - */ - public void setNext(HalfEdge edge) - { - next = edge; - } - - /** - * Gets the value of the next edge adjacent (counter-clockwise) to this one within the triangle. - * - * @return next adjacent edge - */ - public HalfEdge getNext() - { - return next; - } - - /** - * Sets the value of the previous edge adjacent (clockwise) to this one within the triangle. - * - * @param edge - * previous adjacent edge - */ - public void setPrev(HalfEdge edge) - { - prev = edge; - } - - /** - * Gets the value of the previous edge adjacent (clockwise) to this one within the triangle. - * - * @return previous adjacent edge - */ - public HalfEdge getPrev() - { - return prev; - } - - /** - * Returns the triangular face located to the left of this half-edge. - * - * @return left-hand triangular face - */ - public Face getFace() - { - return face; - } - - /** - * Returns the half-edge opposite to this half-edge. - * - * @return opposite half-edge - */ - public HalfEdge getOpposite() - { - return opposite; - } - - /** - * Sets the half-edge opposite to this half-edge. - * - * @param edge - * opposite half-edge - */ - public void setOpposite(HalfEdge edge) - { - opposite = edge; - edge.opposite = this; - } - - /** - * Returns the head vertex associated with this half-edge. - * - * @return head vertex - */ - public Vertex head() - { - return vertex; - } - - /** - * Returns the tail vertex associated with this half-edge. - * - * @return tail vertex - */ - public Vertex tail() - { - return prev != null ? prev.vertex : null; - } - - /** - * Returns the opposite triangular face associated with this half-edge. - * - * @return opposite triangular face - */ - public Face oppositeFace() - { - return opposite != null ? opposite.face : null; - } - - /** - * Produces a string identifying this half-edge by the point index values of its tail and head - * vertices. - * - * @return identifying string - */ - public String getVertexString() - { - if (tail() != null) - { - return "" + tail().index + "-" + head().index; - } - else - { - return "?-" + head().index; - } - } - - /** - * Returns the length of this half-edge. - * - * @return half-edge length - */ - public double length() - { - if (tail() != null) - { - Vector3d v = new Vector3d(); - v.sub(head().pnt, tail().pnt); - return v.length(); - } - else - { - return -1; - } - } - - /** - * Returns the length squared of this half-edge. - * - * @return half-edge length squared - */ - public double lengthSquared() - { - if (tail() != null) - { - Vector3d v = new Vector3d(); - v.sub(head().pnt, tail().pnt); - return v.lengthSquared(); - } - else - { - return -1; - } - } - - // /** - // * Computes nrml . (del0 X del1), where del0 and del1 - // * are the direction vectors along this halfEdge, and the - // * halfEdge he1. - // * - // * A product > 0 indicates a left turn WRT the normal - // */ - // public double turnProduct (HalfEdge he1, Vector3d nrml) - // { - // Point3d pnt0 = tail().pnt; - // Point3d pnt1 = head().pnt; - // Point3d pnt2 = he1.head().pnt; - - // double del0x = pnt1.x - pnt0.x; - // double del0y = pnt1.y - pnt0.y; - // double del0z = pnt1.z - pnt0.z; - - // double del1x = pnt2.x - pnt1.x; - // double del1y = pnt2.y - pnt1.y; - // double del1z = pnt2.z - pnt1.z; - - // return (nrml.x*(del0y*del1z - del0z*del1y) + - // nrml.y*(del0z*del1x - del0x*del1z) + - // nrml.z*(del0x*del1y - del0y*del1x)); - // } +class HalfEdge { + /** + * The vertex associated with the head of this half-edge. + */ + Vertex vertex; + + /** + * Triangular face associated with this half-edge. + */ + Face face; + + /** + * Next half-edge in the triangle. + */ + HalfEdge next; + + /** + * Previous half-edge in the triangle. + */ + HalfEdge prev; + + /** + * Half-edge associated with the opposite triangle adjacent to this edge. + */ + HalfEdge opposite; + + /** + * Constructs a HalfEdge with head vertex <code>v</code> and left-hand triangular face + * <code>f</code>. + * + * @param v head vertex + * @param f left-hand triangular face + */ + public HalfEdge(final Vertex v, final Face f) { + vertex = v; + face = f; + } + + public HalfEdge() { + } + + /** + * Sets the value of the next edge adjacent (counter-clockwise) to this one within the triangle. + * + * @param edge next adjacent edge + */ + public void setNext(final HalfEdge edge) { + next = edge; + } + + /** + * Gets the value of the next edge adjacent (counter-clockwise) to this one within the triangle. + * + * @return next adjacent edge + */ + public HalfEdge getNext() { + return next; + } + + /** + * Sets the value of the previous edge adjacent (clockwise) to this one within the triangle. + * + * @param edge previous adjacent edge + */ + public void setPrev(final HalfEdge edge) { + prev = edge; + } + + /** + * Gets the value of the previous edge adjacent (clockwise) to this one within the triangle. + * + * @return previous adjacent edge + */ + public HalfEdge getPrev() { + return prev; + } + + /** + * Returns the triangular face located to the left of this half-edge. + * + * @return left-hand triangular face + */ + public Face getFace() { + return face; + } + + /** + * Returns the half-edge opposite to this half-edge. + * + * @return opposite half-edge + */ + public HalfEdge getOpposite() { + return opposite; + } + + /** + * Sets the half-edge opposite to this half-edge. + * + * @param edge opposite half-edge + */ + public void setOpposite(final HalfEdge edge) { + opposite = edge; + edge.opposite = this; + } + + /** + * Returns the head vertex associated with this half-edge. + * + * @return head vertex + */ + public Vertex head() { + return vertex; + } + + /** + * Returns the tail vertex associated with this half-edge. + * + * @return tail vertex + */ + public Vertex tail() { + return prev != null ? prev.vertex : null; + } + + /** + * Returns the opposite triangular face associated with this half-edge. + * + * @return opposite triangular face + */ + public Face oppositeFace() { + return opposite != null ? opposite.face : null; + } + + /** + * Produces a string identifying this half-edge by the point index values of its tail and head + * vertices. + * + * @return identifying string + */ + public String getVertexString() { + if (tail() != null) { + return tail().index + "-" + head().index; + } + else { + return "?-" + head().index; + } + } + + /** + * Returns the length of this half-edge. + * + * @return half-edge length + */ + public double length() { + if (tail() != null) { + final Vector3d v = new Vector3d(); + v.sub(head().pnt, tail().pnt); + return v.length(); + } + else { + return -1; + } + } + + /** + * Returns the length squared of this half-edge. + * + * @return half-edge length squared + */ + public double lengthSquared() { + if (tail() != null) { + final Vector3d v = new Vector3d(); + v.sub(head().pnt, tail().pnt); + return v.lengthSquared(); + } + else { + return -1; + } + } + + // /** + // * Computes nrml . (del0 X del1), where del0 and del1 + // * are the direction vectors along this halfEdge, and the + // * halfEdge he1. + // * + // * A product > 0 indicates a left turn WRT the normal + // */ + // public double turnProduct (HalfEdge he1, Vector3d nrml) + // { + // Point3d pnt0 = tail().pnt; + // Point3d pnt1 = head().pnt; + // Point3d pnt2 = he1.head().pnt; + + // double del0x = pnt1.x - pnt0.x; + // double del0y = pnt1.y - pnt0.y; + // double del0z = pnt1.z - pnt0.z; + + // double del1x = pnt2.x - pnt1.x; + // double del1y = pnt2.y - pnt1.y; + // double del1z = pnt2.z - pnt1.z; + + // return (nrml.x*(del0y*del1z - del0z*del1y) + + // nrml.y*(del0z*del1x - del0x*del1z) + + // nrml.z*(del0x*del1y - del0y*del1x)); + // } } diff --git a/src/main/java/plugins/adufour/quickhull/QuickHull.java b/src/main/java/plugins/adufour/quickhull/QuickHull.java index c87cf06..1be8ced 100644 --- a/src/main/java/plugins/adufour/quickhull/QuickHull.java +++ b/src/main/java/plugins/adufour/quickhull/QuickHull.java @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2010-2023. Institut Pasteur. + * + * This file is part of Icy. + * Icy is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Icy is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Icy. If not, see <https://www.gnu.org/licenses/>. + */ + package plugins.adufour.quickhull; import icy.plugin.abstract_.Plugin; @@ -5,14 +23,12 @@ import icy.plugin.interface_.PluginLibrary; /** * Main class of the QuickHull library for Icy - * + * <p> * {@link QuickHull2D} * {@link QuickHull3D} - * - * @author Alexandre Dufour * + * @author Alexandre Dufour */ -public class QuickHull extends Plugin implements PluginLibrary -{ - +public class QuickHull extends Plugin implements PluginLibrary { + } diff --git a/src/main/java/plugins/adufour/quickhull/QuickHull2D.java b/src/main/java/plugins/adufour/quickhull/QuickHull2D.java index 8d601d1..2f2542b 100644 --- a/src/main/java/plugins/adufour/quickhull/QuickHull2D.java +++ b/src/main/java/plugins/adufour/quickhull/QuickHull2D.java @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2010-2023. Institut Pasteur. + * + * This file is part of Icy. + * Icy is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Icy is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Icy. If not, see <https://www.gnu.org/licenses/>. + */ + package plugins.adufour.quickhull; import icy.plugin.abstract_.Plugin; @@ -7,104 +25,91 @@ import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.List; -public class QuickHull2D extends Plugin -{ - public static List<Point2D> computeConvexEnvelope(List<Point2D> points) - { - ArrayList<Point2D> envelope = new ArrayList<Point2D>(); - +public class QuickHull2D extends Plugin { + public static List<Point2D> computeConvexEnvelope(final List<Point2D> points) { + final ArrayList<Point2D> envelope = new ArrayList<>(); + // find two points: right (bottom) and left (top) - + Point2D l = points.get(0); Point2D r = points.get(0); - - for (int i = 1; i < points.size(); i++) - { - Point2D p = points.get(i); + + for (int i = 1; i < points.size(); i++) { + final Point2D p = points.get(i); if (r.getX() > p.getX() || (r.getX() == p.getX() && r.getY() > p.getY())) r = p; if (l.getX() < p.getX() || (l.getX() == p.getX() && l.getY() < p.getY())) l = p; } - + // create neighbor lists - - ArrayList<Point2D> neighbors1 = new ArrayList<Point2D>(); - ArrayList<Point2D> neighbors2 = new ArrayList<Point2D>(); - - for (Point2D p : points) - { + + final ArrayList<Point2D> neighbors1 = new ArrayList<>(); + final ArrayList<Point2D> neighbors2 = new ArrayList<>(); + + for (final Point2D p : points) { if (p == l || p == r) continue; - - int upper = Line2D.relativeCCW(r.getX(), r.getY(), l.getX(), l.getY(), p.getX(), p.getY()); - - if (upper > 0) - { + + final int upper = Line2D.relativeCCW(r.getX(), r.getY(), l.getX(), l.getY(), p.getX(), p.getY()); + + if (upper > 0) { neighbors1.add(p); } - else if (upper < 0) - { + else if (upper < 0) { neighbors2.add(p); } } - + envelope.add(r); - + quickhull(envelope, r, l, neighbors1); - + envelope.add(l); - + quickhull(envelope, l, r, neighbors2); - + return envelope; } - - private static void quickhull(ArrayList<Point2D> envelope, Point2D a, Point2D b, ArrayList<Point2D> neighbors) - { + + private static void quickhull(final ArrayList<Point2D> envelope, final Point2D a, final Point2D b, final ArrayList<Point2D> neighbors) { if (neighbors.size() == 0) return; - - Point2D c = farthestpoint(a, b, neighbors); - - ArrayList<Point2D> al1 = new ArrayList<Point2D>(); - ArrayList<Point2D> al2 = new ArrayList<Point2D>(); - - for (Point2D pt : neighbors) - { + + final Point2D c = farthestpoint(a, b, neighbors); + + final ArrayList<Point2D> al1 = new ArrayList<>(); + final ArrayList<Point2D> al2 = new ArrayList<>(); + + for (final Point2D pt : neighbors) { if (pt == a || pt == b) continue; - - if (Line2D.relativeCCW(a.getX(), a.getY(), c.getX(), c.getY(), pt.getX(), pt.getY()) > 0) - { + + if (Line2D.relativeCCW(a.getX(), a.getY(), c.getX(), c.getY(), pt.getX(), pt.getY()) > 0) { al1.add(pt); } - else if (Line2D.relativeCCW(c.getX(), c.getY(), b.getX(), b.getY(), pt.getX(), pt.getY()) > 0) - { + else if (Line2D.relativeCCW(c.getX(), c.getY(), b.getX(), b.getY(), pt.getX(), pt.getY()) > 0) { al2.add(pt); } } - + quickhull(envelope, a, c, al1); - + envelope.add(c); - + quickhull(envelope, c, b, al2); } - - private static Point2D farthestpoint(Point2D a, Point2D b, ArrayList<Point2D> points) - { + + private static Point2D farthestpoint(final Point2D a, final Point2D b, final ArrayList<Point2D> points) { double maxD = -1; Point2D maxP = null; - - for (Point2D p : points) - { + + for (final Point2D p : points) { if (p == a || p == b) continue; - - double dist = Line2D.ptLineDistSq(a.getX(), a.getY(), b.getX(), b.getY(), p.getX(), p.getY()); - - if (dist > maxD) - { + + final double dist = Line2D.ptLineDistSq(a.getX(), a.getY(), b.getX(), b.getY(), p.getX(), p.getY()); + + if (dist > maxD) { maxD = dist; maxP = p; } } - + return maxP; } } diff --git a/src/main/java/plugins/adufour/quickhull/QuickHull3D.java b/src/main/java/plugins/adufour/quickhull/QuickHull3D.java index 7821d00..dac0a2f 100644 --- a/src/main/java/plugins/adufour/quickhull/QuickHull3D.java +++ b/src/main/java/plugins/adufour/quickhull/QuickHull3D.java @@ -1,40 +1,46 @@ -/** - * Copyright John E. Lloyd, 2004. All rights reserved. Permission to use, - * copy, modify and redistribute is granted, provided that this copyright - * notice is retained and the author is given credit whenever appropriate. +/* + * Copyright (c) 2010-2023. Institut Pasteur. + * + * This file is part of Icy. + * Icy is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * This software is distributed "as is", without any warranty, including - * any implied warranty of merchantability or fitness for a particular - * use. The author assumes no responsibility for, and shall not be liable - * for, any special, indirect, or consequential damages, or any damages - * whatsoever, arising out of or in connection with the use of this - * software. + * Icy is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Icy. If not, see <https://www.gnu.org/licenses/>. */ package plugins.adufour.quickhull; -import java.util.*; -import java.io.*; - import javax.vecmath.Point3d; import javax.vecmath.Vector3d; +import java.io.*; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Vector; /** - * Computes the convex hull of a set of three dimensional points. - * + * Computes the convex hull of a three-dimensional points set. + * * <p> - * The algorithm is a three dimensional implementation of Quickhull, as described in Barber, Dobkin, + * The algorithm is a three-dimensional implementation of Quickhull, as described in Barber, Dobkin, * and Huhdanpaa, <a href=http://citeseer.ist.psu.edu/barber96quickhull.html> ``The Quickhull * Algorithm for Convex Hulls''</a> (ACM Transactions on Mathematical Software, Vol. 22, No. 4, * December 1996), and has a complexity of O(n log(n)) with respect to the number of points. A * well-known C implementation of Quickhull that works for arbitrary dimensions is provided by <a * href=http://www.qhull.org>qhull</a>. - * + * * <p> * A hull is constructed by providing a set of points to either a constructor or a * {@link #build(Point3d[]) build} method. After the hull is built, its vertices and faces can be * retrieved using {@link #getVertices() getVertices} and {@link #getFaces() getFaces}. A typical * usage might look like this: - * + * * <pre> * // x y z coordinates of 6 points * Point3d[] points = new Point3d[] { new Point3d(0.0, 0.0, 0.0), new Point3d(1.0, 0.5, 0.0), new Point3d(2.0, 0.0, 0.0), new Point3d(0.5, 0.5, 0.5), new Point3d(0.0, 0.0, 2.0), @@ -53,17 +59,17 @@ import javax.vecmath.Vector3d; * for (int i = 0; i < vertices.length; i++) * { * for (int k = 0; k < faceIndices[i].length; k++) - * { + * { * System.out.print(faceIndices[i][k] + " "); - * } + * } * System.out.println(""); * } * </pre> - * + * <p> * As a convenience, there are also {@link #build(double[]) build} and * {@link #getVertices(double[]) getVertex} methods which pass point information using an array of * doubles. - * + * * <h3><a href=distTol>Robustness </a></h3> * Because this algorithm uses floating point arithmetic, it is potentially vulnerable to errors * arising from numerical imprecision. We address this problem in the same way as <a @@ -74,1451 +80,1219 @@ import javax.vecmath.Vector3d; * tolerance}. This tolerance represents the smallest distance that can be reliably computed within * the available numeric precision. It is normally computed automatically from the point data, * although an application may {@link #setExplicitDistanceTolerance set this tolerance explicitly}. - * + * * <p> * Numerical problems are more likely to arise in situations where data points lie on or within the * faces or edges of the convex hull. We have tested QuickHull3D for such situations by computing * the convex hull of a random point set, then adding additional randomly chosen points which lie * very close to the hull vertices and edges, and computing the convex hull again. The hull is * deemed correct if {@link #check check} returns <code>true</code>. These tests have been - * successful for a large number of trials and so we are confident that QuickHull3D is reasonably + * successful for a large number of trials, and so we are confident that QuickHull3D is reasonably * robust. - * + * * <h3>Merged Faces</h3> * The merging of faces means that the faces returned by QuickHull3D may be convex polygons instead * of triangles. If triangles are desired, the application may {@link #triangulate triangulate} the * faces, but it should be noted that this may result in triangles which are very small or thin and * hence difficult to perform reliable convexity tests on. In other words, triangulating a merged - * face is likely to restore the numerical problems which the merging process removed. Hence is it + * face is likely to restore the numerical problems which the merging process removed. Hence, is it * possible that, after triangulation, {@link #check check} will fail (the same behavior is observed * with triangulated output from <a href=http://www.qhull.org>qhull</a>). - * + * * <h3>Degenerate Input</h3> * It is assumed that the input points are non-degenerate in that they are not coincident, colinear, * or colplanar, and thus the convex hull has a non-zero volume. If the input points are detected to * be degenerate within the {@link #getDistanceTolerance() distance tolerance}, an * IllegalArgumentException will be thrown. - * + * * @author John E. Lloyd, Fall 2004 */ -public class QuickHull3D -{ - /** - * Specifies that (on output) vertex indices for a face should be listed in clockwise order. - */ - public static final int CLOCKWISE = 0x1; - - /** - * Specifies that (on output) the vertex indices for a face should be numbered starting from 1. - */ - public static final int INDEXED_FROM_ONE = 0x2; - - /** - * Specifies that (on output) the vertex indices for a face should be numbered starting from 0. - */ - public static final int INDEXED_FROM_ZERO = 0x4; - - /** - * Specifies that (on output) the vertex indices for a face should be numbered with respect to - * the original input points. - */ - public static final int POINT_RELATIVE = 0x8; - - /** - * Specifies that the distance tolerance should be computed automatically from the input point - * data. - */ - public static final double AUTOMATIC_TOLERANCE = -1; - - protected int findIndex = -1; - - // estimated size of the point set - protected double charLength; - - protected boolean debug = false; - - protected Vertex[] pointBuffer = new Vertex[0]; - protected int[] vertexPointIndices = new int[0]; - private Face[] discardedFaces = new Face[3]; - - private Vertex[] maxVtxs = new Vertex[3]; - private Vertex[] minVtxs = new Vertex[3]; - - protected Vector<Face> faces = new Vector<Face>(16); - protected Vector<HalfEdge> horizon = new Vector<HalfEdge>(16); - - private FaceList newFaces = new FaceList(); - private VertexList unclaimed = new VertexList(); - private VertexList claimed = new VertexList(); - - protected int numVertices; - protected int numFaces; - protected int numPoints; - - protected double explicitTolerance = AUTOMATIC_TOLERANCE; - protected double tolerance; - - /** - * Returns true if debugging is enabled. - * - * @return true is debugging is enabled - * @see QuickHull3D#setDebug - */ - public boolean getDebug() - { - return debug; - } - - /** - * Enables the printing of debugging diagnostics. - * - * @param enable - * if true, enables debugging - */ - public void setDebug(boolean enable) - { - debug = enable; - } - - /** - * Precision of a double. - */ - static private final double DOUBLE_PREC = 2.2204460492503131e-16; - - /** - * Returns the distance tolerance that was used for the most recently computed hull. The - * distance tolerance is used to determine when faces are unambiguously convex with respect to - * each other, and when points are unambiguously above or below a face plane, in the presence of - * <a href=#distTol>numerical imprecision</a>. Normally, this tolerance is computed - * automatically for each set of input points, but it can be set explicitly by the application. - * - * @return distance tolerance - * @see QuickHull3D#setExplicitDistanceTolerance - */ - public double getDistanceTolerance() - { - return tolerance; - } - - /** - * Sets an explicit distance tolerance for convexity tests. If - * {@link #AUTOMATIC_TOLERANCE AUTOMATIC_TOLERANCE} is specified (the default), then the - * tolerance will be computed automatically from the point data. - * - * @param tol - * explicit tolerance - * @see #getDistanceTolerance - */ - public void setExplicitDistanceTolerance(double tol) - { - explicitTolerance = tol; - } - - /** - * Returns the explicit distance tolerance. - * - * @return explicit tolerance - * @see #setExplicitDistanceTolerance - */ - public double getExplicitDistanceTolerance() - { - return explicitTolerance; - } - - private void addPointToFace(Vertex vtx, Face face) - { - vtx.face = face; - - if (face.outside == null) - { - claimed.add(vtx); - } - else - { - claimed.insertBefore(vtx, face.outside); - } - face.outside = vtx; - } - - private void removePointFromFace(Vertex vtx, Face face) - { - if (vtx == face.outside) - { - if (vtx.next != null && vtx.next.face == face) - { - face.outside = vtx.next; - } - else - { - face.outside = null; - } - } - claimed.delete(vtx); - } - - private Vertex removeAllPointsFromFace(Face face) - { - if (face.outside != null) - { - Vertex end = face.outside; - while (end.next != null && end.next.face == face) - { - end = end.next; - } - claimed.delete(face.outside, end); - end.next = null; - return face.outside; - } - else - { - return null; - } - } - - /** - * Creates an empty convex hull object. - */ - public QuickHull3D() - { - } - - /** - * Creates a convex hull object and initializes it to the convex hull of a set of points whose - * coordinates are given by an array of doubles. - * - * @param coords - * x, y, and z coordinates of each input point. The length of this array will be - * three times the the number of input points. - * @throws IllegalArgumentException - * the number of input points is less than four, or the points appear to be - * coincident, colinear, or coplanar. - */ - public QuickHull3D(double[] coords) throws IllegalArgumentException - { - build(coords, coords.length / 3); - } - - /** - * Creates a convex hull object and initializes it to the convex hull of a set of points. - * - * @param points - * input points. - * @throws IllegalArgumentException - * the number of input points is less than four, or the points appear to be - * coincident, colinear, or coplanar. - */ - public QuickHull3D(Point3d[] points) throws IllegalArgumentException - { - build(points, points.length); - } - - private HalfEdge findHalfEdge(Vertex tail, Vertex head) - { - // brute force ... OK, since setHull is not used much - for (Iterator<Face> it = faces.iterator(); it.hasNext();) - { - HalfEdge he = it.next().findEdge(tail, head); - if (he != null) - { - return he; - } - } - return null; - } - - protected void setHull(double[] coords, int nump, int[][] faceIndices, int numf) - { - initBuffers(nump); - setPoints(coords, nump); - computeMaxAndMin(); - for (int i = 0; i < numf; i++) - { - Face face = Face.create(pointBuffer, faceIndices[i]); - HalfEdge he = face.he0; - do - { - HalfEdge heOpp = findHalfEdge(he.head(), he.tail()); - if (heOpp != null) - { - he.setOpposite(heOpp); - } - he = he.next; - } while (he != face.he0); - faces.add(face); - } - } - - private void printQhullErrors(Process proc) throws IOException - { - boolean wrote = false; - InputStream es = proc.getErrorStream(); - while (es.available() > 0) - { - System.out.write(es.read()); - wrote = true; - } - if (wrote) - { - System.out.println(""); - } - } - - protected void setFromQhull(double[] coords, int nump, boolean triangulate) - { - String commandStr = "./qhull i"; - if (triangulate) - { - commandStr += " -Qt"; - } - try - { - Process proc = Runtime.getRuntime().exec(commandStr); - PrintStream ps = new PrintStream(proc.getOutputStream()); - StreamTokenizer stok = new StreamTokenizer(new InputStreamReader(proc.getInputStream())); - - ps.println("3 " + nump); - for (int i = 0; i < nump; i++) - { - ps.println(coords[i * 3 + 0] + " " + coords[i * 3 + 1] + " " + coords[i * 3 + 2]); - } - ps.flush(); - ps.close(); - Vector<Integer> indexList = new Vector<Integer>(3); - stok.eolIsSignificant(true); - printQhullErrors(proc); - - do - { - stok.nextToken(); - } while (stok.sval == null || !stok.sval.startsWith("MERGEexact")); - for (int i = 0; i < 4; i++) - { - stok.nextToken(); - } - if (stok.ttype != StreamTokenizer.TT_NUMBER) - { - System.out.println("Expecting number of faces"); - System.exit(1); - } - int numf = (int) stok.nval; - stok.nextToken(); // clear EOL - int[][] faceIndices = new int[numf][]; - for (int i = 0; i < numf; i++) - { - indexList.clear(); - while (stok.nextToken() != StreamTokenizer.TT_EOL) - { - if (stok.ttype != StreamTokenizer.TT_NUMBER) - { - System.out.println("Expecting face index"); - System.exit(1); - } - indexList.add(0, Integer.valueOf((int) stok.nval)); - } - faceIndices[i] = new int[indexList.size()]; - int k = 0; - for (Integer integer : indexList) - { - faceIndices[i][k++] = integer; - } - } - setHull(coords, nump, faceIndices, numf); - } - catch (Exception e) - { - e.printStackTrace(); - System.exit(1); - } - } - - /** - * Constructs the convex hull of a set of points whose coordinates are given by an array of - * doubles. - * - * @param coords - * x, y, and z coordinates of each input point. The length of this array will be - * three times the number of input points. - * @throws IllegalArgumentException - * the number of input points is less than four, or the points appear to be - * coincident, colinear, or coplanar. - */ - public void build(double[] coords) throws IllegalArgumentException - { - build(coords, coords.length / 3); - } - - /** - * Constructs the convex hull of a set of points whose coordinates are given by an array of - * doubles. - * - * @param coords - * x, y, and z coordinates of each input point. The length of this array must be at - * least three times <code>nump</code>. - * @param nump - * number of input points - * @throws IllegalArgumentException - * the number of input points is less than four or greater than 1/3 the length of - * <code>coords</code>, or the points appear to be coincident, colinear, or - * coplanar. - */ - public void build(double[] coords, int nump) throws IllegalArgumentException - { - if (nump < 4) - { - throw new IllegalArgumentException("Less than four input points specified"); - } - if (coords.length / 3 < nump) - { - throw new IllegalArgumentException("Coordinate array too small for specified number of points"); - } - initBuffers(nump); - setPoints(coords, nump); - buildHull(); - } - - /** - * Constructs the convex hull of a set of points. - * - * @param points - * input points - * @throws IllegalArgumentException - * the number of input points is less than four, or the points appear to be - * coincident, colinear, or coplanar. - */ - public void build(Point3d[] points) throws IllegalArgumentException - { - build(points, points.length); - } - - /** - * Constructs the convex hull of a set of points. - * - * @param points - * input points - * @param nump - * number of input points - * @throws IllegalArgumentException - * the number of input points is less than four or greater then the length of - * <code>points</code>, or the points appear to be coincident, colinear, or - * coplanar. - */ - public void build(Point3d[] points, int nump) throws IllegalArgumentException - { - if (nump < 4) - { - throw new IllegalArgumentException("Less than four input points specified"); - } - if (points.length < nump) - { - throw new IllegalArgumentException("Point array too small for specified number of points"); - } - initBuffers(nump); - setPoints(points, nump); - buildHull(); - } - - /** - * Triangulates any non-triangular hull faces. In some cases, due to precision issues, the - * resulting triangles may be very thin or small, and hence appear to be non-convex (this same - * limitation is present in <a href=http://www.qhull.org>qhull</a>). - */ - public void triangulate() - { - double minArea = 1000 * charLength * DOUBLE_PREC; - newFaces.clear(); - for (Face face : faces) - { - if (face.mark == Face.VISIBLE) - { - face.triangulate(newFaces, minArea); - // splitFace (face); - } - } - for (Face face = newFaces.first(); face != null; face = face.next) - { - faces.add(face); - } - } - - // private void splitFace (Face face) - // { - // Face newFace = face.split(); - // if (newFace != null) - // { newFaces.add (newFace); - // splitFace (newFace); - // splitFace (face); - // } - // } - - protected void initBuffers(int nump) - { - if (pointBuffer.length < nump) - { - Vertex[] newBuffer = new Vertex[nump]; - vertexPointIndices = new int[nump]; - for (int i = 0; i < pointBuffer.length; i++) - { - newBuffer[i] = pointBuffer[i]; - } - for (int i = pointBuffer.length; i < nump; i++) - { - newBuffer[i] = new Vertex(); - } - pointBuffer = newBuffer; - } - faces.clear(); - claimed.clear(); - numFaces = 0; - numPoints = nump; - } - - protected void setPoints(double[] coords, int nump) - { - for (int i = 0; i < nump; i++) - { - Vertex vtx = pointBuffer[i]; - vtx.pnt.set(coords[i * 3 + 0], coords[i * 3 + 1], coords[i * 3 + 2]); - vtx.index = i; - } - } - - protected void setPoints(Point3d[] pnts, int nump) - { - for (int i = 0; i < nump; i++) - { - Vertex vtx = pointBuffer[i]; - vtx.pnt.set(pnts[i]); - vtx.index = i; - } - } - - protected void computeMaxAndMin() - { - Vector3d max = new Vector3d(); - Vector3d min = new Vector3d(); - - for (int i = 0; i < 3; i++) - { - maxVtxs[i] = minVtxs[i] = pointBuffer[0]; - } - max.set(pointBuffer[0].pnt); - min.set(pointBuffer[0].pnt); - - for (int i = 1; i < numPoints; i++) - { - Vector3d pnt = pointBuffer[i].pnt; - if (pnt.x > max.x) - { - max.x = pnt.x; - maxVtxs[0] = pointBuffer[i]; - } - else if (pnt.x < min.x) - { - min.x = pnt.x; - minVtxs[0] = pointBuffer[i]; - } - if (pnt.y > max.y) - { - max.y = pnt.y; - maxVtxs[1] = pointBuffer[i]; - } - else if (pnt.y < min.y) - { - min.y = pnt.y; - minVtxs[1] = pointBuffer[i]; - } - if (pnt.z > max.z) - { - max.z = pnt.z; - maxVtxs[2] = pointBuffer[i]; - } - else if (pnt.z < min.z) - { - min.z = pnt.z; - maxVtxs[2] = pointBuffer[i]; - } - } - - // this epsilon formula comes from QuickHull, and I'm - // not about to quibble. - charLength = Math.max(max.x - min.x, max.y - min.y); - charLength = Math.max(max.z - min.z, charLength); - if (explicitTolerance == AUTOMATIC_TOLERANCE) - { - tolerance = 3 * DOUBLE_PREC * (Math.max(Math.abs(max.x), Math.abs(min.x)) + Math.max(Math.abs(max.y), Math.abs(min.y)) + Math.max(Math.abs(max.z), Math.abs(min.z))); - } - else - { - tolerance = explicitTolerance; - } - } - - /** - * Creates the initial simplex from which the hull will be built. - */ - protected void createInitialSimplex() throws IllegalArgumentException - { - double max = 0; - int imax = 0; - - { - double diff; - - diff = maxVtxs[0].pnt.x - minVtxs[0].pnt.x; - if (diff > max) - { - max = diff; - imax = 0; - } - - diff = maxVtxs[1].pnt.y - minVtxs[1].pnt.y; - if (diff > max) - { - max = diff; - imax = 0; - } - - diff = maxVtxs[1].pnt.z - minVtxs[1].pnt.z; - if (diff > max) - { - max = diff; - imax = 0; - } - } - - if (max <= tolerance) - { - throw new IllegalArgumentException("Input points appear to be coincident"); - } - Vertex[] vtx = new Vertex[4]; - // set first two vertices to be those with the greatest - // one dimensional separation - - vtx[0] = maxVtxs[imax]; - vtx[1] = minVtxs[imax]; - - // set third vertex to be the vertex farthest from - // the line between vtx0 and vtx1 - Vector3d u01 = new Vector3d(); - Vector3d diff02 = new Vector3d(); - Vector3d nrml = new Vector3d(); - Vector3d xprod = new Vector3d(); - double maxSqr = 0; - u01.sub(vtx[1].pnt, vtx[0].pnt); - u01.normalize(); - for (int i = 0; i < numPoints; i++) - { - diff02.sub(pointBuffer[i].pnt, vtx[0].pnt); - xprod.cross(u01, diff02); - double lenSqr = xprod.lengthSquared(); - if (lenSqr > maxSqr && pointBuffer[i] != vtx[0] && // paranoid - pointBuffer[i] != vtx[1]) - { - maxSqr = lenSqr; - vtx[2] = pointBuffer[i]; - nrml.set(xprod); - } - } - if (Math.sqrt(maxSqr) <= 100 * tolerance) - { - throw new IllegalArgumentException("Input points appear to be coplanar"); - } - nrml.normalize(); - - double maxDist = 0; - double d0 = vtx[2].pnt.dot(nrml); - for (int i = 0; i < numPoints; i++) - { - double dist = Math.abs(pointBuffer[i].pnt.dot(nrml) - d0); - if (dist > maxDist && pointBuffer[i] != vtx[0] && // paranoid - pointBuffer[i] != vtx[1] && pointBuffer[i] != vtx[2]) - { - maxDist = dist; - vtx[3] = pointBuffer[i]; - } - } - if (Math.abs(maxDist) <= 100 * tolerance) - { - throw new IllegalArgumentException("Input points appear to be coplanar"); - } - - if (debug) - { - System.out.println("initial vertices:"); - System.out.println(vtx[0].index + ": " + vtx[0].pnt); - System.out.println(vtx[1].index + ": " + vtx[1].pnt); - System.out.println(vtx[2].index + ": " + vtx[2].pnt); - System.out.println(vtx[3].index + ": " + vtx[3].pnt); - } - - Face[] tris = new Face[4]; - - if (vtx[3].pnt.dot(nrml) - d0 < 0) - { - tris[0] = Face.createTriangle(vtx[0], vtx[1], vtx[2]); - tris[1] = Face.createTriangle(vtx[3], vtx[1], vtx[0]); - tris[2] = Face.createTriangle(vtx[3], vtx[2], vtx[1]); - tris[3] = Face.createTriangle(vtx[3], vtx[0], vtx[2]); - - for (int i = 0; i < 3; i++) - { - int k = (i + 1) % 3; - tris[i + 1].getEdge(1).setOpposite(tris[k + 1].getEdge(0)); - tris[i + 1].getEdge(2).setOpposite(tris[0].getEdge(k)); - } - } - else - { - tris[0] = Face.createTriangle(vtx[0], vtx[2], vtx[1]); - tris[1] = Face.createTriangle(vtx[3], vtx[0], vtx[1]); - tris[2] = Face.createTriangle(vtx[3], vtx[1], vtx[2]); - tris[3] = Face.createTriangle(vtx[3], vtx[2], vtx[0]); - - for (int i = 0; i < 3; i++) - { - int k = (i + 1) % 3; - tris[i + 1].getEdge(0).setOpposite(tris[k + 1].getEdge(1)); - tris[i + 1].getEdge(2).setOpposite(tris[0].getEdge((3 - i) % 3)); - } - } - - for (int i = 0; i < 4; i++) - { - faces.add(tris[i]); - } - - for (int i = 0; i < numPoints; i++) - { - Vertex v = pointBuffer[i]; - - if (v == vtx[0] || v == vtx[1] || v == vtx[2] || v == vtx[3]) - { - continue; - } - - maxDist = tolerance; - Face maxFace = null; - for (int k = 0; k < 4; k++) - { - double dist = tris[k].distanceToPlane(v.pnt); - if (dist > maxDist) - { - maxFace = tris[k]; - maxDist = dist; - } - } - if (maxFace != null) - { - addPointToFace(v, maxFace); - } - } - } - - /** - * Returns the number of vertices in this hull. - * - * @return number of vertices - */ - public int getNumVertices() - { - return numVertices; - } - - /** - * Returns the vertex points in this hull. - * - * @return array of vertex points - * @see QuickHull3D#getVertices(double[]) - * @see QuickHull3D#getFaces() - */ - public Point3d[] getVertices() - { - Point3d[] vtxs = new Point3d[numVertices]; - for (int i = 0; i < numVertices; i++) - { - vtxs[i] = new Point3d(pointBuffer[vertexPointIndices[i]].pnt); - } - return vtxs; - } - - /** - * Returns the coordinates of the vertex points of this hull. - * - * @param coords - * returns the x, y, z coordinates of each vertex. This length of this array must be - * at least three times the number of vertices. - * @return the number of vertices - * @see QuickHull3D#getVertices() - * @see QuickHull3D#getFaces() - */ - public int getVertices(double[] coords) - { - for (int i = 0; i < numVertices; i++) - { - Point3d pnt = new Point3d(pointBuffer[vertexPointIndices[i]].pnt); - coords[i * 3 + 0] = pnt.x; - coords[i * 3 + 1] = pnt.y; - coords[i * 3 + 2] = pnt.z; - } - return numVertices; - } - - /** - * Returns an array specifing the index of each hull vertex with respect to the original input - * points. - * - * @return vertex indices with respect to the original points - */ - public int[] getVertexPointIndices() - { - int[] indices = new int[numVertices]; - for (int i = 0; i < numVertices; i++) - { - indices[i] = vertexPointIndices[i]; - } - return indices; - } - - /** - * Returns the number of faces in this hull. - * - * @return number of faces - */ - public int getNumFaces() - { - return faces.size(); - } - - /** - * Returns the faces associated with this hull. - * - * <p> - * Each face is represented by an integer array which gives the indices of the vertices. These - * indices are numbered relative to the hull vertices, are zero-based, and are arranged - * counter-clockwise. More control over the index format can be obtained using - * {@link #getFaces(int) getFaces(indexFlags)}. - * - * @return array of integer arrays, giving the vertex indices for each face. - * @see QuickHull3D#getVertices() - * @see QuickHull3D#getFaces(int) - */ - public int[][] getFaces() - { - return getFaces(0); - } - - /** - * Returns the faces associated with this hull. - * - * <p> - * Each face is represented by an integer array which gives the indices of the vertices. By - * default, these indices are numbered with respect to the hull vertices (as opposed to the - * input points), are zero-based, and are arranged counter-clockwise. However, this can be - * changed by setting {@link #POINT_RELATIVE POINT_RELATIVE}, - * {@link #INDEXED_FROM_ONE INDEXED_FROM_ONE}, or {@link #CLOCKWISE CLOCKWISE} in the - * indexFlags parameter. - * - * @param indexFlags - * specifies index characteristics (0 results in the default) - * @return array of integer arrays, giving the vertex indices for each face. - * @see QuickHull3D#getVertices() - */ - public int[][] getFaces(int indexFlags) - { - int[][] allFaces = new int[faces.size()][]; - int k = 0; - for (Face face : faces) - { - allFaces[k] = new int[face.numVertices()]; - getFaceIndices(allFaces[k], face, indexFlags); - k++; - } - return allFaces; - } - - /** - * Prints the vertices and faces of this hull to the stream ps. - * - * <p> - * This is done using the Alias Wavefront .obj file format, with the vertices printed first - * (each preceding by the letter <code>v</code>), followed by the vertex indices for each - * face (each preceded by the letter <code>f</code>). - * - * <p> - * The face indices are numbered with respect to the hull vertices (as opposed to the input - * points), with a lowest index of 1, and are arranged counter-clockwise. More control over the - * index format can be obtained using {@link #print(PrintStream,int) print(ps,indexFlags)}. - * - * @param ps - * stream used for printing - * @see QuickHull3D#print(PrintStream,int) - * @see QuickHull3D#getVertices() - * @see QuickHull3D#getFaces() - */ - public void print(PrintStream ps) - { - print(ps, 0); - } - - /** - * Prints the vertices and faces of this hull to the stream ps. - * - * <p> - * This is done using the Alias Wavefront .obj file format, with the vertices printed first - * (each preceding by the letter <code>v</code>), followed by the vertex indices for each - * face (each preceded by the letter <code>f</code>). - * - * <p> - * By default, the face indices are numbered with respect to the hull vertices (as opposed to - * the input points), with a lowest index of 1, and are arranged counter-clockwise. However, - * this can be changed by setting {@link #POINT_RELATIVE POINT_RELATIVE}, - * {@link #INDEXED_FROM_ONE INDEXED_FROM_ZERO}, or {@link #CLOCKWISE CLOCKWISE} in the - * indexFlags parameter. - * - * @param ps - * stream used for printing - * @param indexFlags - * specifies index characteristics (0 results in the default). - * @see QuickHull3D#getVertices() - * @see QuickHull3D#getFaces() - */ - public void print(PrintStream ps, int indexFlags) - { - if ((indexFlags & INDEXED_FROM_ZERO) == 0) - { - indexFlags |= INDEXED_FROM_ONE; - } - for (int i = 0; i < numVertices; i++) - { - Vector3d pnt = pointBuffer[vertexPointIndices[i]].pnt; - ps.println("v " + pnt.x + " " + pnt.y + " " + pnt.z); - } - for (Face face : faces) - { - int[] indices = new int[face.numVertices()]; - getFaceIndices(indices, face, indexFlags); - - ps.print("f"); - for (int k = 0; k < indices.length; k++) - { - ps.print(" " + indices[k]); - } - ps.println(""); - } - } - - private void getFaceIndices(int[] indices, Face face, int flags) - { - boolean ccw = ((flags & CLOCKWISE) == 0); - boolean indexedFromOne = ((flags & INDEXED_FROM_ONE) != 0); - boolean pointRelative = ((flags & POINT_RELATIVE) != 0); - - HalfEdge hedge = face.he0; - int k = 0; - do - { - int idx = hedge.head().index; - if (pointRelative) - { - idx = vertexPointIndices[idx]; - } - if (indexedFromOne) - { - idx++; - } - indices[k++] = idx; - hedge = (ccw ? hedge.next : hedge.prev); - } while (hedge != face.he0); - } - - protected void resolveUnclaimedPoints(FaceList newFaces) - { - Vertex vtxNext = unclaimed.first(); - for (Vertex vtx = vtxNext; vtx != null; vtx = vtxNext) - { - vtxNext = vtx.next; - - double maxDist = tolerance; - Face maxFace = null; - for (Face newFace = newFaces.first(); newFace != null; newFace = newFace.next) - { - if (newFace.mark == Face.VISIBLE) - { - double dist = newFace.distanceToPlane(vtx.pnt); - if (dist > maxDist) - { - maxDist = dist; - maxFace = newFace; - } - if (maxDist > 1000 * tolerance) - { - break; - } - } - } - if (maxFace != null) - { - addPointToFace(vtx, maxFace); - if (debug && vtx.index == findIndex) - { - System.out.println(findIndex + " CLAIMED BY " + maxFace.getVertexString()); - } - } - else - { - if (debug && vtx.index == findIndex) - { - System.out.println(findIndex + " DISCARDED"); - } - } - } - } - - protected void deleteFacePoints(Face face, Face absorbingFace) - { - Vertex faceVtxs = removeAllPointsFromFace(face); - if (faceVtxs != null) - { - if (absorbingFace == null) - { - unclaimed.addAll(faceVtxs); - } - else - { - Vertex vtxNext = faceVtxs; - for (Vertex vtx = vtxNext; vtx != null; vtx = vtxNext) - { - vtxNext = vtx.next; - double dist = absorbingFace.distanceToPlane(vtx.pnt); - if (dist > tolerance) - { - addPointToFace(vtx, absorbingFace); - } - else - { - unclaimed.add(vtx); - } - } - } - } - } - - private static final int NONCONVEX_WRT_LARGER_FACE = 1; - private static final int NONCONVEX = 2; - - protected double oppFaceDistance(HalfEdge he) - { - return he.face.distanceToPlane(he.opposite.face.getCentroid()); - } - - private boolean doAdjacentMerge(Face face, int mergeType) - { - HalfEdge hedge = face.he0; - - boolean convex = true; - do - { - Face oppFace = hedge.oppositeFace(); - boolean merge = false; - - if (mergeType == NONCONVEX) - { // then merge faces if they are definitively non-convex - if (oppFaceDistance(hedge) > -tolerance || oppFaceDistance(hedge.opposite) > -tolerance) - { - merge = true; - } - } - else - // mergeType == NONCONVEX_WRT_LARGER_FACE - { // merge faces if they are parallel or non-convex - // wrt to the larger face; otherwise, just mark - // the face non-convex for the second pass. - if (face.area > oppFace.area) - { - if (oppFaceDistance(hedge) > -tolerance) - { - merge = true; - } - else if (oppFaceDistance(hedge.opposite) > -tolerance) - { - convex = false; - } - } - else - { - if (oppFaceDistance(hedge.opposite) > -tolerance) - { - merge = true; - } - else if (oppFaceDistance(hedge) > -tolerance) - { - convex = false; - } - } - } - - if (merge) - { - if (debug) - { - System.out.println(" merging " + face.getVertexString() + " and " + oppFace.getVertexString()); - } - - int numd = face.mergeAdjacentFace(hedge, discardedFaces); - for (int i = 0; i < numd; i++) - { - deleteFacePoints(discardedFaces[i], face); - } - if (debug) - { - System.out.println(" result: " + face.getVertexString()); - } - return true; - } - hedge = hedge.next; - } while (hedge != face.he0); - if (!convex) - { - face.mark = Face.NON_CONVEX; - } - return false; - } - - protected void calculateHorizon(Vector3d eyePnt, HalfEdge edge0, Face face, Vector<HalfEdge> horizon) - { - // oldFaces.add (face); - deleteFacePoints(face, null); - face.mark = Face.DELETED; - if (debug) - { - System.out.println(" visiting face " + face.getVertexString()); - } - HalfEdge edge; - if (edge0 == null) - { - edge0 = face.getEdge(0); - edge = edge0; - } - else - { - edge = edge0.getNext(); - } - do - { - Face oppFace = edge.oppositeFace(); - if (oppFace.mark == Face.VISIBLE) - { - if (oppFace.distanceToPlane(eyePnt) > tolerance) - { - calculateHorizon(eyePnt, edge.getOpposite(), oppFace, horizon); - } - else - { - horizon.add(edge); - if (debug) - { - System.out.println(" adding horizon edge " + edge.getVertexString()); - } - } - } - edge = edge.getNext(); - } while (edge != edge0); - } - - private HalfEdge addAdjoiningFace(Vertex eyeVtx, HalfEdge he) - { - Face face = Face.createTriangle(eyeVtx, he.tail(), he.head()); - faces.add(face); - face.getEdge(-1).setOpposite(he.getOpposite()); - return face.getEdge(0); - } - - protected void addNewFaces(FaceList newFaces, Vertex eyeVtx, Vector<HalfEdge> horizon) - { - newFaces.clear(); - - HalfEdge hedgeSidePrev = null; - HalfEdge hedgeSideBegin = null; - - for (HalfEdge horizonHe : horizon) - { - HalfEdge hedgeSide = addAdjoiningFace(eyeVtx, horizonHe); - if (debug) - { - System.out.println("new face: " + hedgeSide.face.getVertexString()); - } - if (hedgeSidePrev != null) - { - hedgeSide.next.setOpposite(hedgeSidePrev); - } - else - { - hedgeSideBegin = hedgeSide; - } - newFaces.add(hedgeSide.getFace()); - hedgeSidePrev = hedgeSide; - } - hedgeSideBegin.next.setOpposite(hedgeSidePrev); - } - - protected Vertex nextPointToAdd() - { - if (!claimed.isEmpty()) - { - Face eyeFace = claimed.first().face; - Vertex eyeVtx = null; - double maxDist = 0; - for (Vertex vtx = eyeFace.outside; vtx != null && vtx.face == eyeFace; vtx = vtx.next) - { - double dist = eyeFace.distanceToPlane(vtx.pnt); - if (dist > maxDist) - { - maxDist = dist; - eyeVtx = vtx; - } - } - return eyeVtx; - } - else - { - return null; - } - } - - protected void addPointToHull(Vertex eyeVtx) - { - horizon.clear(); - unclaimed.clear(); - - if (debug) - { - System.out.println("Adding point: " + eyeVtx.index); - System.out.println(" which is " + eyeVtx.face.distanceToPlane(eyeVtx.pnt) + " above face " + eyeVtx.face.getVertexString()); - } - removePointFromFace(eyeVtx, eyeVtx.face); - calculateHorizon(eyeVtx.pnt, null, eyeVtx.face, horizon); - newFaces.clear(); - addNewFaces(newFaces, eyeVtx, horizon); - - // first merge pass ... merge faces which are non-convex - // as determined by the larger face - - for (Face face = newFaces.first(); face != null; face = face.next) - { - if (face.mark == Face.VISIBLE) - { - while (doAdjacentMerge(face, NONCONVEX_WRT_LARGER_FACE)) - ; - } - } - // second merge pass ... merge faces which are non-convex - // wrt either face - for (Face face = newFaces.first(); face != null; face = face.next) - { - if (face.mark == Face.NON_CONVEX) - { - face.mark = Face.VISIBLE; - while (doAdjacentMerge(face, NONCONVEX)) - ; - } - } - resolveUnclaimedPoints(newFaces); - } - - protected void buildHull() - { - int cnt = 0; - Vertex eyeVtx; - - computeMaxAndMin(); - createInitialSimplex(); - while ((eyeVtx = nextPointToAdd()) != null) - { - addPointToHull(eyeVtx); - cnt++; - if (debug) - { - System.out.println("iteration " + cnt + " done"); - } - } - reindexFacesAndVertices(); - if (debug) - { - System.out.println("hull done"); - } - } - - private void markFaceVertices(Face face, int mark) - { - HalfEdge he0 = face.getFirstEdge(); - HalfEdge he = he0; - do - { - he.head().index = mark; - he = he.next; - } while (he != he0); - } - - protected void reindexFacesAndVertices() - { - for (int i = 0; i < numPoints; i++) - { - pointBuffer[i].index = -1; - } - // remove inactive faces and mark active vertices - numFaces = 0; - for (Iterator<Face> it = faces.iterator(); it.hasNext();) - { - Face face = (Face) it.next(); - if (face.mark != Face.VISIBLE) - { - it.remove(); - } - else - { - markFaceVertices(face, 0); - numFaces++; - } - } - // reindex vertices - numVertices = 0; - for (int i = 0; i < numPoints; i++) - { - Vertex vtx = pointBuffer[i]; - if (vtx.index == 0) - { - vertexPointIndices[numVertices] = i; - vtx.index = numVertices++; - } - } - } - - protected boolean checkFaceConvexity(Face face, double tol, PrintStream ps) - { - double dist; - HalfEdge he = face.he0; - do - { - face.checkConsistency(); - // make sure edge is convex - dist = oppFaceDistance(he); - if (dist > tol) - { - if (ps != null) - { - ps.println("Edge " + he.getVertexString() + " non-convex by " + dist); - } - return false; - } - dist = oppFaceDistance(he.opposite); - if (dist > tol) - { - if (ps != null) - { - ps.println("Opposite edge " + he.opposite.getVertexString() + " non-convex by " + dist); - } - return false; - } - if (he.next.oppositeFace() == he.oppositeFace()) - { - if (ps != null) - { - ps.println("Redundant vertex " + he.head().index + " in face " + face.getVertexString()); - } - return false; - } - he = he.next; - } while (he != face.he0); - return true; - } - - protected boolean checkFaces(double tol, PrintStream ps) - { - // check edge convexity - boolean convex = true; - for (Face face : faces) - { - if (face.mark == Face.VISIBLE) - { - if (!checkFaceConvexity(face, tol, ps)) - { - convex = false; - } - } - } - return convex; - } - - /** - * Checks the correctness of the hull using the distance tolerance returned by - * {@link QuickHull3D#getDistanceTolerance getDistanceTolerance}; see - * {@link QuickHull3D#check(PrintStream,double) check(PrintStream,double)} for details. - * - * @param ps - * print stream for diagnostic messages; may be set to <code>null</code> if no - * messages are desired. - * @return true if the hull is valid - * @see QuickHull3D#check(PrintStream,double) - */ - public boolean check(PrintStream ps) - { - return check(ps, getDistanceTolerance()); - } - - /** - * Checks the correctness of the hull. This is done by making sure that no faces are non-convex - * and that no points are outside any face. These tests are performed using the distance - * tolerance <i>tol</i>. Faces are considered non-convex if any edge is non-convex, and an edge - * is non-convex if the centroid of either adjoining face is more than <i>tol</i> above the - * plane of the other face. Similarly, a point is considered outside a face if its distance to - * that face's plane is more than 10 times <i>tol</i>. - * - * <p> - * If the hull has been {@link #triangulate triangulated}, then this routine may fail if some - * of the resulting triangles are very small or thin. - * - * @param ps - * print stream for diagnostic messages; may be set to <code>null</code> if no - * messages are desired. - * @param tol - * distance tolerance - * @return true if the hull is valid - * @see QuickHull3D#check(PrintStream) - */ - public boolean check(PrintStream ps, double tol) - - { - // check to make sure all edges are fully connected - // and that the edges are convex - double dist; - double pointTol = 10 * tol; - - if (!checkFaces(tolerance, ps)) - { - return false; - } - - // check point inclusion - - for (int i = 0; i < numPoints; i++) - { - Vector3d pnt = pointBuffer[i].pnt; - for (Face face : faces) - { - if (face.mark == Face.VISIBLE) - { - dist = face.distanceToPlane(pnt); - if (dist > pointTol) - { - if (ps != null) - { - ps.println("Point " + i + " " + dist + " above face " + face.getVertexString()); - } - return false; - } - } - } - } - return true; - } +public class QuickHull3D { + /** + * Specifies that (on output) vertex indices for a face should be listed in clockwise order. + */ + public static final int CLOCKWISE = 0x1; + + /** + * Specifies that (on output) the vertex indices for a face should be numbered starting from 1. + */ + public static final int INDEXED_FROM_ONE = 0x2; + + /** + * Specifies that (on output) the vertex indices for a face should be numbered starting from 0. + */ + public static final int INDEXED_FROM_ZERO = 0x4; + + /** + * Specifies that (on output) the vertex indices for a face should be numbered with respect to + * the original input points. + */ + public static final int POINT_RELATIVE = 0x8; + + /** + * Specifies that the distance tolerance should be computed automatically from the input point + * data. + */ + public static final double AUTOMATIC_TOLERANCE = -1; + + protected int findIndex = -1; + + // estimated size of the point set + protected double charLength; + + protected boolean debug = false; + + protected Vertex[] pointBuffer = new Vertex[0]; + protected int[] vertexPointIndices = new int[0]; + private final Face[] discardedFaces = new Face[3]; + + private final Vertex[] maxVtxs = new Vertex[3]; + private final Vertex[] minVtxs = new Vertex[3]; + + protected Vector<Face> faces = new Vector<>(16); + protected Vector<HalfEdge> horizon = new Vector<>(16); + + private final FaceList newFaces = new FaceList(); + private final VertexList unclaimed = new VertexList(); + private final VertexList claimed = new VertexList(); + + protected int numVertices; + protected int numFaces; + protected int numPoints; + + protected double explicitTolerance = AUTOMATIC_TOLERANCE; + protected double tolerance; + + /** + * Returns true if debugging is enabled. + * + * @return true if debugging is enabled + * @see QuickHull3D#setDebug + */ + public boolean getDebug() { + return debug; + } + + /** + * Enables the printing of debugging diagnostics. + * + * @param enable if true, enables debugging + */ + public void setDebug(final boolean enable) { + debug = enable; + } + + /** + * Precision of a double. + */ + static private final double DOUBLE_PREC = 2.2204460492503131e-16; + + /** + * Returns the distance tolerance that was used for the most recently computed hull. + * The distance tolerance is used to determine when faces are unambiguously convex + * with respect to each other, and when points are unambiguously above or below + * a face plane, in the presence of <a href=#distTol>numerical imprecision</a>. + * Normally, this tolerance is computed automatically for each set of input points, + * but it can be set explicitly by the application. + * + * @return distance tolerance + * @see QuickHull3D#setExplicitDistanceTolerance + */ + public double getDistanceTolerance() { + return tolerance; + } + + /** + * Sets an explicit distance tolerance for convexity tests. + * If {@link #AUTOMATIC_TOLERANCE AUTOMATIC_TOLERANCE} is specified (the default), + * then the tolerance will be computed automatically from the point data. + * + * @param tol explicit tolerance + * @see #getDistanceTolerance + */ + public void setExplicitDistanceTolerance(final double tol) { + explicitTolerance = tol; + } + + /** + * Returns the explicit distance tolerance. + * + * @return explicit tolerance + * @see #setExplicitDistanceTolerance + */ + public double getExplicitDistanceTolerance() { + return explicitTolerance; + } + + private void addPointToFace(final Vertex vtx, final Face face) { + vtx.face = face; + + if (face.outside == null) { + claimed.add(vtx); + } + else { + claimed.insertBefore(vtx, face.outside); + } + face.outside = vtx; + } + + private void removePointFromFace(final Vertex vtx, final Face face) { + if (vtx == face.outside) { + if (vtx.next != null && vtx.next.face == face) { + face.outside = vtx.next; + } + else { + face.outside = null; + } + } + claimed.delete(vtx); + } + + private Vertex removeAllPointsFromFace(final Face face) { + if (face.outside != null) { + Vertex end = face.outside; + while (end.next != null && end.next.face == face) { + end = end.next; + } + claimed.delete(face.outside, end); + end.next = null; + return face.outside; + } + else { + return null; + } + } + + /** + * Creates an empty convex hull object. + */ + public QuickHull3D() { + } + + /** + * Creates a convex hull object and initializes it to the convex hull of a point set whose + * coordinates are given by an array of doubles. + * + * @param coords x, y, and z coordinates of each input point. + * The length of this array will be three times the number of input points. + * @throws IllegalArgumentException the number of input points is less than four, or the points appear to be + * coincident, colinear, or coplanar. + */ + public QuickHull3D(final double[] coords) throws IllegalArgumentException { + build(coords, coords.length / 3); + } + + /** + * Creates a convex hull object and initializes it to the convex hull of a point set. + * + * @param points input points. + * @throws IllegalArgumentException the number of input points is less than four, or the points appear to be + * coincident, colinear, or coplanar. + */ + public QuickHull3D(final Point3d[] points) throws IllegalArgumentException { + build(points, points.length); + } + + private HalfEdge findHalfEdge(final Vertex tail, final Vertex head) { + // brute force ... OK, since setHull is not used much + for (final Face face : faces) { + final HalfEdge he = face.findEdge(tail, head); + if (he != null) { + return he; + } + } + return null; + } + + protected void setHull(final double[] coords, final int nump, final int[][] faceIndices, final int numf) { + initBuffers(nump); + setPoints(coords, nump); + computeMaxAndMin(); + for (int i = 0; i < numf; i++) { + final Face face = Face.create(pointBuffer, faceIndices[i]); + HalfEdge he = face.he0; + do { + final HalfEdge heOpp = findHalfEdge(he.head(), he.tail()); + if (heOpp != null) { + he.setOpposite(heOpp); + } + he = he.next; + } while (he != face.he0); + faces.add(face); + } + } + + private void printQhullErrors(final Process proc) throws IOException { + boolean wrote = false; + final InputStream es = proc.getErrorStream(); + while (es.available() > 0) { + System.out.write(es.read()); + wrote = true; + } + if (wrote) { + System.out.println(); + } + } + + protected void setFromQhull(final double[] coords, final int nump, final boolean triangulate) { + String commandStr = "./qhull i"; + if (triangulate) { + commandStr += " -Qt"; + } + try { + final Process proc = Runtime.getRuntime().exec(commandStr); + final PrintStream ps = new PrintStream(proc.getOutputStream()); + final StreamTokenizer stok = new StreamTokenizer(new InputStreamReader(proc.getInputStream())); + + ps.println("3 " + nump); + for (int i = 0; i < nump; i++) { + ps.println(coords[i * 3] + " " + coords[i * 3 + 1] + " " + coords[i * 3 + 2]); + } + ps.flush(); + ps.close(); + final Vector<Integer> indexList = new Vector<>(3); + stok.eolIsSignificant(true); + printQhullErrors(proc); + + do { + stok.nextToken(); + } while (stok.sval == null || !stok.sval.startsWith("MERGEexact")); + for (int i = 0; i < 4; i++) { + stok.nextToken(); + } + if (stok.ttype != StreamTokenizer.TT_NUMBER) { + System.out.println("Expecting number of faces"); + System.exit(1); + } + final int numf = (int) stok.nval; + stok.nextToken(); // clear EOL + final int[][] faceIndices = new int[numf][]; + for (int i = 0; i < numf; i++) { + indexList.clear(); + while (stok.nextToken() != StreamTokenizer.TT_EOL) { + if (stok.ttype != StreamTokenizer.TT_NUMBER) { + System.out.println("Expecting face index"); + System.exit(1); + } + indexList.add(0, (int) stok.nval); + } + faceIndices[i] = new int[indexList.size()]; + int k = 0; + for (final Integer integer : indexList) { + faceIndices[i][k++] = integer; + } + } + setHull(coords, nump, faceIndices, numf); + } + catch (final Exception e) { + e.printStackTrace(); + System.exit(1); + } + } + + /** + * Constructs the convex hull of a point set whose coordinates are given by an array of + * doubles. + * + * @param coords x, y, and z coordinates of each input point. + * The length of this array will be three times the number of input points. + * @throws IllegalArgumentException the number of input points is less than four, or the points appear to be + * coincident, colinear, or coplanar. + */ + public void build(final double[] coords) throws IllegalArgumentException { + build(coords, coords.length / 3); + } + + /** + * Constructs the convex hull of a point set whose coordinates are given by an array of + * doubles. + * + * @param coords x, y, and z coordinates of each input point. + * The length of this array must be at least three times <code>nump</code>. + * @param nump number of input points + * @throws IllegalArgumentException the number of input points is less than four or greater than 1/3 the length of + * <code>coords</code>, or the points appear to be coincident, colinear, or + * coplanar. + */ + public void build(final double[] coords, final int nump) throws IllegalArgumentException { + if (nump < 4) { + throw new IllegalArgumentException("Less than four input points specified"); + } + if (coords.length / 3 < nump) { + throw new IllegalArgumentException("Coordinate array too small for specified number of points"); + } + initBuffers(nump); + setPoints(coords, nump); + buildHull(); + } + + /** + * Constructs the convex hull of a point set. + * + * @param points input points + * @throws IllegalArgumentException the number of input points is less than four, or the points appear to be + * coincident, colinear, or coplanar. + */ + public void build(final Point3d[] points) throws IllegalArgumentException { + build(points, points.length); + } + + /** + * Constructs the convex hull of a point set. + * + * @param points input points + * @param nump number of input points + * @throws IllegalArgumentException the number of input points is less than four or greater than the length of + * <code>points</code>, or the points appear to be coincident, colinear, or + * coplanar. + */ + public void build(final Point3d[] points, final int nump) throws IllegalArgumentException { + if (nump < 4) { + throw new IllegalArgumentException("Less than four input points specified"); + } + if (points.length < nump) { + throw new IllegalArgumentException("Point array too small for specified number of points"); + } + initBuffers(nump); + setPoints(points, nump); + buildHull(); + } + + /** + * Triangulates any non-triangular hull faces. + * In some cases, due to precision issues, + * the resulting triangles may be very thin or small, + * and hence appear to be non-convex + * (this same limitation is present in <a href=http://www.qhull.org>qhull</a>). + */ + public void triangulate() { + final double minArea = 1000 * charLength * DOUBLE_PREC; + newFaces.clear(); + for (final Face face : faces) { + if (face.mark == Face.VISIBLE) { + face.triangulate(newFaces, minArea); + // splitFace (face); + } + } + for (Face face = newFaces.first(); face != null; face = face.next) { + faces.add(face); + } + } + + // private void splitFace (Face face) + // { + // Face newFace = face.split(); + // if (newFace != null) + // { newFaces.add (newFace); + // splitFace (newFace); + // splitFace (face); + // } + // } + + protected void initBuffers(final int nump) { + if (pointBuffer.length < nump) { + final Vertex[] newBuffer = new Vertex[nump]; + vertexPointIndices = new int[nump]; + System.arraycopy(pointBuffer, 0, newBuffer, 0, pointBuffer.length); + for (int i = pointBuffer.length; i < nump; i++) { + newBuffer[i] = new Vertex(); + } + pointBuffer = newBuffer; + } + faces.clear(); + claimed.clear(); + numFaces = 0; + numPoints = nump; + } + + protected void setPoints(final double[] coords, final int nump) { + for (int i = 0; i < nump; i++) { + final Vertex vtx = pointBuffer[i]; + vtx.pnt.set(coords[i * 3], coords[i * 3 + 1], coords[i * 3 + 2]); + vtx.index = i; + } + } + + protected void setPoints(final Point3d[] pnts, final int nump) { + for (int i = 0; i < nump; i++) { + final Vertex vtx = pointBuffer[i]; + vtx.pnt.set(pnts[i]); + vtx.index = i; + } + } + + protected void computeMaxAndMin() { + final Vector3d max = new Vector3d(); + final Vector3d min = new Vector3d(); + + for (int i = 0; i < 3; i++) { + maxVtxs[i] = minVtxs[i] = pointBuffer[0]; + } + max.set(pointBuffer[0].pnt); + min.set(pointBuffer[0].pnt); + + for (int i = 1; i < numPoints; i++) { + final Vector3d pnt = pointBuffer[i].pnt; + if (pnt.x > max.x) { + max.x = pnt.x; + maxVtxs[0] = pointBuffer[i]; + } + else if (pnt.x < min.x) { + min.x = pnt.x; + minVtxs[0] = pointBuffer[i]; + } + if (pnt.y > max.y) { + max.y = pnt.y; + maxVtxs[1] = pointBuffer[i]; + } + else if (pnt.y < min.y) { + min.y = pnt.y; + minVtxs[1] = pointBuffer[i]; + } + if (pnt.z > max.z) { + max.z = pnt.z; + maxVtxs[2] = pointBuffer[i]; + } + else if (pnt.z < min.z) { + min.z = pnt.z; + maxVtxs[2] = pointBuffer[i]; + } + } + + // this epsilon formula comes from QuickHull, and I'm + // not about to quibble. + charLength = Math.max(max.x - min.x, max.y - min.y); + charLength = Math.max(max.z - min.z, charLength); + if (explicitTolerance == AUTOMATIC_TOLERANCE) { + tolerance = 3 * DOUBLE_PREC * (Math.max(Math.abs(max.x), Math.abs(min.x)) + Math.max(Math.abs(max.y), Math.abs(min.y)) + Math.max(Math.abs(max.z), Math.abs(min.z))); + } + else { + tolerance = explicitTolerance; + } + } + + /** + * Creates the initial simplex from which the hull will be built. + */ + protected void createInitialSimplex() throws IllegalArgumentException { + double max = 0; + final int imax = 0; + + { + double diff; + + diff = maxVtxs[0].pnt.x - minVtxs[0].pnt.x; + if (diff > max) { + max = diff; + //imax = 0; + } + + diff = maxVtxs[1].pnt.y - minVtxs[1].pnt.y; + if (diff > max) { + max = diff; + //imax = 0; + } + + diff = maxVtxs[1].pnt.z - minVtxs[1].pnt.z; + if (diff > max) { + max = diff; + //imax = 0; + } + } + + if (max <= tolerance) { + throw new IllegalArgumentException("Input points appear to be coincident"); + } + final Vertex[] vtx = new Vertex[4]; + // set first two vertices to be those with the greatest + // one dimensional separation + + vtx[0] = maxVtxs[imax]; + vtx[1] = minVtxs[imax]; + + // set third vertex to be the vertex farthest from + // the line between vtx0 and vtx1 + final Vector3d u01 = new Vector3d(); + final Vector3d diff02 = new Vector3d(); + final Vector3d nrml = new Vector3d(); + final Vector3d xprod = new Vector3d(); + double maxSqr = 0; + u01.sub(vtx[1].pnt, vtx[0].pnt); + u01.normalize(); + for (int i = 0; i < numPoints; i++) { + diff02.sub(pointBuffer[i].pnt, vtx[0].pnt); + xprod.cross(u01, diff02); + final double lenSqr = xprod.lengthSquared(); + if (lenSqr > maxSqr && pointBuffer[i] != vtx[0] && // paranoid + pointBuffer[i] != vtx[1]) { + maxSqr = lenSqr; + vtx[2] = pointBuffer[i]; + nrml.set(xprod); + } + } + if (Math.sqrt(maxSqr) <= 100 * tolerance) { + throw new IllegalArgumentException("Input points appear to be coplanar"); + } + nrml.normalize(); + + double maxDist = 0; + final double d0 = vtx[2].pnt.dot(nrml); + for (int i = 0; i < numPoints; i++) { + final double dist = Math.abs(pointBuffer[i].pnt.dot(nrml) - d0); + if (dist > maxDist && pointBuffer[i] != vtx[0] && // paranoid + pointBuffer[i] != vtx[1] && pointBuffer[i] != vtx[2]) { + maxDist = dist; + vtx[3] = pointBuffer[i]; + } + } + if (Math.abs(maxDist) <= 100 * tolerance) { + throw new IllegalArgumentException("Input points appear to be coplanar"); + } + + if (debug) { + System.out.println("initial vertices:"); + System.out.println(vtx[0].index + ": " + vtx[0].pnt); + System.out.println(vtx[1].index + ": " + vtx[1].pnt); + System.out.println(vtx[2].index + ": " + vtx[2].pnt); + System.out.println(vtx[3].index + ": " + vtx[3].pnt); + } + + final Face[] tris = new Face[4]; + + if (vtx[3].pnt.dot(nrml) - d0 < 0) { + tris[0] = Face.createTriangle(vtx[0], vtx[1], vtx[2]); + tris[1] = Face.createTriangle(vtx[3], vtx[1], vtx[0]); + tris[2] = Face.createTriangle(vtx[3], vtx[2], vtx[1]); + tris[3] = Face.createTriangle(vtx[3], vtx[0], vtx[2]); + + for (int i = 0; i < 3; i++) { + final int k = (i + 1) % 3; + tris[i + 1].getEdge(1).setOpposite(tris[k + 1].getEdge(0)); + tris[i + 1].getEdge(2).setOpposite(tris[0].getEdge(k)); + } + } + else { + tris[0] = Face.createTriangle(vtx[0], vtx[2], vtx[1]); + tris[1] = Face.createTriangle(vtx[3], vtx[0], vtx[1]); + tris[2] = Face.createTriangle(vtx[3], vtx[1], vtx[2]); + tris[3] = Face.createTriangle(vtx[3], vtx[2], vtx[0]); + + for (int i = 0; i < 3; i++) { + final int k = (i + 1) % 3; + tris[i + 1].getEdge(0).setOpposite(tris[k + 1].getEdge(1)); + tris[i + 1].getEdge(2).setOpposite(tris[0].getEdge((3 - i) % 3)); + } + } + + faces.addAll(Arrays.asList(tris).subList(0, 4)); + + for (int i = 0; i < numPoints; i++) { + final Vertex v = pointBuffer[i]; + + if (v == vtx[0] || v == vtx[1] || v == vtx[2] || v == vtx[3]) { + continue; + } + + maxDist = tolerance; + Face maxFace = null; + for (int k = 0; k < 4; k++) { + final double dist = tris[k].distanceToPlane(v.pnt); + if (dist > maxDist) { + maxFace = tris[k]; + maxDist = dist; + } + } + if (maxFace != null) { + addPointToFace(v, maxFace); + } + } + } + + /** + * Returns the number of vertices in this hull. + * + * @return number of vertices + */ + public int getNumVertices() { + return numVertices; + } + + /** + * Returns the vertex points in this hull. + * + * @return array of vertex points + * @see QuickHull3D#getVertices(double[]) + * @see QuickHull3D#getFaces() + */ + public Point3d[] getVertices() { + final Point3d[] vtxs = new Point3d[numVertices]; + for (int i = 0; i < numVertices; i++) { + vtxs[i] = new Point3d(pointBuffer[vertexPointIndices[i]].pnt); + } + return vtxs; + } + + /** + * Returns the coordinates of this hull vertex points. + * + * @param coords returns the x, y, z coordinates of each vertex. + * This length of this array must be at least three times the number of vertices. + * @return the number of vertices + * @see QuickHull3D#getVertices() + * @see QuickHull3D#getFaces() + */ + public int getVertices(final double[] coords) { + for (int i = 0; i < numVertices; i++) { + final Point3d pnt = new Point3d(pointBuffer[vertexPointIndices[i]].pnt); + coords[i * 3] = pnt.x; + coords[i * 3 + 1] = pnt.y; + coords[i * 3 + 2] = pnt.z; + } + return numVertices; + } + + /** + * Returns an array specifing the index of each hull vertex with respect to the original input + * points. + * + * @return vertex indices with respect to the original points + */ + public int[] getVertexPointIndices() { + final int[] indices = new int[numVertices]; + System.arraycopy(vertexPointIndices, 0, indices, 0, numVertices); + return indices; + } + + /** + * Returns the number of faces in this hull. + * + * @return number of faces + */ + public int getNumFaces() { + return faces.size(); + } + + /** + * Returns the faces associated with this hull. + * + * <p> + * Each face is represented by an integer array which gives the indices of the vertices. + * These indices are numbered relative to the hull vertices, + * are zero-based, and are arranged counter-clockwise. + * More control over the index format can be obtained using {@link #getFaces(int) getFaces(indexFlags)}. + * + * @return array of integer arrays, giving the vertex indices for each face. + * @see QuickHull3D#getVertices() + * @see QuickHull3D#getFaces(int) + */ + public int[][] getFaces() { + return getFaces(0); + } + + /** + * Returns the faces associated with this hull. + * + * <p> + * Each face is represented by an integer array which gives the indices of the vertices. + * By default, these indices are numbered with respect to the hull vertices + * (as opposed to the input points), are zero-based, and are arranged counter-clockwise. + * However, this can be changed by setting {@link #POINT_RELATIVE POINT_RELATIVE}, + * {@link #INDEXED_FROM_ONE INDEXED_FROM_ONE}, or {@link #CLOCKWISE CLOCKWISE} in the + * indexFlags parameter. + * + * @param indexFlags specifies index characteristics (0 results in the default) + * @return array of integer arrays, giving the vertex indices for each face. + * @see QuickHull3D#getVertices() + */ + public int[][] getFaces(final int indexFlags) { + final int[][] allFaces = new int[faces.size()][]; + int k = 0; + for (final Face face : faces) { + allFaces[k] = new int[face.numVertices()]; + getFaceIndices(allFaces[k], face, indexFlags); + k++; + } + return allFaces; + } + + /** + * Prints the vertices and faces of this hull to the stream ps. + * + * <p> + * This is done using the Alias Wavefront .obj file format, with the vertices printed first + * (each preceding by the letter <code>v</code>), followed by the vertex indices for each + * face (each preceded by the letter <code>f</code>). + * + * <p> + * The face indices are numbered with respect to the hull vertices (as opposed to the input + * points), with the lowest index of 1, and are arranged counter-clockwise. + * More control over the index format can be obtained using + * {@link #print(PrintStream, int) print(ps,indexFlags)}. + * + * @param ps stream used for printing + * @see QuickHull3D#print(PrintStream, int) + * @see QuickHull3D#getVertices() + * @see QuickHull3D#getFaces() + */ + public void print(final PrintStream ps) { + print(ps, 0); + } + + /** + * Prints the vertices and faces of this hull to the stream ps. + * + * <p> + * This is done using the Alias Wavefront .obj file format, with the vertices printed first + * (each preceding by the letter <code>v</code>), followed by the vertex indices for each + * face (each preceded by the letter <code>f</code>). + * + * <p> + * By default, the face indices are numbered with respect to the hull vertices (as opposed to + * the input points), with the lowest index of 1, and are arranged counter-clockwise. + * However, this can be changed by setting {@link #POINT_RELATIVE POINT_RELATIVE}, + * {@link #INDEXED_FROM_ONE INDEXED_FROM_ZERO}, or {@link #CLOCKWISE CLOCKWISE} in the + * indexFlags parameter. + * + * @param ps stream used for printing + * @param indexFlags specifies index characteristics (0 results in the default). + * @see QuickHull3D#getVertices() + * @see QuickHull3D#getFaces() + */ + public void print(final PrintStream ps, int indexFlags) { + if ((indexFlags & INDEXED_FROM_ZERO) == 0) { + indexFlags |= INDEXED_FROM_ONE; + } + for (int i = 0; i < numVertices; i++) { + final Vector3d pnt = pointBuffer[vertexPointIndices[i]].pnt; + ps.println("v " + pnt.x + " " + pnt.y + " " + pnt.z); + } + for (final Face face : faces) { + final int[] indices = new int[face.numVertices()]; + getFaceIndices(indices, face, indexFlags); + + ps.print("f"); + for (final int index : indices) { + ps.print(" " + index); + } + ps.println(); + } + } + + private void getFaceIndices(final int[] indices, final Face face, final int flags) { + final boolean ccw = ((flags & CLOCKWISE) == 0); + final boolean indexedFromOne = ((flags & INDEXED_FROM_ONE) != 0); + final boolean pointRelative = ((flags & POINT_RELATIVE) != 0); + + HalfEdge hedge = face.he0; + int k = 0; + do { + int idx = hedge.head().index; + if (pointRelative) { + idx = vertexPointIndices[idx]; + } + if (indexedFromOne) { + idx++; + } + indices[k++] = idx; + hedge = (ccw ? hedge.next : hedge.prev); + } while (hedge != face.he0); + } + + protected void resolveUnclaimedPoints(final FaceList newFaces) { + Vertex vtxNext = unclaimed.first(); + for (Vertex vtx = vtxNext; vtx != null; vtx = vtxNext) { + vtxNext = vtx.next; + + double maxDist = tolerance; + Face maxFace = null; + for (Face newFace = newFaces.first(); newFace != null; newFace = newFace.next) { + if (newFace.mark == Face.VISIBLE) { + final double dist = newFace.distanceToPlane(vtx.pnt); + if (dist > maxDist) { + maxDist = dist; + maxFace = newFace; + } + if (maxDist > 1000 * tolerance) { + break; + } + } + } + if (maxFace != null) { + addPointToFace(vtx, maxFace); + if (debug && vtx.index == findIndex) { + System.out.println(findIndex + " CLAIMED BY " + maxFace.getVertexString()); + } + } + else { + if (debug && vtx.index == findIndex) { + System.out.println(findIndex + " DISCARDED"); + } + } + } + } + + protected void deleteFacePoints(final Face face, final Face absorbingFace) { + final Vertex faceVtxs = removeAllPointsFromFace(face); + if (faceVtxs != null) { + if (absorbingFace == null) { + unclaimed.addAll(faceVtxs); + } + else { + Vertex vtxNext = faceVtxs; + for (Vertex vtx = vtxNext; vtx != null; vtx = vtxNext) { + vtxNext = vtx.next; + final double dist = absorbingFace.distanceToPlane(vtx.pnt); + if (dist > tolerance) { + addPointToFace(vtx, absorbingFace); + } + else { + unclaimed.add(vtx); + } + } + } + } + } + + private static final int NONCONVEX_WRT_LARGER_FACE = 1; + private static final int NONCONVEX = 2; + + protected double oppFaceDistance(final HalfEdge he) { + return he.face.distanceToPlane(he.opposite.face.getCentroid()); + } + + private boolean doAdjacentMerge(final Face face, final int mergeType) { + HalfEdge hedge = face.he0; + + boolean convex = true; + do { + final Face oppFace = hedge.oppositeFace(); + boolean merge = false; + + if (mergeType == NONCONVEX) { // then merge faces if they are definitively non-convex + if (oppFaceDistance(hedge) > -tolerance || oppFaceDistance(hedge.opposite) > -tolerance) { + merge = true; + } + } + else + // mergeType == NONCONVEX_WRT_LARGER_FACE + { // merge faces if they are parallel or non-convex + // wrt to the larger face; otherwise, just mark + // the face non-convex for the second pass. + if (face.area > oppFace.area) { + if (oppFaceDistance(hedge) > -tolerance) { + merge = true; + } + else if (oppFaceDistance(hedge.opposite) > -tolerance) { + convex = false; + } + } + else { + if (oppFaceDistance(hedge.opposite) > -tolerance) { + merge = true; + } + else if (oppFaceDistance(hedge) > -tolerance) { + convex = false; + } + } + } + + if (merge) { + if (debug) { + System.out.println(" merging " + face.getVertexString() + " and " + oppFace.getVertexString()); + } + + final int numd = face.mergeAdjacentFace(hedge, discardedFaces); + for (int i = 0; i < numd; i++) { + deleteFacePoints(discardedFaces[i], face); + } + if (debug) { + System.out.println(" result: " + face.getVertexString()); + } + return true; + } + hedge = hedge.next; + } while (hedge != face.he0); + if (!convex) { + face.mark = Face.NON_CONVEX; + } + return false; + } + + protected void calculateHorizon(final Vector3d eyePnt, HalfEdge edge0, final Face face, final Vector<HalfEdge> horizon) { + // oldFaces.add (face); + deleteFacePoints(face, null); + face.mark = Face.DELETED; + if (debug) { + System.out.println(" visiting face " + face.getVertexString()); + } + HalfEdge edge; + if (edge0 == null) { + edge0 = face.getEdge(0); + edge = edge0; + } + else { + edge = edge0.getNext(); + } + do { + final Face oppFace = edge.oppositeFace(); + if (oppFace.mark == Face.VISIBLE) { + if (oppFace.distanceToPlane(eyePnt) > tolerance) { + calculateHorizon(eyePnt, edge.getOpposite(), oppFace, horizon); + } + else { + horizon.add(edge); + if (debug) { + System.out.println(" adding horizon edge " + edge.getVertexString()); + } + } + } + edge = edge.getNext(); + } while (edge != edge0); + } + + private HalfEdge addAdjoiningFace(final Vertex eyeVtx, final HalfEdge he) { + final Face face = Face.createTriangle(eyeVtx, he.tail(), he.head()); + faces.add(face); + face.getEdge(-1).setOpposite(he.getOpposite()); + return face.getEdge(0); + } + + protected void addNewFaces(final FaceList newFaces, final Vertex eyeVtx, final Vector<HalfEdge> horizon) { + newFaces.clear(); + + HalfEdge hedgeSidePrev = null; + HalfEdge hedgeSideBegin = null; + + for (final HalfEdge horizonHe : horizon) { + final HalfEdge hedgeSide = addAdjoiningFace(eyeVtx, horizonHe); + if (debug) { + System.out.println("new face: " + hedgeSide.face.getVertexString()); + } + if (hedgeSidePrev != null) { + hedgeSide.next.setOpposite(hedgeSidePrev); + } + else { + hedgeSideBegin = hedgeSide; + } + newFaces.add(hedgeSide.getFace()); + hedgeSidePrev = hedgeSide; + } + assert hedgeSideBegin != null; + hedgeSideBegin.next.setOpposite(hedgeSidePrev); + } + + protected Vertex nextPointToAdd() { + if (!claimed.isEmpty()) { + final Face eyeFace = claimed.first().face; + Vertex eyeVtx = null; + double maxDist = 0; + for (Vertex vtx = eyeFace.outside; vtx != null && vtx.face == eyeFace; vtx = vtx.next) { + final double dist = eyeFace.distanceToPlane(vtx.pnt); + if (dist > maxDist) { + maxDist = dist; + eyeVtx = vtx; + } + } + return eyeVtx; + } + else { + return null; + } + } + + protected void addPointToHull(final Vertex eyeVtx) { + horizon.clear(); + unclaimed.clear(); + + if (debug) { + System.out.println("Adding point: " + eyeVtx.index); + System.out.println(" which is " + eyeVtx.face.distanceToPlane(eyeVtx.pnt) + " above face " + eyeVtx.face.getVertexString()); + } + removePointFromFace(eyeVtx, eyeVtx.face); + calculateHorizon(eyeVtx.pnt, null, eyeVtx.face, horizon); + newFaces.clear(); + addNewFaces(newFaces, eyeVtx, horizon); + + // first merge pass ... merge faces which are non-convex + // as determined by the larger face + + for (Face face = newFaces.first(); face != null; face = face.next) { + if (face.mark == Face.VISIBLE) { + while (doAdjacentMerge(face, NONCONVEX_WRT_LARGER_FACE)) + ; + } + } + // second merge pass ... merge faces which are non-convex + // wrt either face + for (Face face = newFaces.first(); face != null; face = face.next) { + if (face.mark == Face.NON_CONVEX) { + face.mark = Face.VISIBLE; + while (doAdjacentMerge(face, NONCONVEX)) + ; + } + } + resolveUnclaimedPoints(newFaces); + } + + protected void buildHull() { + int cnt = 0; + Vertex eyeVtx; + + computeMaxAndMin(); + createInitialSimplex(); + while ((eyeVtx = nextPointToAdd()) != null) { + addPointToHull(eyeVtx); + cnt++; + if (debug) { + System.out.println("iteration " + cnt + " done"); + } + } + reindexFacesAndVertices(); + if (debug) { + System.out.println("hull done"); + } + } + + private void markFaceVertices(final Face face, final int mark) { + final HalfEdge he0 = face.getFirstEdge(); + HalfEdge he = he0; + do { + he.head().index = mark; + he = he.next; + } while (he != he0); + } + + protected void reindexFacesAndVertices() { + for (int i = 0; i < numPoints; i++) { + pointBuffer[i].index = -1; + } + // remove inactive faces and mark active vertices + numFaces = 0; + for (final Iterator<Face> it = faces.iterator(); it.hasNext(); ) { + final Face face = it.next(); + if (face.mark != Face.VISIBLE) { + it.remove(); + } + else { + markFaceVertices(face, 0); + numFaces++; + } + } + // reindex vertices + numVertices = 0; + for (int i = 0; i < numPoints; i++) { + final Vertex vtx = pointBuffer[i]; + if (vtx.index == 0) { + vertexPointIndices[numVertices] = i; + vtx.index = numVertices++; + } + } + } + + protected boolean checkFaceConvexity(final Face face, final double tol, final PrintStream ps) { + double dist; + HalfEdge he = face.he0; + do { + face.checkConsistency(); + // make sure edge is convex + dist = oppFaceDistance(he); + if (dist > tol) { + if (ps != null) { + ps.println("Edge " + he.getVertexString() + " non-convex by " + dist); + } + return false; + } + dist = oppFaceDistance(he.opposite); + if (dist > tol) { + if (ps != null) { + ps.println("Opposite edge " + he.opposite.getVertexString() + " non-convex by " + dist); + } + return false; + } + if (he.next.oppositeFace() == he.oppositeFace()) { + if (ps != null) { + ps.println("Redundant vertex " + he.head().index + " in face " + face.getVertexString()); + } + return false; + } + he = he.next; + } while (he != face.he0); + return true; + } + + protected boolean checkFaces(final double tol, final PrintStream ps) { + // check edge convexity + boolean convex = true; + for (final Face face : faces) { + if (face.mark == Face.VISIBLE) { + if (!checkFaceConvexity(face, tol, ps)) { + convex = false; + } + } + } + return convex; + } + + /** + * Checks the correctness of the hull using the distance tolerance returned by + * {@link QuickHull3D#getDistanceTolerance getDistanceTolerance}; see + * {@link QuickHull3D#check(PrintStream, double) check(PrintStream,double)} for details. + * + * @param ps print stream for diagnostic messages; may be set to <code>null</code> if no + * messages are desired. + * @return true if the hull is valid + * @see QuickHull3D#check(PrintStream, double) + */ + public boolean check(final PrintStream ps) { + return check(ps, getDistanceTolerance()); + } + + /** + * Checks the correctness of the hull. + * This is done by making sure that no faces are non-convex and that no points are outside any face. + * These tests are performed using the distance tolerance <i>tol</i>. + * Faces are considered non-convex if any edge is non-convex, and an edge + * is non-convex if the centroid of either adjoining face is more than <i>tol</i> above the + * plane of the other face. + * Similarly, a point is considered outside a face if its distance to + * that face's plane is more than 10 times <i>tol</i>. + * + * <p> + * If the hull has been {@link #triangulate triangulated}, then this routine may fail if some + * of the resulting triangles are very small or thin. + * + * @param ps print stream for diagnostic messages; may be set to <code>null</code> if no + * messages are desired. + * @param tol distance tolerance + * @return true if the hull is valid + * @see QuickHull3D#check(PrintStream) + */ + public boolean check(final PrintStream ps, final double tol) { + // check to make sure all edges are fully connected + // and that the edges are convex + double dist; + final double pointTol = 10 * tol; + + if (!checkFaces(tolerance, ps)) { + return false; + } + + // check point inclusion + + for (int i = 0; i < numPoints; i++) { + final Vector3d pnt = pointBuffer[i].pnt; + for (final Face face : faces) { + if (face.mark == Face.VISIBLE) { + dist = face.distanceToPlane(pnt); + if (dist > pointTol) { + if (ps != null) { + ps.println("Point " + i + " " + dist + " above face " + face.getVertexString()); + } + return false; + } + } + } + } + return true; + } } diff --git a/src/main/java/plugins/adufour/quickhull/Vertex.java b/src/main/java/plugins/adufour/quickhull/Vertex.java index e70f2ce..b652810 100644 --- a/src/main/java/plugins/adufour/quickhull/Vertex.java +++ b/src/main/java/plugins/adufour/quickhull/Vertex.java @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2010-2023. Institut Pasteur. + * + * This file is part of Icy. + * Icy is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Icy is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Icy. If not, see <https://www.gnu.org/licenses/>. + */ + package plugins.adufour.quickhull; import javax.vecmath.Vector3d; @@ -8,48 +26,46 @@ import javax.vecmath.Vector3d; * * @author John E. Lloyd, Fall 2004 */ -class Vertex -{ - /** - * Spatial point associated with this vertex. - */ - Vector3d pnt; - - /** - * Back index into an array. - */ - int index; - - /** - * List forward link. - */ - Vertex prev; - - /** - * List backward link. - */ - Vertex next; - - /** - * Current face that this vertex is outside of. - */ - Face face; - - /** - * Constructs a vertex and sets its coordinates to 0. - */ - public Vertex() - { pnt = new Vector3d(); - } - - /** - * Constructs a vertex with the specified coordinates - * and index. - */ - public Vertex (double x, double y, double z, int idx) - { - pnt = new Vector3d(x, y, z); - index = idx; - } +class Vertex { + /** + * Spatial point associated with this vertex. + */ + Vector3d pnt; + + /** + * Back index into an array. + */ + int index; + + /** + * List forward link. + */ + Vertex prev; + + /** + * List backward link. + */ + Vertex next; + + /** + * Current face that this vertex is outside. + */ + Face face; + + /** + * Constructs a vertex and sets its coordinates to 0. + */ + public Vertex() { + pnt = new Vector3d(); + } + + /** + * Constructs a vertex with the specified coordinates + * and index. + */ + public Vertex(final double x, final double y, final double z, final int idx) { + pnt = new Vector3d(x, y, z); + index = idx; + } } diff --git a/src/main/java/plugins/adufour/quickhull/VertexList.java b/src/main/java/plugins/adufour/quickhull/VertexList.java index fc6c29f..f6d6247 100644 --- a/src/main/java/plugins/adufour/quickhull/VertexList.java +++ b/src/main/java/plugins/adufour/quickhull/VertexList.java @@ -1,137 +1,131 @@ +/* + * Copyright (c) 2010-2023. Institut Pasteur. + * + * This file is part of Icy. + * Icy is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Icy is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Icy. If not, see <https://www.gnu.org/licenses/>. + */ + package plugins.adufour.quickhull; /** * Maintains a double-linked list of vertices for use by QuickHull3D */ -class VertexList -{ - private Vertex head; - private Vertex tail; - - /** - * Clears this list. - */ - public void clear() - { - head = tail = null; - } - - /** - * Adds a vertex to the end of this list. - */ - public void add(Vertex vtx) - { - if (head == null) - { - head = vtx; - } - else - { - tail.next = vtx; - } - vtx.prev = tail; - vtx.next = null; - tail = vtx; - } - - /** - * Adds a chain of vertices to the end of this list. - */ - public void addAll(Vertex vtx) - { - if (head == null) - { - head = vtx; - } - else - { - tail.next = vtx; - } - vtx.prev = tail; - while (vtx.next != null) - { - vtx = vtx.next; - } - tail = vtx; - } - - /** - * Deletes a vertex from this list. - */ - public void delete(Vertex vtx) - { - if (vtx.prev == null) - { - head = vtx.next; - } - else - { - vtx.prev.next = vtx.next; - } - if (vtx.next == null) - { - tail = vtx.prev; - } - else - { - vtx.next.prev = vtx.prev; - } - } - - /** - * Deletes a chain of vertices from this list. - */ - public void delete(Vertex vtx1, Vertex vtx2) - { - if (vtx1.prev == null) - { - head = vtx2.next; - } - else - { - vtx1.prev.next = vtx2.next; - } - if (vtx2.next == null) - { - tail = vtx1.prev; - } - else - { - vtx2.next.prev = vtx1.prev; - } - } - - /** - * Inserts a vertex into this list before another specificed vertex. - */ - public void insertBefore(Vertex vtx, Vertex next) - { - vtx.prev = next.prev; - if (next.prev == null) - { - head = vtx; - } - else - { - next.prev.next = vtx; - } - vtx.next = next; - next.prev = vtx; - } - - /** - * Returns the first element in this list. - */ - public Vertex first() - { - return head; - } - - /** - * Returns true if this list is empty. - */ - public boolean isEmpty() - { - return head == null; - } +class VertexList { + private Vertex head; + private Vertex tail; + + /** + * Clears this list. + */ + public void clear() { + head = tail = null; + } + + /** + * Adds a vertex to the end of this list. + */ + public void add(final Vertex vtx) { + if (head == null) { + head = vtx; + } + else { + tail.next = vtx; + } + vtx.prev = tail; + vtx.next = null; + tail = vtx; + } + + /** + * Adds a chain of vertices to the end of this list. + */ + public void addAll(Vertex vtx) { + if (head == null) { + head = vtx; + } + else { + tail.next = vtx; + } + vtx.prev = tail; + while (vtx.next != null) { + vtx = vtx.next; + } + tail = vtx; + } + + /** + * Deletes a vertex from this list. + */ + public void delete(final Vertex vtx) { + if (vtx.prev == null) { + head = vtx.next; + } + else { + vtx.prev.next = vtx.next; + } + if (vtx.next == null) { + tail = vtx.prev; + } + else { + vtx.next.prev = vtx.prev; + } + } + + /** + * Deletes a chain of vertices from this list. + */ + public void delete(final Vertex vtx1, final Vertex vtx2) { + if (vtx1.prev == null) { + head = vtx2.next; + } + else { + vtx1.prev.next = vtx2.next; + } + if (vtx2.next == null) { + tail = vtx1.prev; + } + else { + vtx2.next.prev = vtx1.prev; + } + } + + /** + * Inserts a vertex into this list before another specificed vertex. + */ + public void insertBefore(final Vertex vtx, final Vertex next) { + vtx.prev = next.prev; + if (next.prev == null) { + head = vtx; + } + else { + next.prev.next = vtx; + } + vtx.next = next; + next.prev = vtx; + } + + /** + * Returns the first element in this list. + */ + public Vertex first() { + return head; + } + + /** + * Returns true if this list is empty. + */ + public boolean isEmpty() { + return head == null; + } } -- GitLab