/* jCAE stand for Java Computer Aided Engineering. Features are : Small CAD modeler, Finite element mesher, Plugin architecture. Copyright (C) 2006, by EADS CRC Copyright (C) 2007,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.ds; import org.jcae.mesh.amibe.traits.HalfEdgeTraitsBuilder; import java.util.Iterator; import org.jcae.mesh.amibe.metrics.Location; import org.jcae.mesh.amibe.metrics.Matrix3D; import org.jcae.mesh.amibe.projection.MeshLiaison; /** * Abstract class to define common methods on edges. * We use an half-edge data structure to perform mesh traversal. An half-edge * (red arrows) contains a link (black arrows) to its symmetric half-edge, a * link to the next half-edge in the same triangle, a link to its underlying * triangle and a local number within this triangle. A triangle contains an * array of three vertices; by convention, in each triangle, edge <em>i</em> is * located at the opposite of vertex <em>i</em>. Thus even if half-edges have * no links to vertices, they can be easily found; suppose that half-edge * <code>e</code> has local number <code>l</code> in triangle <code>tri</code>: * <ul> * <li><code>e.origin()</code> is <code>tri.vertex[(l+1)%3]</code></li> * <li><code>e.destination()</code> is <code>tri.vertex[(l-1)%3]</code></li> * <li><code>e.apex()</code> is <code>tri.vertex[l]</code></li> * </ul> * <p align="center"><img src="doc-files/Mesh-2.png" alt="[Image of a simple mesh with triangles and half-edges]"/></p> * * <p> * For an half-edge <code>e</code>, <code>e.sym()</code> returns its symmetric * half-edge, if it does exist. In this case, <code>e.sym().sym()</code> is * always <code>e</code>, and in images below a link between symmetric half-edges * is represented by a single double-headed arrow instead of two arrows. * Moreover, symmetric edges must have opposite directions (except for * <a href="#non-manifold">non-manifold</a> edges), or in other words * </p> * <ul> * <li><code>e.origin() == e.sym().destination()</code></li> * <li><code>e.destination() == e.sym().origin()</code></li> * </ul> * <p> * Image below shows two triangles which cannot be linked together because their common * edge has the same direction in both triangles. * </p> * <p align="center"><img src="doc-files/AbstractHalfEdge-11.png" alt="[Image showing invalid symmetric edges]"/></p> * * <h2>Geometrical primitives</h2> * * <p> * Consider <code>AbstractHalfEdge</code> edge <code>e</code> between vertices * <em>A</em> and <em>B</em>, starting from <em>A</em>, in image below: * </p> * <p align="center"><img src="doc-files/AbstractHalfEdge-1.png" alt="[Drawing to illustrate geometrical primitives]"/></p> * <p> * The following methods can be applied to <code>e</code>: * </p> * <ul> * <li><code>e.{@link #next}</code> and <code>e.{@link #prev}</code> get respectively next and previous * edges in the same triangle <em>(ABC)</em> in a counterclockwise cycle.</li> * <li><code>e.{@link #sym}</code> gets the opposite <code>AbstractHalfEdge</code>, in triangle <em>(BAF)</em>.</li> * <li><code>e.{@link #nextOrigin}</code> returns next edge starting from the same origin <em>A</em> * when cycling counterclockwise around <em>A</em>.</li> * </ul> * <p><strong>Warning:</strong> * As {@link VirtualHalfEdge} instances are handles to edges and not physical objects, these methods * modify current instance. Another set of methods is defined to apply these transformations to * another instance, so that current instance is not modified. * </p> * <ul> * <li><code>e.<a href="#next(org.jcae.mesh.amibe.ds.AbstractHalfEdge)">next</a>(f)</code> (resp. * <code>e.<a href="#prev(org.jcae.mesh.amibe.ds.AbstractHalfEdge)">prev</a>(f)</code>) * moves <code>f</code> to next (resp. previous) edge in the same triangle <em>(ABC)</em> * in a counterclockwise cycle.</li> * <li><code>e.<a href="#sym(org.jcae.mesh.amibe.ds.AbstractHalfEdge)">sym</a>(f)</code> * moves <code>f</code> to opposite of <code>e</code>.</li> * <li><code>e.<a href="#nextOrigin(org.jcae.mesh.amibe.ds.AbstractHalfEdge)">nextOrigin</a>(f)</code> * moves <code>f</code> to next edge starting from the same origin <em>A</em> when * cycling counterclockwise around <em>A</em>.</li> * </ul> * * <p> * For convenience, derived classes may also define the following methods, which * are combinations of previous ones: * </p> * <ul> * <li><code>e.prevOrigin()</code> moves counterclockwise to the previous edge * starting from the same origin.</li> * <li><code>e.nextDest()</code> (resp. <code>e.prevDest()</code>) moves counterclockwise * to next (resp. previous) edge with the same destination vertex <em>B</em>.</li> * <li><code>e.nextApex()</code> (resp. <code>e.prevApex()</code>) moves counterclockwise * to next (resp. previous) edge with the same apical vertex <em>C</em>.</li> * </ul> * * <h2>Mesh Operations</h2> * <p> * These operations are abstract methods and are implemented by derived classes: * </p> * <dl> * <dt><a href="#swap()"><code>swap</code></a></dt> * <dd>Swaps an edge. Return value has the same original and apical vertices as * original edge. Triangles and edges are modified, objects are not destroyed and * inserted into mesh. * <p align="center"><img src="doc-files/AbstractHalfEdge-4.png" alt="[Image showing edge swap]"/></p> * </dd> * <dt><a href="#split(org.jcae.mesh.amibe.ds.Mesh, org.jcae.mesh.amibe.ds.Vertex)"><code>split</code></a></dt> * <dd>Splits a vertex to create a new edge. In figure below, <em>A</em> is duplicated into <em>N</em>, * and two new triangles are created. Return value has the same original and apical vertices as * original edge, and its destination vertex is <em>N</em>. * <p><strong>Warning:</strong> This method does not check that new triangles are not inverted.</p> * <p align="center"><img src="doc-files/AbstractHalfEdge-3.png" alt="[Image showing vertex split]"/></p> * </dd> * <dt><a href="#collapse(org.jcae.mesh.amibe.ds.Mesh, org.jcae.mesh.amibe.ds.Vertex)"><code>collapse</code></a></dt> * <dd>Collapses an edge into a new point. Triangles, edges and vertices are removed from mesh * and replaced by new objects. New point may be origin or destination points, or a new point. * Return value has the new point as its origin, and its apex is the same as in original edge. * When <em>N</em> is <em>A</em>, * <code><a href="#collapse(org.jcae.mesh.amibe.ds.Mesh, org.jcae.mesh.amibe.ds.Vertex)">collapse</a></code> * is the opposite of * <code><a href="#split(org.jcae.mesh.amibe.ds.Mesh, org.jcae.mesh.amibe.ds.Vertex)">split</a></code>. * <p><strong>Warning:</strong> This method does not check that triangles are not inverted. * Method <a href="#canCollapse(org.jcae.mesh.amibe.ds.Vertex)"><code>canCollapse</code></a> * <strong>must</strong> have been called to ensure that this edge * collapse is possible, otherwise errors may occur. This method is not * called automatically because it is sometimes costful.</p> * <p align="center"><img src="doc-files/AbstractHalfEdge-2.png" alt="[Image showing edge collapse]"/></p> * </dd> * </dl> * <p> * Return values have been chosen so that these methods always return an edge * which has the same apical vertex. As will be explained below, they gracefully * work with boundaries and non-manifold meshes. * </p> * * <p> * These two methods may also be of interest when writing new algorithms: * </p> * <dl> * <dt><a href="#canCollapse(org.jcae.mesh.amibe.ds.Vertex)"><code>canCollapse</code></a></dt> * <dd>Tells whether an edge can be collapsed and replaced by the given vertex.</dd> * <dt><a href="#checkNewRingNormals(double[])"><code>checkNewRingNormals</code></a></dt> * <dd>Tells whether triangles become inverted if origin point is moved at * given location.</dd> * </dl> * * <h2>Boundaries</h2> * * <p> * In order to simplify mesh operations, outer triangles are added to opposite * side of free edges, by linking vertices on boundary to a special vertex * {@link Mesh#outerVertex} which represents a point at infinite. With this * convention, all mesh edges have a symmetric edge. On the other hand, all edges * which have {@link Mesh#outerVertex} as an endpoint have no symmetric edges, so * in these special triangles, there is only one edge with a symmetric edge. * </p> * <p align="center"><img src="doc-files/AbstractHalfEdge-5.png" alt="[Drawing to illustrate outer triangles]"/></p> * <p> * All <code>AbstractHalfEdge</code> derived classes allow to set attributes on edges. * They are defined as bitwise OR values, these values are useful: * </p> * <dl> * <dt><code>AbstractHalfEdge.BOUNDARY</code></dt> * <dd>This edge is on a mesh boundary (either part of an outer triangle or of * a regular triangle) * <dt><code>AbstractHalfEdge.OUTER</code></dt> * <dd>This edge is part of an outer triangle * <dt><code>AbstractHalfEdge.NONMANIFOLD</code></dt> * <dd>This edge is not manifold (see below) * </dl> * <p> * Other values, namely <code>AbstractHalfEdge.MARKED</code>, <code>AbstractHalfEdge.SWAPPED</code> * and <code>AbstractHalfEdge.QUAD</code> may be removed in future releases, please do not use them. * </p> * * <p>For instance, image below is an excerpt of previous image into which edge attributes * are displayed: <code>O</code> represents <code>AbstractHalfEdge.OUTER</code> * attribute, <code>B</code> represents <code>AbstractHalfEdge.BOUNDARY</code> and <code>|</code> * means that attributes are combined. * </p> * <p align="center"><img src="doc-files/AbstractHalfEdge-6.png" alt="[Drawing to illustrate edge attributes]"/></p> * * <p> * Attributes are handled by these methods: * </p> * <dl> * <dt>{@link #setAttributes}</dt> * <dd>Combines edge attribute with method argument.</dd> * <dt>{@link #clearAttributes}</dt> * <dd>Combines edge attribute with 2-complement of method argument, which means that * attributes passed as argument are reset.</dd> * <dt>{@link #hasAttributes}</dt> * <dd>Checks whether edge attribute contains any of attributes passed as method argument.</dd> * </dl> * * <p> * With these outer triangles, we can also loop around vertices even on mesh boundaries. * The {@link #nextOriginLoop} method behaves like {@link #nextOrigin} when edge is not * outer, and when it is outer, it turns clockwise until boundary is reached. For instance * on image below, <code>a.nextOriginLoop()</code> returns <code>b</code>, * <code>b.nextOriginLoop()</code> returns <code>o</code>, * <code>o.nextOriginLoop()</code> returns <code>d</code> and * <code>d.nextOriginLoop()</code> returns <code>a</code>. * </p> * <p align="center"><img src="doc-files/AbstractHalfEdge-7.png" alt="[Drawing to illustrate nextOriginLoop method]"/></p> * <p> * With <code>nextOriginLoop</code>, it is easy to loop around a vertex without * having to care about boundaries; a canonical way to process all inner triangles * by looping around edge origin is: * </p> * <pre> * Vertex d = e.destination(); * do * { * if (!e.hasAttributes(AbstractHalfEdge.OUTER)) * { * Triangle t = e.getTri(); * // Do something with t * } * e = e.nextOriginLoop(); * } * while (e.destination() != d); * </pre> * <p> * Here is a more complete example to show how to process all neighbors of a manifold vertex: * </p> * <pre> * // This sample code only works with manifold vertices! * Triangle t = (Triangle) o.getLink(); * AbstractHalfEdge e = t.getAbstractHalfEdge(); * if (e.destination() == o) * e = e.next(); * else if (e.apex() == o) * e = e.prev(); * // Now e is an edge starting from vertex o. * Vertex d = e.destination(); * do * { * if (!e.hasAttributes(AbstractHalfEdge.OUTER) * || e.hasAttributes(AbstractHalfEdge.BOUNDARY|AbstractHalfEdge.NONMANIFOLD)) * { * Vertex v = e.destination(); * // Do something with v * } * e = e.nextOriginLoop(); * } * while (e.destination() != d); * </pre> * * <h2 id="non-manifold">Non-manifold edges</h2> * <p> * A non-manifold edge is bound to more than two triangles. In image below, edges <code>(AB)</code>, * <code>(BC)</code> and <code>(CD)</code> belong to four triangles. * </p> * <p align="center"><img src="doc-files/AbstractHalfEdge-8.png" alt="[Image of a non-manifold mesh]"/></p> * * <p> * As with free edges, virtual triangles are added so that non-manifold edge has only one symmetric edge, * and this time it is tagged with <code>NONMANIFOLD</code> attribute. * Edge attributes are printed in blue for left shell: * </p> * <p align="center"><img src="doc-files/AbstractHalfEdge-9.png" alt="[Image showing edge attributes for a non-manifold mesh]"/></p> * * <p> * We explained that the two outer edges are not connected to other triangles, * so we have two free slots. We use these two slots to build a circular * doubly-linked list to iterate over all half-edges connected together, as is * done with radial-edge data structure. Here is an example, with an edge connected to three triangles; virtual triangles * are represented by dashed lines. * </p> * <p align="center"><img src="doc-files/AbstractHalfEdge-13.png" alt="[Non-manifold edge connected to three triangles]"/></p> * <p align="center"><img src="doc-files/AbstractHalfEdge-14.png" alt="[Virtual triangles and connections for a non-manifold edge connected to three triangles]"/></p> * <p> * All edges have only one symmetric edge. But adjacency relations between virtual triangles (represented by dotted arrows) * are special, these half-edges are the only ones which do not necessarily satisfy these identities: * </p> * <ul> * <li><code>e.origin() == e.sym().destination()</code></li> * <li><code>e.destination() == e.sym().origin()</code></li> * </ul> * * <p> * The <a href="#fanIterator()"><code>fanIterator</code></a> method returns an iterator over all edges connected * to this one through such virtual triangles. By convention, this iterator returns half-edges which are not outer. * In order to ease writing algorithms, this iterator is also defined on regular half-edges, and return only one * value which is this half-edge. * </p> * * <p> * How can we iterate over all neighbors of <code>A</code>? If we look again at sample code above, * it is obvious that not all neighbors are caught. We could try to ask non-manifold edges to the rescue, * it does not seem trivial. A simpler solution is to store a list of all triangles connected to each vertex, * but it consumes lots of memory. We call <em>triangle fan</em> the set of triangles which are visited * when calling {@link #nextOriginLoop} iteratively. If vertex is manifold, all neighbors are then reached. * If not, we take another triangle which has not been visited yet, and repeat the same procedure until * all neighbors have been reached. A good compromise is to take a single triangle by triangle fans, and they * are stored into a triangle array because topology is usually not modified and number of triangle fans does * not change. It is easy to modify sample code above, put it into a method taking a triangle as argument, * call it with <code>o.getLink()</code> if it is a <code>Triangle</code> instance, otherwise loop over * <code>Triangle[]</code> and call this method for each <code>Triangle</code> instance. * </p> * * <p> * Consider the same mesh as above, from which triangles <code>(ABE)</code>, <code>(CDF)</code> and their symmetric * ones have been removed. Edges <code>(AB)</code> and <code>(CD)</code> are manifold, but <code>(BC)</code> is not. * Vertices <code>A</code> and <code>D</code> are manifold, but <code>B</code> and <code>C</code> and connected to * three triangle fans, while edge <code>(BC)</code> is connected to four triangles. All mesh operations described * above can gracefully handle such geometries, the only problem is when we try to mesh a surface which is not * orientable. * </p> * <p align="center"><img src="doc-files/AbstractHalfEdge-10.png" alt="[Image showing a non-manifold edge bound to four triangles while its endpoints are bound to three triangle fans]"/></p> * * <p> * Non-manifold edges can also be used to handle ill-oriented meshes. Two triangles which have opposite * orientations, as already shown in this image * </p> * <p align="center"><img src="doc-files/AbstractHalfEdge-11.png" alt="[Image showing invalid symmetric edges]"/></p> * <p> * can be linked together if we state that common edge is non-manifold, add two virtual triangles and link * these triangles as shown below: * </p> * <p align="center"><img src="doc-files/AbstractHalfEdge-12.png" alt="[Image showing how to use non-manifold edges to link ill-oriented triangles]"/></p> */ public abstract class AbstractHalfEdge { /** Helper class to compute the quality of a HaldEdge */ public static class Quality { private double[] normal1 = new double[3]; private double[] normal2 = new double[3]; private double[] swappedNormal1 = new double[3]; private double[] swappedNormal2 = new double[3]; private double[] temp1 = new double[3]; private double[] temp2 = new double[3]; private double area1, area2, swappedArea1, swappedArea2; private Vertex o, d, apex, symApex; private Vertex middle, projectedMiddle; private boolean normalComputed, swappedNormalComputed; private MeshLiaison liaison; public void setEdge(AbstractHalfEdge edge) { o = edge.origin(); d = edge.destination(); apex = edge.apex(); symApex = edge.sym().apex(); normalComputed = false; swappedNormalComputed = false; } private void computeNormal() { if(!normalComputed) { area1 = Matrix3D.computeNormal3D(o, d, apex, temp1, temp2, normal1); area2 = Matrix3D.computeNormal3D(d, o, symApex, temp1, temp2, normal2); normalComputed = true; } } private void computeSwappedNormal() { if(!swappedNormalComputed) { swappedArea1 = Matrix3D.computeNormal3D(o, symApex, apex, temp1, temp2, swappedNormal1); swappedArea2 = Matrix3D.computeNormal3D(d, apex, symApex, temp1, temp2, swappedNormal2); swappedNormalComputed = true; } } public double getQuality() { computeNormal(); double p1 = o.distance3D(d) + d.distance3D(apex) + apex.distance3D(o); double p2 = d.distance3D(o) + o.distance3D(symApex) + symApex.distance3D(d); return Math.min(area1/p1/p1, area2/p2/p2); } /** * cosinus of the angle between the 2 normals of the adjacent triangle */ public double getAngle() { computeNormal(); return Matrix3D.prodSca(normal1, normal2); } public double getSwappedQuality() { computeSwappedNormal(); double p3 = o.distance3D(symApex) + symApex.distance3D(apex) + apex.distance3D(o); double p4 = d.distance3D(apex) + apex.distance3D(symApex) + symApex.distance3D(d); return Math.min(swappedArea1/p3/p3, swappedArea2/p4/p4); } /** * cosinus of the angle between the 2 normals of the adjacent triangle * of the edge if it is swapped */ public double getSwappedAngle() { computeSwappedNormal(); return Matrix3D.prodSca(swappedNormal1, swappedNormal2); } public double swappedVolume() { return Math.abs(tetraVolume(o, d, apex, symApex)); } /** Signed tetrahedron volume */ public static double tetraVolume(Location o, Location d, Location apex, Location symApex) { double p1x = d.getX() - o.getX(); double p1y = d.getY() - o.getY(); double p1z = d.getZ() - o.getZ(); double p2x = apex.getX() - o.getX(); double p2y = apex.getY() - o.getY(); double p2z = apex.getZ() - o.getZ(); double p3x = symApex.getX() - o.getX(); double p3y = symApex.getY() - o.getY(); double p3z = symApex.getZ() - o.getZ(); double det = (p1x * p2y * p3z + p1y * p2z * p3x + p2x * p3y * p1z)- (p1z * p2y * p3x + p1y * p2x * p3z + p1x * p2z * p3y); return det / 6.0; } /** Set the liaison ton compute the swapped deflection */ public void setLiaison(MeshLiaison liaison) { this.liaison = liaison; if(liaison != null) { middle = liaison.getMesh().createVertex(0,0,0); projectedMiddle = liaison.getMesh().createVertex(0,0,0); } } /** * distance between the middle of the swapped edge and the background mesh * @param group the group of the background triangle if you knows it, -1 else * @return the square of the deflection or 0 if the liaison was not set */ public double sqrSwappedDeflection(int group) { if(liaison == null) return 0; middle.middle(apex, symApex); liaison.move(projectedMiddle, middle, group, true); return projectedMiddle.sqrDistance3D(middle); } } /** * User-defined traits. There are currently no traits for half-edges. */ //protected final Traits traits; /** * Numeric constants for edge attributes. Set if edge is on * boundary. * @see #setAttributes * @see #hasAttributes * @see #clearAttributes */ public static final int BOUNDARY = 1 << 0; /** * Numeric constants for edge attributes. Set if edge is outer. * (Ie. one of its end point is {@link Mesh#outerVertex}) * @see #setAttributes * @see #hasAttributes * @see #clearAttributes */ public static final int OUTER = 1 << 1; /** * Numeric constants for edge attributes. Set if edge had been * swapped. * @see #setAttributes * @see #hasAttributes * @see #clearAttributes */ public static final int SWAPPED = 1 << 2; /** * Numeric constants for edge attributes. Set if edge had been * marked (for any operation). * @see #setAttributes * @see #hasAttributes * @see #clearAttributes */ public static final int MARKED = 1 << 3; /** * Numeric constants for edge attributes. Set if edge is the inner * edge of a quadrangle. * @see #setAttributes * @see #hasAttributes * @see #clearAttributes */ //public static final int QUAD = 1 << 4; /** * Numeric constants for edge attributes. Set if edge is non * manifold. * @see #setAttributes * @see #hasAttributes * @see #clearAttributes */ public static final int NONMANIFOLD = 1 << 5; /** * Numeric constants for edge attributes. Set if edge is sharp. * @see #setAttributes * @see #hasAttributes * @see #clearAttributes */ public static final int SHARP = 1 << 6; /** * Numeric constants for edge attributes. Set if edge is immutable. * @see #setAttributes * @see #hasAttributes * @see #clearAttributes */ public static final int IMMUTABLE = 1 << 7; /** * <code>Integer</code> array to store values for 0, 1 and 2. These objects * may be useful when edge local numbers are put into <code>HashSet</code> or * <code>HashMap</code> structures. */ static final Integer [] int3 = new Integer[3]; static { int3[0] = Integer.valueOf(0); int3[1] = Integer.valueOf(1); int3[2] = Integer.valueOf(2); } /** * Constructor. Creates a new instance, and creates traits by * @param builder half-edge traits builder */ AbstractHalfEdge(HalfEdgeTraitsBuilder builder) { /*if (builder != null) traits = builder.createTraits(); else traits = null;*/ } /** * Moves to symmetric edge. * @return current instance after its transformation */ public abstract AbstractHalfEdge sym(); /** * Moves to symmetric edge. * Make <code>that</code> instance be a copy of current * instance, move it to its symmetric edge and return * this instance. Current instance is not modified. * * @param that instance where transformed edge is stored * @return argument after its transformation */ public abstract AbstractHalfEdge sym(AbstractHalfEdge that); /** * Moves counterclockwise to following edge. * @return current instance after its transformation */ public abstract AbstractHalfEdge next(); /** * Moves counterclockwise to following edge. * Make <code>that</code> instance be a copy of current * instance, move it counterclockwise to next edge and * return this instance. Current instance is not modified. * * @param that instance where transformed edge is stored * @return argument after its transformation */ public abstract AbstractHalfEdge next(AbstractHalfEdge that); /** * Moves counterclockwise to previous edge. * @return current instance after its transformation */ public abstract AbstractHalfEdge prev(); /** * Moves counterclockwise to previous edge. * Make <code>that</code> instance be a copy of current * instance, move it counterclockwise to previous edge and * return this instance. Current instance is not modified. * * @param that instance where transformed edge is stored * @return argument after its transformation */ public abstract AbstractHalfEdge prev(AbstractHalfEdge that); /** * Moves counterclockwise to the following edge which has the same origin. * @return current instance after its transformation */ public abstract AbstractHalfEdge nextOrigin(); /** * Moves counterclockwise to the following edge which has the same origin. * Make <code>that</code> instance be a copy of current * instance, move it counterclockwise to the following edge which * has the same origin and return this instance. Current instance is * not modified. * * @param that instance where transformed edge is stored * @return argument after its transformation */ public abstract AbstractHalfEdge nextOrigin(AbstractHalfEdge that); /** * Moves counterclockwise to the following edge which has the same origin. * If a boundary is reached, loop backward until another * boundary is found and start again from there. * @return current instance after its transformation */ public abstract AbstractHalfEdge nextOriginLoop(); /** * Returns triangle tied to this edge. * * @return triangle tied to this edge */ public abstract Triangle getTri(); /** * Returns edge local number. * * @return edge local number */ public abstract int getLocalNumber(); /** * Tells whether edge is connected to a symmetric edge. * * @return <code>true</code> if edge has a symmetric edge, <code>false</code> otherwise. */ public abstract boolean hasSymmetricEdge(); /** * Returns start vertex of this edge. * * @return start vertex of this edge */ public abstract Vertex origin(); /** * Returns end vertex of this edge. * * @return end vertex of this edge */ public abstract Vertex destination(); /** * Returns apex of this edge. * * @return apex of this edge */ public abstract Vertex apex(); /** * Sets attributes of this edge. * * @param attr attributes of this edge */ public abstract void setAttributes(int attr); /** * Resets attributes of this edge. * * @param attr attributes of this edge to clear out */ public abstract void clearAttributes(int attr); /** * Checks if some attributes of this edge are set. * * @param attr attributes to check * @return <code>true</code> if this AbstractHalfEdge has * one of these attributes set, <code>false</code> * otherwise */ public abstract boolean hasAttributes(int attr); /** * Swaps an edge. * * @return swapped edge, origin and apical vertices are the same as in original edge * @throws IllegalArgumentException if edge is on a boundary or belongs * to an outer triangle. * @see Mesh#edgeSwap */ abstract AbstractHalfEdge swap(Mesh m); /** * Checks that triangles are not inverted if origin vertex is moved. * * @param newpt the new position to be checked * @return <code>false</code> if the new position produces * an inverted triangle, <code>true</code> otherwise. */ abstract boolean checkNewRingNormals(Mesh m, Location newpt); abstract boolean canMoveOrigin(Mesh m, Location newpt); /** * Checks whether an edge can be contracted into a given vertex. * * @param v the resulting vertex * @return <code>true</code> if this edge can be contracted into the single vertex n, <code>false</code> otherwise * @see Mesh#canCollapseEdge */ abstract boolean canCollapse(Mesh m, Location v); /** * Contracts an edge. * * @param m mesh * @param v the resulting vertex * @return edge starting from <code>n</code> and with the same apex * @throws IllegalArgumentException if edge belongs to an outer triangle, * because there would be no valid return value. User must then run this * method against symmetric edge, this is not done automatically. * @see Mesh#edgeCollapse */ abstract AbstractHalfEdge collapse(Mesh m, Vertex v); /** * Splits an edge. This is the opposite of {@link #collapse}. * * @param m mesh * @param v the resulting vertex * @return edge starting from <code>n</code> and pointing to original apex * @see Mesh#vertexSplit */ abstract AbstractHalfEdge split(Mesh m, Vertex v); /** * Sets the edge tied to this object. * * @param e the edge tied to this object */ public abstract void glue(AbstractHalfEdge e); /** * Returns the area of triangle bound to this edge. * * @return triangle area */ public abstract double area(Mesh m); /** * Returns an iterator over triangle fans connected to this edge. If edge is * manifold, this iterator contains a single value, which is this edge. * But if it is non-manifold and bound to <em>n</em> triangles, this iterator * returns successively the <em>n</em> edges contained in these triangles and * connected to the same endpoints. * * @return iterator over triangle fans connected to this edge */ public abstract Iterator<AbstractHalfEdge> fanIterator(); public Triangle createBoundaryTriangle(Mesh mesh) { if (!hasSymmetricEdge() && !hasAttributes(AbstractHalfEdge.OUTER)) { setAttributes(AbstractHalfEdge.BOUNDARY); Triangle adj = mesh.createTriangle(mesh.outerVertex, destination(), origin()); adj.setAttributes(AbstractHalfEdge.OUTER); adj.setReadable(false); adj.setWritable(false); AbstractHalfEdge sym = adj.getAbstractHalfEdge(); sym.setAttributes(AbstractHalfEdge.BOUNDARY); glue(sym); return adj; } return null; } }