/**************************************************************************** Copyright (c) 2006, Colorado School of Mines and others. All rights reserved. This program and accompanying materials are made available under the terms of the Common Public License - v1.0, which accompanies this distribution, and is available at http://www.eclipse.org/legal/cpl-v10.html ****************************************************************************/ package edu.mines.jtk.mesh; import static java.lang.Math.*; import java.util.*; import edu.mines.jtk.util.Check; /** * A 3-D triangulated manifold oriented surface, possibly with boundary. *

* This class currently enables construction of a surface from a set of * points, using the algorithm of Cohen-Steiner and Da, 2002, A greedy * Delaunay based surface reconstruction algorithm: The Visual Computer, * v. 20, p. 4-16. * * @author Dave Hale, Colorado School of Mines * @version 2004.06.14, 2007.01.12 */ public class TriSurf { /** * A node, which can be added or removed from the surface. */ public static class Node { /** * An integer index associated with this node. * Intended for external use only; the surface does not use it. */ public int index; /** * A data object associated with this node. * Intended for external use only; the surface does not use it. */ public Object data; /** * Constructs a node with specified coordinates. * @param x the x coordinate. * @param y the y coordinate. * @param z the z coordinate. */ public Node(float x, float y, float z) { _meshNode = new TetMesh.Node(x,y,z); _meshNode.data = this; } /** * Returns the x coordinate of this node. * @return the x coordinate. */ public final float x() { return _meshNode.x(); } /** * Returns the y coordinate of this node. * @return the y coordinate. */ public final float y() { return _meshNode.y(); } /** * Returns the z coordinate of this node. * @return the z coordinate. */ public final float z() { return _meshNode.z(); } /** * Determines whether this node is in the surface. * @return true, if in surface; false, otherwise. */ public boolean isInSurface() { return _face!=null; } /** * Determines whether this node is on the surface boundary. * @return true, if on the boundary; false, otherwise. */ public boolean isOnBoundary() { return _edgeBefore!=null; } /** * Returns the edge before this node on the surface boundary. * Returns null, if this node is not on the surface boundary. * If on the surface boundary, this is node B of the returned edge. * @return previous edge; null if node not on surface boundary. */ public Edge edgeBefore() { return _edgeBefore; } /** * Returns the edge after this node on the surface boundary. * Returns null, if this node is not on the surface boundary. * If on the surface boundary, this is node A of the returned edge. * @return next edge; null if node not on surface boundary. */ public Edge edgeAfter() { return _edgeAfter; } /** * Returns the area-weighted average normal vector for this node. * @return array containing the {X,Y,Z} components of the normal vector. */ public float[] normalVector() { float[] vn = new float[3]; normalVector(vn); return vn; } /** * Computes the area-weighted average normal vector for this node. * @param vn array to contain the {X,Y,Z} components of the normal vector. */ public void normalVector(float[] vn) { vn[0] = vn[1] = vn[2] = 0.0f; FaceIterator fi = getFaces(); while (fi.hasNext()) { Face face = fi.next(); accNormalVector(face,vn); } float x = vn[0]; float y = vn[1]; float z = vn[2]; float s = 1.0f/(float)sqrt(x*x+y*y+z*z); vn[0] *= s; vn[1] *= s; vn[2] *= s; } /** * Returns the number of faces that reference this node. * @return the number of faces. */ public int countFaces() { int nface = 0; FaceIterator fi = getFaces(); while (fi.hasNext()) { fi.next(); ++nface; } return nface; } /** * Gets an iterator for all faces that reference this node. * @return the iterator. */ public FaceIterator getFaces() { return new FaceIterator() { public boolean hasNext() { return _next!=null; } public Face next() { if (_next==null) throw new NoSuchElementException(); Face face = _next; loadNext(); return face; } private Face _next = _face; private boolean _ccw = true; private void loadNext() { if (_ccw) { _next = faceNext(_next); if (_next==null) { _ccw = false; _next = _face; } else if (_next==_face) { _next = null; } } if (!_ccw) { _next = facePrev(_next); } } }; } public String toString() { return _meshNode.toString(); } private TetMesh.Node _meshNode; private Face _face; // null if node not in surface private Edge _edgeBefore; // non-null if on surface boundary private Edge _edgeAfter; // non-null if on surface boundary private void validate() { assert _meshNode!=null; assert _face==null || _face.references(this); if (_edgeBefore==null) { assert _edgeAfter==null; } else { assert this==_edgeBefore.nodeB(); assert this==_edgeAfter.nodeA(); assert this==_edgeBefore.nodeA().edgeAfter().nodeB(); assert this==_edgeAfter.nodeB().edgeBefore().nodeA(); } assert _edgeBefore==null && _edgeAfter==null || _edgeBefore!=null && this==_edgeBefore.nodeB() && _edgeAfter!=null && this==_edgeAfter.nodeA(); } private void init() { _face = null; _edgeBefore = null; _edgeAfter = null; } private void setFace(Face face) { _face = face; } private void setEdgeBefore(Edge edgeBefore) { _edgeBefore = edgeBefore; } private void setEdgeAfter(Edge edgeAfter) { _edgeAfter = edgeAfter; } private Face face() { return _face; } private Face faceNext(Face face) { if (this==face.nodeA()) { return face.faceB(); } else if (this==face.nodeB()) { return face.faceC(); } else { return face.faceA(); } } private Face facePrev(Face face) { if (this==face.nodeA()) { return face.faceC(); } else if (this==face.nodeB()) { return face.faceA(); } else { return face.faceB(); } } private static void accNormalVector(Face face, float[] v) { Node na = face.nodeA(); Node nb = face.nodeB(); Node nc = face.nodeC(); float xa = na.x(); float ya = na.y(); float za = na.z(); float xb = nb.x(); float yb = nb.y(); float zb = nb.z(); float xc = nc.x(); float yc = nc.y(); float zc = nc.z(); float x0 = xc-xa; float y0 = yc-ya; float z0 = zc-za; float x1 = xa-xb; float y1 = ya-yb; float z1 = za-zb; v[0] += y0*z1-y1*z0; v[1] += x1*z0-x0*z1; v[2] += x0*y1-x1*y0; } } /** * A type-safe iterator for nodes. */ public interface NodeIterator { public boolean hasNext(); public Node next(); } /** * A directed edge. *

* An edge is specified by two nodes A and B. The order of these nodes * is significant. An edge is directed from A to B. *

* Every edge has a mate. An edge and its mate reference the same two * nodes, but in the opposite order, so they have opposite directions. * Therefore, an edge does not equal its mate. *

* An edge within the surface has a left and right face. An edge on the * boundary of the surface has a right face and a null left face. An edge * on the boundary is linked to the previous and next edge on the boundary. * */ public static class Edge { public Node nodeA() { return (Node)_meshEdge.nodeA().data; } public Node nodeB() { return (Node)_meshEdge.nodeB().data; } public Face faceLeft() { return _faceLeft; } public Face faceRight() { return _faceRight; } public Node nodeLeft() { return (_faceLeft!=null)?otherNode(_faceLeft,nodeA(),nodeB()):null; } public Node nodeRight() { return (_faceRight!=null)?otherNode(_faceRight,nodeA(),nodeB()):null; } public Edge edgeBefore() { return nodeA()._edgeBefore; } public Edge edgeAfter() { return nodeB()._edgeAfter; } public Edge mate() { return new Edge(_meshEdge.mate(),_faceRight); } public boolean isInSurface() { return _faceRight!=null; } public boolean isOnBoundary() { return _faceLeft==null; } public boolean equals(Object object) { if (object==this) return true; if (object!=null && object.getClass()==getClass()) { Edge other = (Edge)object; return other.nodeA()==nodeA() && other.nodeB()==nodeB(); } return false; } public int hashCode() { return nodeA().hashCode()^nodeB().hashCode(); } private TetMesh.Edge _meshEdge; private Face _faceLeft; // null if edge on surface boundary private Face _faceRight; // null if edge not in surface private void validate() { assert _meshEdge!=null; assert _faceLeft==null || _faceLeft.references(nodeA(),nodeB()); assert _faceRight==null || _faceRight.references(nodeA(),nodeB()); } private Edge(TetMesh.Edge meshEdge, Face face) { _meshEdge = meshEdge; Node nodeA = (Node)meshEdge.nodeA().data; Node nodeB = (Node)meshEdge.nodeB().data; Node nodeC = (face!=null)?otherNode(face,nodeA,nodeB):null; Check.argument(face==null || nodeC!=null,"face references edge"); if (nodeC!=null) { if (nodesInOrder(face,nodeA,nodeB,nodeC)) { _faceLeft = face; _faceRight = face.faceNabor(nodeC); } else { _faceLeft = face.faceNabor(nodeC); _faceRight = face; } } } } /** * A type-safe iterator for edges. */ public interface EdgeIterator { public boolean hasNext(); public Edge next(); } /** * One triangular face in the surface. * Each face references three nodes (A, B, and C), and up to three * face neighbors (nabors) opposite those nodes. A null nabor denotes * an edge (opposite the corresponding node) on the surface boundary. * The nodes A, B, and C are in CCW order. */ public static class Face { /** * An integer index associated with this face. * Intended for external use only; the surface does not use it. */ public int index; /** * A data object associated with this face. * Intended for external use only; the surface does not use it. */ public Object data; /** * Returns the node A referenced by this face. * @return the node A. */ public final Node nodeA() { return (Node)_meshFace.nodeA().data; } /** * Returns the node B referenced by this face. * @return the node B. */ public final Node nodeB() { return (Node)_meshFace.nodeB().data; } /** * Returns the node C referenced by this face. * @return the node C. */ public final Node nodeC() { return (Node)_meshFace.nodeC().data; } /** * Returns the face nabor A (opposite node A) referenced by this face. * @return the face nabor A. */ public final Face faceA() { return _faceA; } /** * Returns the face nabor B (opposite node B) referenced by this face. * @return the face nabor B. */ public final Face faceB() { return _faceB; } /** * Returns the face nabor C (opposite node C) referenced by this face. * @return the face nabor C. */ public final Face faceC() { return _faceC; } /** * Returns the mate of this face. * @return the mate of this face. */ public Face mate() { return new Face(_meshFace.mate()); } /** * Returns the node referenced by this face that is nearest to * the point with specified coordinates. * @param x the x coordinate. * @param y the y coordinate. * @param z the z coordinate. * @return the node nearest to the point (x,y,z). */ public final Node nodeNearest(float x, float y, float z) { Node na = nodeA(); Node nb = nodeB(); Node nc = nodeC(); double da = distanceSquared(na,x,y,z); double db = distanceSquared(nb,x,y,z); double dc = distanceSquared(nc,x,y,z); double dmin = da; Node nmin = na; if (dbindex) System.arraycopy(_a,index+1,_a,index,_n-index); return face; } /** * Facems this list so that its array length equals the number of faces. * @return the array of faces in this list, after trimming. */ public final Face[] trim() { if (_n<_a.length) { Face[] t = new Face[_n]; System.arraycopy(_a,0,t,0,_n); _a = t; } return _a; } /** * Removes all faces from this list. */ public final void clear() { _n = 0; } /** * Returns the number of faces in this list. * @return the number of faces. */ public final int nface() { return _n; } /** * Returns (by reference) the array of faces in this list. * @return the array of faces. */ public final Face[] faces() { return _a; } private int _n = 0; private Face[] _a = new Face[64]; } /** * Adds the specified node to this surface, if not already present. * @param node the node. * @return true, if node was added; false, otherwise. */ public synchronized boolean addNode(Node node) { boolean added = _mesh.addNode(node._meshNode); if (added) rebuild(); return added; } /** * Adds the specified nodes to this surface, if not already present. * @param nodes the nodes. * @return true, if all nodes were added; false, otherwise. */ public synchronized boolean addNodes(Node[] nodes) { int nnode = nodes.length; int nadded = 0; for (int inode=0; inode0) rebuild(); return nadded==nnode; } /** * Removes the specified node from this surface, if present. * @param node the node. * @return true, if node was removed; false, otherwise. */ public synchronized boolean removeNode(Node node) { boolean removed = _mesh.removeNode(node._meshNode); if (removed) rebuild(); return removed; } /** * Removes the specified nodes from this surface, if present. * @param nodes the nodes. * @return true, if all nodes were removed; false, otherwise. */ public synchronized boolean removeNodes(Node[] nodes) { int nnode = nodes.length; int nremoved = 0; for (int inode=0; inode0) rebuild(); return nremoved==nnode; } /** * Returns the number of nodes in the surface. * @return the number of nodes. */ public int countNodes() { return _mesh.countNodes(); } /** * Returns the number of faces in the surface. * @return the number of faces. */ public int countFaces() { return _faceMap.size(); } /** * Gets an iterator for all nodes in this surface. * @return the iterator. */ public synchronized NodeIterator getNodes() { return new NodeIterator() { public final boolean hasNext() { return _i.hasNext(); } public final Node next() { return (Node)_i.next().data; } private TetMesh.NodeIterator _i = _mesh.getNodes(); }; } /** * Gets an iterator for all faces in this surface. * @return the iterator. */ public synchronized FaceIterator getFaces() { return new FaceIterator() { public final boolean hasNext() { return _i.hasNext(); } public final Face next() { return _i.next(); } private Iterator _i = _faceMap.values().iterator(); }; } /** * Finds the node nearest to the point with specified coordinates. * @param x the x coordinate. * @param y the y coordinate. * @param z the z coordinate. * @return the nearest node. */ public synchronized Node findNodeNearest(float x, float y, float z) { TetMesh.Node meshNode = _mesh.findNodeNearest(x,y,z); return (Node)meshNode.data; } /** * Gets an array of face nabors of the specified node. * @param node the node for which to get nabors. * @return the array of nabors. */ public synchronized Face[] getFaceNabors(Node node) { FaceList nabors = new FaceList(); getFaceNabors(node,nabors); return nabors.trim(); } /** * Appends the face nabors of the specified node to the specified list. * @param node the node for which to get nabors. * @param nabors the list to which nabors are appended. */ public synchronized void getFaceNabors(Node node, FaceList nabors) { clearFaceMarks(); getFaceNabors(node,node._face,nabors); } /** * Returns a face that references the specified node. * @param node the node. * @return a face that references the specified node; or null, if * the node is not in the surface or the surface has no faces. */ public Face findFace(Node node) { return node._face; } /** * Returns a face that references the specified nodes. * @param node1 a node. * @param node2 a node. * @return a face that references the specified nodes; or null, * if a node is not in the surface or the surface has no faces. */ public synchronized Face findFace(Node node1, Node node2) { Face face = findFace(node1); if (face!=null) { // clearFaceMarks(); // return findFace(face,node1,node2); if (face.references(node2)) return face; Face face1 = face; face = node1.faceNext(face1); while (face!=face1 && face!=null) { if (face.references(node2)) return face; face = node1.faceNext(face); } if (face==null) { face = node1.facePrev(face1); while (face!=face1 && face!=null) { if (face.references(node2)) return face; face = node1.facePrev(face); } } } return null; } /** * Returns a face that references the specified nodes. * @param node1 a node. * @param node2 a node. * @param node3 a node. * @return a face that references the specified nodes; or null, * if a node is not in the surface or the surface has no faces. */ public synchronized Face findFace(Node node1, Node node2, Node node3) { Face face = findFace(node1,node2); // if (face!=null) { // clearFaceMarks(); // face = findFace(face,node1,node2,node3); // } if (face!=null) { if (face.references(node3)) return face; face = face.faceNabor(node3); if (face!=null && face.references(node3)) return face; } return null; } /** * Returns a directed edge AB that references the specified nodes. * @param nodeA a node. * @param nodeB a node. * @return a directed edge that references the specified nodes; * or null, if nodes A and B are not adjacent in the surface. */ public synchronized Edge findEdge(Node nodeA, Node nodeB) { TetMesh.Edge meshEdge = findMeshEdge(nodeA,nodeB); Edge edge = getEdge(meshEdge); if (meshEdge!=null && edge==null) { Face face = findFace(nodeA,nodeB); if (face!=null) { Node nodeC = otherNode(face,nodeA,nodeB); if (nodesInOrder(face,nodeA,nodeB,nodeC)) edge = new Edge(meshEdge,face); } } return edge; } /////////////////////////////////////////////////////////////////////////// // private private static class EdgeFace implements Comparable { Edge edge; Face face; double grade; EdgeFace(Edge edge, Face face, double grade) { this.edge = edge; this.face = face; this.grade = grade; } public int compareTo(EdgeFace other) { double gradeOther = other.grade; if (gradegradeOther) { return 1; } else { Edge edgeOther = other.edge; int hash = edge.hashCode(); int hashOther = edgeOther.hashCode(); if (hashhashOther) { return 1; } else { return 0; } } } } private static final int FACE_MARK_MAX = Integer.MAX_VALUE-1; // tet mesh private TetMesh _mesh = new TetMesh(); // mesh faces not yet in surf private Set _faceSet = new HashSet(); // mesh face -> surf face private Map _faceMap = new HashMap(); // mesh edge -> surf edge-face private Map _edgeMap = new HashMap(); // edge-face sorted by grade private SortedSet _edgeQueue = new TreeSet(); private int _faceMarkRed; // current value of red face mark private int _faceMarkBlue; // current value of blue face mark private void validate() { NodeIterator ni = getNodes(); while (ni.hasNext()) { Node node = ni.next(); node.validate(); } FaceIterator fi = getFaces(); while (fi.hasNext()) { Face face = fi.next(); face.validate(); } } /** * Returns the distance squared between the specified node and a point * with specified coordinates. */ private static final double distanceSquared( Node node, double x, double y, double z) { double dx = x-node.x(); double dy = y-node.y(); double dz = z-node.z(); return dx*dx+dy*dy+dz*dz; } /** * Recursively searches for any face that references n1 and n2, given * a face that references n1. If no such face exists, then returns null. * Face marks must be cleared before calling this method. */ private Face findFace(Face face, Node n1, Node n2) { if (face!=null) { mark(face); Node na = face.nodeA(); Node nb = face.nodeB(); Node nc = face.nodeC(); Face fa = face.faceA(); Face fb = face.faceB(); Face fc = face.faceC(); if (n1==na) { if (n2==nb || n2==nc || fb!=null && !isMarked(fb) && (face=findFace(fb,n1,n2))!=null || fc!=null && !isMarked(fc) && (face=findFace(fc,n1,n2))!=null) return face; } else if (n1==nb) { if (n2==nc || n2==na || fc!=null && !isMarked(fc) && (face=findFace(fc,n1,n2))!=null || fa!=null && !isMarked(fa) && (face=findFace(fa,n1,n2))!=null) return face; } else if (n1==nc) { if (n2==na || n2==nb || fa!=null && !isMarked(fa) && (face=findFace(fa,n1,n2))!=null || fb!=null && !isMarked(fb) && (face=findFace(fb,n1,n2))!=null) return face; } else { assert false:"n1 is referenced by face"; } } return null; } /** * Recursively searches for any face that references n1, n2, and n3, * given a face that references n1 and n2. If no such face exists, then * returns null. Face marks must be cleared before calling this method. */ private Face findFace(Face face, Node n1, Node n2, Node n3) { if (face!=null) { mark(face); Node na = face.nodeA(); Node nb = face.nodeB(); Node nc = face.nodeC(); Face fa = face.faceA(); Face fb = face.faceB(); Face fc = face.faceC(); if (n1==na) { if (n2==nb) { if (n3==nc || fc!=null && !isMarked(fc) && (face=findFace(fc,n1,n2,n3))!=null) return face; } else if (n2==nc) { if (n3==nb || fb!=null && !isMarked(fb) && (face=findFace(fb,n1,n2,n3))!=null) return face; } else { assert false:"n2 is referenced by face"; } } else if (n1==nb) { if (n2==na) { if (n3==nc || fc!=null && !isMarked(fc) && (face=findFace(fc,n1,n2,n3))!=null) return face; } else if (n2==nc) { if (n3==na || fa!=null && !isMarked(fa) && (face=findFace(fa,n1,n2,n3))!=null) return face; } else { assert false:"n2 is referenced by face"; } } else if (n1==nc) { if (n2==na) { if (n3==nb || fb!=null && !isMarked(fb) && (face=findFace(fb,n1,n2,n3))!=null) return face; } else if (n2==nb) { if (n3==na || fa!=null && !isMarked(fa) && (face=findFace(fa,n1,n2,n3))!=null) return face; } else { assert false:"n2 is referenced by face"; } } else { assert false:"n1 is referenced by face"; } } return null; } /** * Marks the specified face (red). Marks are used during iterations * over faces. Because faces (e.g., those faces containing a particular * node) are linked in an unordered structure, such iterations are * often performed by recursively visiting faces, and marks are used * to tag faces that have already been visited. * @param face the face to mark (red). */ private final void mark(Face face) { face._mark = _faceMarkRed; } /** * Marks the specified face red. * This is equivalent to simply marking the face. * @param face the face to mark red. */ private final void markRed(Face face) { face._mark = _faceMarkRed; } /** * Marks the specified face blue. * @param face the face to mark blue. */ private final void markBlue(Face face) { face._mark = _faceMarkBlue; } /** * Determines whether the specified face is marked (red). * @param face the face. * @return true, if the face is marked (red); false, otherwise. */ private final boolean isMarked(Face face) { return face._mark==_faceMarkRed; } /** * Determines whether the specified face is marked red. * @param face the face. * @return true, if the face is marked red; false, otherwise. */ private final boolean isMarkedRed(Face face) { return face._mark==_faceMarkRed; } /** * Determines whether the specified face is marked blue. * @param face the face. * @return true, if the face is marked blue; false, otherwise. */ private final boolean isMarkedBlue(Face face) { return face._mark==_faceMarkBlue; } /** * Clears all face marks, so that no face is marked. This can usually * be accomplished without iterating over all faces in the mesh. */ private synchronized void clearFaceMarks() { // If the mark is about to overflow, we must zero all the marks. if (_faceMarkRed==FACE_MARK_MAX) { Iterator fi = _faceMap.values().iterator(); while (fi.hasNext()) { Face face = fi.next(); face._mark = 0; } _faceMarkRed = 0; _faceMarkBlue = 0; } // Usually, we simply increment/decrement the mark values. ++_faceMarkRed; --_faceMarkBlue; } /** * Recursively adds face nabors of the specified node to the specified list. * The face marks must be cleared before calling this method. This method * could be made shorter by using another recursive method, but this longer * inlined version is more efficient. */ private void getFaceNabors(Node node, Face face, FaceList nabors) { if (face!=null) { mark(face); nabors.add(face); Node na = face.nodeA(); Node nb = face.nodeB(); Node nc = face.nodeC(); Face fa = face.faceA(); Face fb = face.faceB(); Face fc = face.faceC(); if (node==na) { if (fb!=null && !isMarked(fb)) getFaceNabors(node,fb,nabors); if (fc!=null && !isMarked(fc)) getFaceNabors(node,fc,nabors); } else if (node==nb) { if (fc!=null && !isMarked(fc)) getFaceNabors(node,fc,nabors); if (fa!=null && !isMarked(fa)) getFaceNabors(node,fa,nabors); } else if (node==nc) { if (fa!=null && !isMarked(fa)) getFaceNabors(node,fa,nabors); if (fb!=null && !isMarked(fb)) getFaceNabors(node,fb,nabors); } else { assert false:"node is referenced by face"; } } } private Edge getEdge(TetMesh.Edge meshEdge) { EdgeFace edgeFace = _edgeMap.get(meshEdge); return (edgeFace!=null)?edgeFace.edge:null; } private EdgeFace getEdgeFace(Edge edge) { return _edgeMap.get(edge._meshEdge); } private EdgeFace getBestEdgeFace() { return (!_edgeQueue.isEmpty())?_edgeQueue.last():null; } private EdgeFace getNextEdgeFace(EdgeFace edgeFace) { SortedSet headSet = _edgeQueue.headSet(edgeFace); return (!headSet.isEmpty())?headSet.last():null; } private EdgeFace addEdge(Edge edge) { // trace("addEdge: edge="+edge); EdgeFace edgeFace = makeEdgeFace(edge); assert edgeFace!=null:"edgeFace!=null"; Object edgeFaceOld = _edgeMap.put(edge._meshEdge,edgeFace); assert edgeFaceOld==null:"edge was not mapped"; boolean added = _edgeQueue.add(edgeFace); assert added:"edgeFace was not in queue"; return edgeFace; } private void removeEdge(Edge edge) { // trace("removeEdge: edge="+edge); EdgeFace edgeFace = getEdgeFace(edge); assert edgeFace!=null:"edgeFace!=null"; Object edgeFaceOld = _edgeMap.remove(edge._meshEdge); assert edgeFaceOld!=null:"edge was mapped"; boolean removed = _edgeQueue.remove(edgeFace); assert removed:"edgeFace was in queue"; } private void addFace(Face face) { boolean removed = _faceSet.remove(face._meshFace) || _faceSet.remove(face._meshFace.mate()); assert removed:"face not already in surface"; Face faceOld = _faceMap.put(face._meshFace,face); assert faceOld==null:"face not already in surface"; } private void removeFace(Face face) { _faceMap.remove(face._meshFace); } private void init(Face face) { trace("init: face="+face); trace(" meshFace A="+face._meshFace.nodeA()); trace(" meshFace B="+face._meshFace.nodeB()); trace(" meshFace C="+face._meshFace.nodeC()); face._faceA = null; face._faceB = null; face._faceC = null; Node nodeA = face.nodeA(); Node nodeB = face.nodeB(); Node nodeC = face.nodeC(); nodeA.setFace(face); nodeB.setFace(face); nodeC.setFace(face); Edge edgeCB = makeEdge(nodeC,nodeB,face); Edge edgeBA = makeEdge(nodeB,nodeA,face); Edge edgeAC = makeEdge(nodeA,nodeC,face); nodeA.setEdgeBefore(edgeBA); nodeB.setEdgeBefore(edgeCB); nodeC.setEdgeBefore(edgeAC); nodeA.setEdgeAfter(edgeAC); nodeB.setEdgeAfter(edgeBA); nodeC.setEdgeAfter(edgeCB); addEdge(edgeCB); addEdge(edgeBA); addEdge(edgeAC); addFace(face); } private void extend(Edge edge, Face face) { trace("extend: edge="+edge+" face="+face); trace(" meshEdge A="+edge._meshEdge.nodeA()); trace(" meshEdge B="+edge._meshEdge.nodeB()); trace(" meshFace A="+face._meshFace.nodeA()); trace(" meshFace B="+face._meshFace.nodeB()); trace(" meshFace C="+face._meshFace.nodeC()); assert edge.isOnBoundary(); Node nodeA = edge.nodeA(); Node nodeB = edge.nodeB(); Node nodeC = otherNode(face,nodeA,nodeB); nodeC.setFace(face); linkFaces(face,nodeC,edge.faceRight(),edge.nodeRight()); Edge edgeAC = makeEdge(nodeA,nodeC,face); Edge edgeCB = makeEdge(nodeC,nodeB,face); nodeA.setEdgeAfter(edgeAC); nodeB.setEdgeBefore(edgeCB); nodeC.setEdgeAfter(edgeCB); nodeC.setEdgeBefore(edgeAC); removeEdge(edge); addFace(face); addEdge(edgeAC); addEdge(edgeCB); } private void fillEar(Edge edge, Face face) { trace("fillEar: edge="+edge+" face="+face); trace(" meshEdge A="+edge._meshEdge.nodeA()); trace(" meshEdge B="+edge._meshEdge.nodeB()); trace(" meshFace A="+face._meshFace.nodeA()); trace(" meshFace B="+face._meshFace.nodeB()); trace(" meshFace C="+face._meshFace.nodeC()); Node nodeA = edge.nodeA(); Node nodeB = edge.nodeB(); Node nodeC = otherNode(face,nodeA,nodeB); Edge edge1 = nodeC.edgeBefore(); Edge edge2 = nodeC.edgeAfter(); Node node1 = edge1.nodeA(); Node node2 = edge2.nodeB(); if (node2==nodeA) { linkFaces(face,nodeC,edge.faceRight(),edge.nodeRight()); linkFaces(face,nodeB,edge2.faceRight(),edge2.nodeRight()); Edge edgeCB = makeEdge(nodeC,nodeB,face); nodeC.setEdgeAfter(edgeCB); nodeB.setEdgeBefore(edgeCB); nodeA.setEdgeAfter(null); nodeA.setEdgeBefore(null); removeEdge(edge); removeEdge(edge2); addFace(face); addEdge(edgeCB); } else if (node1==nodeB) { linkFaces(face,nodeC,edge.faceRight(),edge.nodeRight()); linkFaces(face,nodeA,edge1.faceRight(),edge1.nodeRight()); Edge edgeAC = makeEdge(nodeA,nodeC,face); nodeA.setEdgeAfter(edgeAC); nodeC.setEdgeBefore(edgeAC); nodeB.setEdgeAfter(null); nodeB.setEdgeBefore(null); removeEdge(edge); removeEdge(edge1); addFace(face); addEdge(edgeAC); } else { assert false:"ear is valid"; } } private void fillHole(Edge edge, Face face) { trace("fillHole: edge="+edge+" face="+face); trace(" meshEdge A="+edge._meshEdge.nodeA()); trace(" meshEdge B="+edge._meshEdge.nodeB()); trace(" meshFace A="+face._meshFace.nodeA()); trace(" meshFace B="+face._meshFace.nodeB()); trace(" meshFace C="+face._meshFace.nodeC()); Edge edgeAB = edge; Edge edgeBC = edge.edgeAfter(); Edge edgeCA = edge.edgeBefore(); assert edgeAB.isOnBoundary(); assert edgeBC.isOnBoundary(); assert edgeCA.isOnBoundary(); Face faceAB = edgeAB.faceRight(); Face faceBC = edgeBC.faceRight(); Face faceCA = edgeCA.faceRight(); Node nodeA = edgeAB.nodeA(); Node nodeB = edgeBC.nodeA(); Node nodeC = edgeCA.nodeA(); linkFaces(face,nodeA,faceBC,otherNode(faceBC,nodeB,nodeC)); linkFaces(face,nodeB,faceCA,otherNode(faceCA,nodeA,nodeC)); linkFaces(face,nodeC,faceAB,otherNode(faceAB,nodeA,nodeB)); nodeA.setEdgeBefore(null); nodeB.setEdgeBefore(null); nodeC.setEdgeBefore(null); nodeA.setEdgeAfter(null); nodeB.setEdgeAfter(null); nodeC.setEdgeAfter(null); removeEdge(edgeAB); removeEdge(edgeBC); removeEdge(edgeCA); addFace(face); } /** * Returns a valid twin with grade higher than the specified edge-face. * Returns null, if no such twin exists. */ private EdgeFace findTwin(EdgeFace edgeFace) { // trace("findTwin"); Edge edge = edgeFace.edge; Face face = edgeFace.face; double grade = edgeFace.grade; Node nodeA = edge.nodeA(); Node nodeB = edge.nodeB(); Node nodeC = otherNode(face,nodeA,nodeB); assert nodeA.isOnBoundary(); assert nodeB.isOnBoundary(); assert nodeC.isOnBoundary(); Node node1 = nodeC.edgeBefore().nodeA(); assert node1!=nodeA; assert node1!=nodeB; if (node1.isOnBoundary()) { Edge edgeTwin = node1.edgeAfter(); assert nodeC==edgeTwin.nodeB(); removeEdge(edgeTwin); EdgeFace edgeFaceTwin = addEdge(edgeTwin); Face faceTwin = edgeFaceTwin.face; double gradeTwin = edgeFaceTwin.grade; if (faceTwin!=null && nodesInOrder(faceTwin,node1,nodeC,nodeB) && gradeTwin>grade) return edgeFaceTwin; } Node node2 = nodeC.edgeAfter().nodeB(); assert node2!=nodeA; assert node2!=nodeB; if (node2.isOnBoundary()) { Edge edgeTwin = node2.edgeBefore(); assert nodeC==edgeTwin.nodeA(); removeEdge(edgeTwin); EdgeFace edgeFaceTwin = addEdge(edgeTwin); Face faceTwin = edgeFaceTwin.face; double gradeTwin = edgeFaceTwin.grade; if (faceTwin!=null && nodesInOrder(faceTwin,node2,nodeA,nodeC) && gradeTwin>grade) return edgeFaceTwin; } return null; } /** * Glues the specified edge-face to its twin. */ private void glue(Edge edge, Face face, Edge edgeTwin, Face faceTwin) { trace("glue: edge="+edge+" face="+face); trace(" meshEdge A="+edge._meshEdge.nodeA()); trace(" meshEdge B="+edge._meshEdge.nodeB()); trace(" meshFace A="+face._meshFace.nodeA()); trace(" meshFace B="+face._meshFace.nodeB()); trace(" meshFace C="+face._meshFace.nodeC()); trace(" meshEdgeTwin A="+edgeTwin._meshEdge.nodeA()); trace(" meshEdgeTwin B="+edgeTwin._meshEdge.nodeB()); trace(" meshFaceTwin A="+faceTwin._meshFace.nodeA()); trace(" meshFaceTwin B="+faceTwin._meshFace.nodeB()); trace(" meshFaceTwin C="+faceTwin._meshFace.nodeC()); Node nodeA = edge.nodeA(); Node nodeB = edge.nodeB(); Node nodeC = otherNode(face,nodeA,nodeB); assert nodeA.isOnBoundary(); assert nodeB.isOnBoundary(); assert nodeC.isOnBoundary(); // Remove edge and its twin; add face and its twin. removeEdge(edge); removeEdge(edgeTwin); addFace(face); addFace(faceTwin); // If face is ABC and its twin is ACD, ... if (faceTwin.references(nodeA)) { Node nodeD = nodeC.edgeAfter().nodeB(); assert nodeD.isOnBoundary(); // If face twin is a hole, fill it. if (nodeD.edgeAfter()==nodeA.edgeBefore()) { Edge edgeDA = nodeD.edgeAfter(); nodeA.setEdgeBefore(null); nodeD.setEdgeBefore(null); nodeA.setEdgeAfter(null); nodeD.setEdgeAfter(null); removeEdge(edgeDA); } // Else face twin is an ear, so fill it. else { Edge edgeAD = makeEdge(nodeA,nodeD,faceTwin); nodeA.setEdgeAfter(edgeAD); nodeD.setEdgeBefore(edgeAD); addEdge(edgeAD); } // For either hole or ear, ... Edge edgeCB = makeEdge(nodeC,nodeB,face); nodeC.setEdgeAfter(edgeCB); nodeB.setEdgeBefore(edgeCB); addEdge(edgeCB); linkFaces(face,nodeB,faceTwin,nodeD); linkFaces(face,nodeC,edge.faceRight(),edge.nodeRight()); linkFaces(faceTwin,nodeA,edgeTwin.faceRight(),edgeTwin.nodeRight()); } // Else if face is ABC and its twin is BDC, ... else if (faceTwin.references(nodeB)) { Node nodeD = nodeC.edgeBefore().nodeA(); assert nodeD.isOnBoundary(); // If face twin is a hole, fill it. if (nodeD.edgeBefore()==nodeB.edgeAfter()) { Edge edgeBD = nodeD.edgeBefore(); nodeB.setEdgeBefore(null); nodeD.setEdgeBefore(null); nodeB.setEdgeAfter(null); nodeD.setEdgeAfter(null); removeEdge(edgeBD); } // Else face twin is an ear, so fill it. else { Edge edgeDB = makeEdge(nodeD,nodeB,faceTwin); nodeD.setEdgeAfter(edgeDB); nodeB.setEdgeBefore(edgeDB); addEdge(edgeDB); } // For either hole or ear, ... Edge edgeAC = makeEdge(nodeA,nodeC,face); nodeA.setEdgeAfter(edgeAC); nodeC.setEdgeBefore(edgeAC); addEdge(edgeAC); linkFaces(face,nodeA,faceTwin,nodeD); linkFaces(face,nodeC,edge.faceRight(),edge.nodeRight()); linkFaces(faceTwin,nodeB,edgeTwin.faceRight(),edgeTwin.nodeRight()); } } private boolean stitch(EdgeFace edgeFace) { // validate(); Edge edge = edgeFace.edge; Face face = edgeFace.face; // assert face!=null; // assert _faceSet.contains(face._meshFace) || // _faceSet.contains(face._meshFace.mate()); // Nodes A and B of edge, and the other node C in the face. Node nodeA = edge.nodeA(); Node nodeB = edge.nodeB(); Node nodeC = otherNode(face,nodeA,nodeB); // If face is not valid, replace it with a valid one, if possible. if (!validForFace(nodeA,nodeB,nodeC)) { removeEdge(edge); addEdge(edge); return true; } // If node C is not in the surface, then extend. if (!nodeC.isInSurface()) { extend(edge,face); return true; } // Else if node C is on the surface boundary, ... else if (nodeC.isOnBoundary()) { // Nabor nodes 1 and 2 of node C, also on the surface boundary. Node node1 = nodeC.edgeBefore().nodeA(); Node node2 = nodeC.edgeAfter().nodeB(); // If both edge nodes A and B are nabors of node C, fill hole. if (node1==nodeB && node2==nodeA) { fillHole(edge,face); return true; } // Else if either node A or node B is a nabor of node C, fill ear. else if (node1==nodeB || node2==nodeA) { fillEar(edge,face); return true; } // Else ... else { // If face has a valid twin with higher grade, glue. EdgeFace edgeFaceTwin = findTwin(edgeFace); if (edgeFaceTwin!=null) { Edge edgeTwin = edgeFaceTwin.edge; Face faceTwin = edgeFaceTwin.face; glue(edge,face,edgeTwin,faceTwin); return true; } else { return false; } } } // Else the face is not valid, and we should not be here! else { assert false:"valid face for extend, fill ear, fill hole, or glue"; return false; } } private void rebuild() { trace("rebuild"); init(); while (surf()) ; } private void init() { trace(" init: ntets="+_mesh.countTets()); _faceSet.clear(); _faceMap.clear(); _edgeMap.clear(); _edgeQueue.clear(); TetMesh.TetIterator ti = _mesh.getTets(); while (ti.hasNext()) { TetMesh.Tet tet = ti.next(); TetMesh.Node a = tet.nodeA(); TetMesh.Node b = tet.nodeB(); TetMesh.Node c = tet.nodeC(); TetMesh.Node d = tet.nodeD(); TetMesh.Face[] meshFaces = { new TetMesh.Face(a,b,c,tet), new TetMesh.Face(b,d,c,tet), new TetMesh.Face(c,d,a,tet), new TetMesh.Face(d,b,a,tet), }; for (int i=0; i<4; ++i) { TetMesh.Face meshFacei = meshFaces[i]; if (!_faceSet.contains(meshFacei.mate())) { _faceSet.add(meshFacei); trace(" init: added face"+meshFacei); trace(" node A="+meshFacei.nodeA()); trace(" node B="+meshFacei.nodeB()); trace(" node C="+meshFacei.nodeC()); } } ((Node)a.data).init(); ((Node)b.data).init(); ((Node)c.data).init(); ((Node)d.data).init(); } trace(" init: _faceSet size="+_faceSet.size()); } /** * Creates a part of the surface. Returns true, if any faces were created. * Typically, this method is called repeatedly until it creates no faces * and returns false. */ private boolean surf() { int nface = countFaces(); // If no mesh faces left in set, simply return. if (_faceSet.isEmpty()) return false; // Among mesh faces in set, find mesh face with smallest circumradius. TetMesh.Face meshFace = null; double rrmin = Double.MAX_VALUE; double[] cc = new double[3]; Iterator mfi = _faceSet.iterator(); while (mfi.hasNext()) { TetMesh.Face meshFacei = mfi.next(); double rr = meshFacei.centerCircle(cc); if (rr faceList = new ArrayList(); mfi = _faceSet.iterator(); while (mfi.hasNext()) { TetMesh.Face meshFacei = mfi.next(); Node nodeA = (Node)meshFacei.nodeA().data; Node nodeB = (Node)meshFacei.nodeB().data; Node nodeC = (Node)meshFacei.nodeC().data; if (nodeA.isInSurface() || nodeB.isInSurface() || nodeC.isInSurface()) { faceList.add(meshFacei); } } mfi = faceList.iterator(); while (mfi.hasNext()) { TetMesh.Face meshFacei = mfi.next(); _faceSet.remove(meshFacei); } // We may have more faces. trace(" surf: more faces = "+(countFaces()>nface)); return countFaces()>nface; } private static boolean nodesInOrder(Face face, Node na, Node nb, Node nc) { Node fa = face.nodeA(); Node fb = face.nodeB(); Node fc = face.nodeC(); return na==fa && nb==fb && nc==fc || na==fb && nb==fc && nc==fa || na==fc && nb==fa && nc==fb; } private static Node otherNode(Face face, Node na, Node nb) { Node fa = face.nodeA(); Node fb = face.nodeB(); Node fc = face.nodeC(); if (na==fa) { if (nb==fb) { return fc; } else if (nb==fc) { return fb; } else { return null; } } else if (na==fb) { if (nb==fa) { return fc; } else if (nb==fc) { return fa; } else { return null; } } else if (na==fc) { if (nb==fa) { return fb; } else if (nb==fb) { return fa; } else { return null; } } else { return null; } } private static TetMesh.Node otherNode( TetMesh.Face face, TetMesh.Node na, TetMesh.Node nb) { TetMesh.Node fa = face.nodeA(); TetMesh.Node fb = face.nodeB(); TetMesh.Node fc = face.nodeC(); if (na==fa) { if (nb==fb) { return fc; } else if (nb==fc) { return fb; } else { return null; } } else if (na==fb) { if (nb==fa) { return fc; } else if (nb==fc) { return fa; } else { return null; } } else if (na==fc) { if (nb==fa) { return fb; } else if (nb==fb) { return fa; } else { return null; } } else { return null; } } private static void linkFaces( Face face, Node node, Face faceNabor, Node nodeNabor) { if (face!=null) { if (node==face.nodeA()) { face._faceA = faceNabor; } else if (node==face.nodeB()) { face._faceB = faceNabor; } else if (node==face.nodeC()) { face._faceC = faceNabor; } else { assert false:"node referenced by face"; } } if (faceNabor!=null) { if (nodeNabor==faceNabor.nodeA()) { faceNabor._faceA = face; } else if (nodeNabor==faceNabor.nodeB()) { faceNabor._faceB = face; } else if (nodeNabor==faceNabor.nodeC()) { faceNabor._faceC = face; } else { assert false:"nodeNabor referenced by faceNabor"; } } } static float normalVector(TetMesh.Face meshFace, float[] v) { TetMesh.Node na = meshFace.nodeA(); TetMesh.Node nb = meshFace.nodeB(); TetMesh.Node nc = meshFace.nodeC(); double xa = na.x(); double ya = na.y(); double za = na.z(); double xb = nb.x(); double yb = nb.y(); double zb = nb.z(); double xc = nc.x(); double yc = nc.y(); double zc = nc.z(); double x0 = xc-xa; double y0 = yc-ya; double z0 = zc-za; double x1 = xa-xb; double y1 = ya-yb; double z1 = za-zb; double x2 = y0*z1-y1*z0; double y2 = x1*z0-x0*z1; double z2 = x0*y1-x1*y0; double alpha = x2*x2+y2*y2+z2*z2; double delta = sqrt(alpha); double scale = (delta>0.0)?1.0/delta:1.0; if (v!=null) { v[0] = (float)(x2*scale); v[1] = (float)(y2*scale); v[2] = (float)(z2*scale); } return (float)(0.5*scale*alpha); } static double normalVector(TetMesh.Face meshFace, double[] v) { TetMesh.Node na = meshFace.nodeA(); TetMesh.Node nb = meshFace.nodeB(); TetMesh.Node nc = meshFace.nodeC(); double xa = na.x(); double ya = na.y(); double za = na.z(); double xb = nb.x(); double yb = nb.y(); double zb = nb.z(); double xc = nc.x(); double yc = nc.y(); double zc = nc.z(); double x0 = xc-xa; double y0 = yc-ya; double z0 = zc-za; double x1 = xa-xb; double y1 = ya-yb; double z1 = za-zb; double x2 = y0*z1-y1*z0; double y2 = x1*z0-x0*z1; double z2 = x0*y1-x1*y0; double alpha = x2*x2+y2*y2+z2*z2; double delta = sqrt(alpha); double scale = (delta>0.0)?1.0/delta:1.0; if (v!=null) { v[0] = x2*scale; v[1] = y2*scale; v[2] = z2*scale; } return 0.5*scale*alpha; } private static double angle(TetMesh.Face face1, TetMesh.Face face2) { double[] v1 = new double[3]; double[] v2 = new double[3]; normalVector(face1,v1); normalVector(face2,v2); double cos12 = v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2]; return acos(cos12); } private TetMesh.Edge findMeshEdge(Node nodeA, Node nodeB) { TetMesh.Node meshNodeA = nodeA._meshNode; TetMesh.Node meshNodeB = nodeB._meshNode; TetMesh.Edge meshEdge = _mesh.findEdge(meshNodeA,meshNodeB); if (meshEdge!=null && meshNodeA!=meshEdge.nodeA()) meshEdge = meshEdge.mate(); return meshEdge; } private Edge makeEdge(Node nodeA, Node nodeB, Face face) { TetMesh.Edge meshEdge = findMeshEdge(nodeA,nodeB); return (meshEdge!=null)?new Edge(meshEdge,face):null; } private static final double VV_SLIVER = cos(5.0*PI/6.0); private static final double VV_LARGE = cos(1.1*PI/2.0); /** * Makes and edge-face for the specified edge. The edge already has * a mesh face incident on its right side. This method computes the * best candidate face for the left side of the edge. The edge and * candidate face form an edge-face pair, with a grade that depends * on the circumradius of the candidate and on the angle between * the normal vectors for the right and left faces. Small circumradii * and small angles correspond to high grades. */ private EdgeFace makeEdgeFace(Edge edge) { assert edge.isOnBoundary(); Node nodeA = edge.nodeA(); Node nodeB = edge.nodeB(); // Mesh face incident on right side of edge is already in surface. TetMesh.Face meshFace = edge.faceRight()._meshFace; // Vector normal to mesh face already in surface. double[] v = new double[3]; normalVector(meshFace,v); // Mesh nodes A and B of edge. TetMesh.Edge meshEdge = edge._meshEdge; TetMesh.Node meshNodeA = meshEdge.nodeA(); TetMesh.Node meshNodeB = meshEdge.nodeB(); // Variables used to find the best mesh face not yet in surface. double[] cc = new double[3]; double[] vi = new double[3]; double rrBest = Double.MAX_VALUE; double vvBest = -1.0; TetMesh.Face mfBest = null; TetMesh.Face mfMate = meshFace.mate(); // Mesh faces incident on left side of edge. One of these is the // mate of the right mesh face that is already in the surface. TetMesh.Face[] meshFaces = _mesh.getFaceNabors(meshEdge); int n = meshFaces.length; // Find the best mesh face not yet in the surface. for (int i=0; iVV_SLIVER) { // Square of mesh face circumradius. double rr = mf.centerCircle(cc); // If circumradius is the best found so far, ... if (rrVV_LARGE)?1.0/rrBest:vvBest-1.0; if (grade<=0.0) face = null; return new EdgeFace(edge,face,grade); } /** * Determines whether the specified nodes are an internal edge. * An internal edge is not on the boundary; it has two nabor faces. */ private boolean hasInternalEdge(Node nodeA, Node nodeB) { Face face = findFace(nodeA,nodeB); if (face==null) return false; face = face.faceNabor(otherNode(face,nodeA,nodeB)); if (face==null) return false; return true; } /** * Determines whether the specified nodes would form a valid face. * Assumes that nodes A and B are a boundary edge. Node C is the * other node that would form the face in question. */ private boolean validForFace(Node nodeA, Node nodeB, Node nodeC) { return !nodeC.isInSurface() || nodeC.isOnBoundary() && !hasInternalEdge(nodeB,nodeC) && !hasInternalEdge(nodeC,nodeA); } private static final boolean TRACE = false; private static void trace(String s) { if (TRACE) System.out.println(s); } }