/* jCAE stand for Java Computer Aided Engineering. Features are : Small CAD modeler, Finite element mesher, Plugin architecture. Copyright (C) 2005,2006, by EADS CRC Copyright (C) 2007,2008,2009,2010, by EADS France This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.jcae.mesh.amibe.patch; import org.jcae.mesh.amibe.ds.AbstractHalfEdge; import org.jcae.mesh.amibe.ds.VirtualHalfEdge; import org.jcae.mesh.amibe.ds.Triangle; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.jcae.mesh.amibe.metrics.KdTree; /** * A handle to abstract edge objects for initial 2D mesh. * This class implements some features which are only relevant * to the initial 2D mesh. In particular, boundary edges have * not yet been rebuilt, so we cannot check whether the OUTER * attribute is set but need to test if vertices are equal to * the infinite point instead. */ public class VirtualHalfEdge2D extends VirtualHalfEdge { private static final Logger logger=Logger.getLogger(VirtualHalfEdge2D.class.getName()); public VirtualHalfEdge2D() { super(); } /** * Create an object to handle data about a triangle. * * @param t geometrical triangle. * @param o a number between 0 and 2 determining an edge. */ public VirtualHalfEdge2D(Triangle t, int o) { super(t, o); } /** * Copy current <code>VirtualHalfEdge</code> and move it to the counterclockwise * previous edge which has the same origin. * * @param that already allocated <code>VirtualHalfEdge</code> where data are * copied */ /* Method never used private final AbstractHalfEdge prevOrigin(AbstractHalfEdge that) { return sym(that).next(); } */ /** * Move counterclockwise to the previous edge with same origin. * @return current instance after its transformation */ private AbstractHalfEdge prevOrigin() { return sym().next(); } /** * Copy current <code>VirtualHalfEdge</code> and move it to the counterclockwise * following edge which has the same destination. * * @param that already allocated <code>VirtualHalfEdge</code> where data are * copied */ /* Method never used private final AbstractHalfEdge nextDest(AbstractHalfEdge that) { return sym(that).prev(); } */ /** * Move counterclockwise to the following edge with same * destination. * @return current instance after its transformation */ private AbstractHalfEdge nextDest() { return sym().prev(); } /** * Copy current <code>VirtualHalfEdge</code> and move it to the counterclockwise * previous edge which has the same destination. * * @param that already allocated <code>VirtualHalfEdge</code> where data are * copied */ /* Method never used private final AbstractHalfEdge prevDest(AbstractHalfEdge that) { return next(that).sym(); } */ /** * Move counterclockwise to the previous edge with same * destination. * @return current instance after its transformation */ /* Method never used private final AbstractHalfEdge prevDest() { return next().sym(); } */ /** * Copy current <code>VirtualHalfEdge</code> and move it to the counterclockwise * following edge which has the same apex. * * @param that already allocated <code>VirtualHalfEdge</code> where data are * copied */ /* Method never used private final AbstractHalfEdge nextApex(AbstractHalfEdge that) { return next(that).sym().next(); } */ /** * Move counterclockwise to the following edge with same apex. * @return current instance after its transformation */ private AbstractHalfEdge nextApex() { return next().sym().next(); } /* * Copy an <code>VirtualHalfEdge</code> and move it to the clockwise * previous edge which has the same apex. * * @param o source <code>VirtualHalfEdge</code> * @param that already allocated <code>VirtualHalfEdge</code> where data are * copied */ /* Method never used private final AbstractHalfEdge prevApex(AbstractHalfEdge that) { return prev(that).sym().prev(); } */ private AbstractHalfEdge prevApex() { return prev().sym().prev(); } /* * a * , * /|\ * / | \ * oldLeft / | \ oldRight * / v+ \ * / / \ \ * / / \ \ * / / \ \ * o '---------------` d * (this) */ /** * Splits a triangle into three new triangles by inserting a vertex. * * Two new triangles have to be created, the last one is * updated. For efficiency reasons, no checks are performed to * ensure that the vertex being inserted is contained by this * triangle. Once triangles are created, edges are swapped if * they are not Delaunay. * * If edges are not swapped after vertex is inserted, the quality of * newly created triangles has decreased, and the vertex is eventually * not inserted unless the <code>force</code> argument is set to * <code>true</code>. * * Origin and destination points must not be at infinite, which * is the case when current triangle is returned by * getSurroundingTriangle(). If apex is Mesh.outerVertex, then * getSurroundingTriangle() ensures that v.onLeft(o,d) > 0. * * @param v vertex being inserted. * @param modifiedTriangles if not null, this set of triangles is updated by adding all triangles modified during this operation. * @param force if <code>false</code>, the vertex is inserted only if some edges were swapped after its insertion. If <code>true</code>, the vertex is unconditionnally inserted. * @return number of edges swapped during insertion. If it is 0, vertex has not been inserted. */ public final int split3(Mesh2D mesh, Vertex2D v, Set<Triangle> modifiedTriangles, boolean force) { if (logger.isLoggable(Level.FINE)) logger.fine("Split VirtualHalfEdge2D "+this+"\nat Vertex "+v); Triangle backup = mesh.createTriangle(tri); // Aliases VirtualHalfEdge2D oldLeft = mesh.poolVH2D[0]; VirtualHalfEdge2D oldRight = mesh.poolVH2D[1]; VirtualHalfEdge2D oldSymLeft = null; VirtualHalfEdge2D oldSymRight = null; prevOTri(this, oldLeft); // = (aod) nextOTri(this, oldRight); // = (dao) oldSymLeft = mesh.poolVH2D[2]; symOTri(oldLeft, oldSymLeft); // = (oa*) oldSymRight = mesh.poolVH2D[3]; symOTri(oldRight, oldSymRight); // = (ad*) // Set vertices of newly created and current triangles Vertex2D o = (Vertex2D) origin(); assert o != mesh.outerVertex; Vertex2D d = (Vertex2D) destination(); assert d != mesh.outerVertex; Vertex2D a = (Vertex2D) apex(); Triangle t1 = mesh.createTriangle(a, o, v); Triangle t2 = mesh.createTriangle(d, a, v); VirtualHalfEdge2D newLeft = new VirtualHalfEdge2D(t1, 2); VirtualHalfEdge2D newRight = new VirtualHalfEdge2D(t2, 2); if (oldLeft.attributes != 0) { newLeft.attributes = oldLeft.attributes; newLeft.pushAttributes(); oldLeft.attributes = 0; oldLeft.pushAttributes(); } if (oldRight.attributes != 0) { newRight.attributes = oldRight.attributes; newRight.pushAttributes(); oldRight.attributes = 0; oldRight.pushAttributes(); } v.setLink(tri); a.setLink(newLeft.tri); // Move apex of current VirtualHalfEdge2D. As a consequence, // oldLeft is now (vod) and oldRight is changed to (dvo). setApex(v); newLeft.glue(oldSymLeft); newRight.glue(oldSymRight); // Creates 3 inner links newLeft.next(); // = (ova) newLeft.glue(oldLeft); newRight.prev(); // = (vda) newRight.glue(oldRight); newLeft.next(); // = (vao) newRight.prev(); // = (avd) newLeft.glue(newRight); // Data structures have been created, search now for non-Delaunay // edges. Re-use newLeft to walk through new vertex ring. newLeft.next(); // = (aov) Triangle newTri1 = newLeft.tri; Triangle newTri2 = newRight.tri; if (logger.isLoggable(Level.FINE)) logger.fine("New triangles:\n"+this+"\n"+newRight+"\n"+newLeft); // newRight is reused int ret = newLeft.checkAndSwap(mesh, modifiedTriangles, false, newRight); if (!force && 0 == ret) { // v has been inserted and no edges are swapped, // thus global quality has been decreased. // Remove v in such cases. tri.copy(backup); o.setLink(tri); d.setLink(tri); a.setLink(tri); nextOTri(this, oldLeft); // = (dao) oldLeft.glue(oldSymRight); oldLeft.next(); // = (aod) oldLeft.glue(oldSymLeft); return ret; } mesh.add(newTri1); mesh.add(newTri2); if (modifiedTriangles != null) { modifiedTriangles.add(tri); modifiedTriangles.add(newTri1); modifiedTriangles.add(newTri2); } mesh.getKdTree().add(v); return ret; } // Called from BasicMesh to improve initial mesh public final int checkSmallerAndSwap(Mesh2D mesh) { // As checkAndSwap modifies its arguments, 'this' // must be protected. VirtualHalfEdge2D ot1 = new VirtualHalfEdge2D(); VirtualHalfEdge2D sym = new VirtualHalfEdge2D(); copyOTri(this, ot1); return ot1.checkAndSwap(mesh, null, true, sym); } private int checkAndSwap(Mesh2D mesh, Set<Triangle> modifiedTriangles, boolean smallerDiag, VirtualHalfEdge2D sym) { int nrSwap = 0; int totNrSwap = 0; Vertex2D v = (Vertex2D) apex(); assert v != mesh.outerVertex; // Loops around v Vertex2D first = (Vertex2D) origin(); while (true) { if (canSwap(mesh, v, smallerDiag, sym)) { if (modifiedTriangles != null) { modifiedTriangles.add(tri); modifiedTriangles.add(sym.tri); } swap(mesh); nrSwap++; totNrSwap++; } else { // This routine may be called before boundaries // are recreated, so VirtualHalfEdge.nextApexLoop // is not relevant here. nextApexLoopNoBoundaries(mesh); if (origin() == first) { // If no swap has been performed, processing is over if (nrSwap == 0) break; nrSwap = 0; } } } return totNrSwap; } private boolean canSwap(Mesh2D mesh, Vertex2D v, boolean smallerDiag, VirtualHalfEdge2D sym) { if (hasAttributes(BOUNDARY | NONMANIFOLD | OUTER)) return false; Vertex2D o = (Vertex2D) origin(); Vertex2D d = (Vertex2D) destination(); symOTri(this, sym); Vertex2D a = (Vertex2D) sym.apex(); if (o == mesh.outerVertex) return (v.onLeft(mesh.getKdTree(), d, a) < 0L); if (d == mesh.outerVertex) return (v.onLeft(mesh.getKdTree(), a, o) < 0L); if (a == mesh.outerVertex) return (v.onLeft(mesh.getKdTree(), o, d) == 0L); if (!smallerDiag) return !isDelaunay(mesh, a); return !a.isSmallerDiagonale(mesh, this); } private void nextApexLoopNoBoundaries(Mesh2D mesh) { if (destination() == mesh.outerVertex) { // Loop clockwise to another boundary // and start again from there. do { prevApex(); } while (origin() != mesh.outerVertex); } else nextApex(); } /** * Tries to rebuild a boundary edge by swapping edges. * * This routine is applied to an oriented triangle, its origin * is an end point of the boundary edge to rebuild. The other end * point is passed as an argument. Current oriented triangle has * been set up by calling routine so that it is the leftmost edge * standing to the right of the boundary edge. * A traversal between end points is performed, and intersected * edges are swapped if possible. At exit, current oriented * triangle has <code>end</code> as its origin, and is the * rightmost edge standing to the left of the inverted edge. * This algorithm can then be called iteratively back and forth, * and it is known that it is guaranteed to finish. * * @param end end point of the boundary edge. * @return the number of intersected edges. */ final int forceBoundaryEdge(Mesh2D mesh, Vertex2D end) { long newl, oldl; int count = 0; Vertex2D start = (Vertex2D) origin(); assert start != mesh.outerVertex; assert end != mesh.outerVertex; KdTree kdTree = mesh.getKdTree(); next(); while (true) { count++; Vertex2D o = (Vertex2D) origin(); Vertex2D d = (Vertex2D) destination(); Vertex2D a = (Vertex2D) apex(); assert a != mesh.outerVertex : ""+this; symOTri(this, mesh.poolVH2D[0]); mesh.poolVH2D[0].next(); Vertex2D n = (Vertex2D) mesh.poolVH2D[0].destination(); assert n != mesh.outerVertex : ""+mesh.poolVH2D[0]; newl = n.onLeft(kdTree, start, end); oldl = a.onLeft(kdTree, start, end); boolean toSwap = (n != mesh.outerVertex) && (a.onLeft(kdTree, n, d) > 0L) && (a.onLeft(kdTree, o, n) > 0L) && !hasAttributes(BOUNDARY); if (newl > 0L) { // o stands to the right of (start,end), d and n to the left. if (!toSwap) prevOrigin(); // = (ond) else if (oldl >= 0L) { // a stands to the left of (start,end). swap(mesh); // = (ona) } else if (mesh.rand.nextBoolean()) swap(mesh); // = (ona) else prevOrigin(); // = (ond) } else if (newl < 0L) { // o and n stand to the right of (start,end), d to the left. if (!toSwap) nextDest(); // = (ndo) else if (oldl <= 0L) { // a stands to the right of (start,end). swap(mesh); // = (ona) next(); // = (nao) prevOrigin(); // = (nda) } else if (mesh.rand.nextBoolean()) { swap(mesh); // = (ona) next(); // = (nao) prevOrigin(); // = (nda) } else nextDest(); // = (ndo) } else { // n is the end point. if (!toSwap) nextDest(); // = (ndo) else { swap(mesh); // = (ona) next(); // = (nao) if (oldl < 0L) prevOrigin();// = (nda) } break; } } if (origin() != end) { // A midpoint is aligned with start and end, this should // never happen. throw new InvalidFaceException("Point "+origin()+" is aligned with "+start+" and "+end); } return count; } /** * Checks whether an edge is Delaunay. * * @param apex2 apex of the symmetric edge * @return <code>true</code> if edge is Delaunay, <code>false</code> * otherwise. */ public final boolean isDelaunay(Mesh2D mesh, Vertex2D apex2) { if (apex2.isPseudoIsotropic(mesh)) return isDelaunay_isotropic(mesh, apex2); return isDelaunay_anisotropic(mesh, apex2); } private boolean isDelaunay_isotropic(Mesh2D mesh, Vertex2D apex2) { assert mesh.outerVertex != origin(); assert mesh.outerVertex != destination(); assert mesh.outerVertex != apex(); Vertex2D vA = (Vertex2D) origin(); Vertex2D vB = (Vertex2D) destination(); Vertex2D v1 = (Vertex2D) apex(); KdTree kdTree = mesh.getKdTree(); long tp1 = vA.onLeft(kdTree, vB, v1); long tp2 = vB.onLeft(kdTree, vA, apex2); long tp3 = apex2.onLeft(kdTree, vB, v1); long tp4 = v1.onLeft(kdTree, vA, apex2); if (Math.abs(tp3) + Math.abs(tp4) < Math.abs(tp1)+Math.abs(tp2) ) return true; if (tp1 > 0L && tp2 > 0L) { if (tp3 <= 0L || tp4 <= 0L) return true; } return !apex2.inCircle2D(mesh, this); } private boolean isDelaunay_anisotropic(Mesh2D mesh, Vertex2D apex2) { assert mesh.outerVertex != origin(); assert mesh.outerVertex != destination(); assert mesh.outerVertex != apex(); if (apex2 == mesh.outerVertex) return true; return !apex2.inCircle(mesh, this); } }