/* jCAE stand for Java Computer Aided Engineering. Features are : Small CAD
modeler, Finite element mesher, Plugin architecture.
Copyright (C) 2004,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.ds;
import java.util.Collection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.NoSuchElementException;
import org.jcae.mesh.amibe.metrics.Matrix3D;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jcae.mesh.amibe.metrics.Location;
/**
* A handle to abstract half-edge instances. When triangles are instances of
* {@link TriangleVH} class, adjacency relations are contained within
* triangles. This <code>VirtualHalfEdge</code> class is a handle to an edge,
* it has a link to a triangle and contains a local number. When a
* <code>VirtualHalfEdge</code> instance is traversing the mesh, its reference
* is not modified, but its instance variables are updated. In order to
* prevent object allocations, we try to reuse <code>VirtualHalfEdge</code>
* objects as much as we can.
*/
public class VirtualHalfEdge extends AbstractHalfEdge
{
private static final Logger logger=Logger.getLogger(VirtualHalfEdge.class.getName());
private static final int [] next3 = { 1, 2, 0 };
private static final int [] prev3 = { 2, 0, 1 };
private final double [] tempD = new double[3];
private final double [] tempD1 = new double[3];
private final double [] tempD2 = new double[3];
/*
* Vertices can be accessed through
* origin = tri.vertex[next3[localNumber]]
* destination = tri.vertex[prev3[localNumber]]
* apex = tri.vertex[localNumber]
* Adjacent triangle is tri.adj[localNumber].tri and its localNumber
* is ((tri.adjPos[0] >> (2*localNumber)) & 3)
*/
/**
* Triangle connected to this edge.
*/
protected TriangleVH tri;
/**
* Local number of this edge in this triangle.
*/
private int localNumber;
/**
* Attributes of this edge.
*/
protected int attributes;
// Section: constructors
/**
* Sole constructor.
*/
public VirtualHalfEdge()
{
super(null);
}
/**
* Creates an object to handle data about a triangle.
*
* @param t geometrical triangle
* @param o a number between 0 and 2 determining an edge
*/
public VirtualHalfEdge(Triangle t, int o)
{
super(null);
tri = (TriangleVH) t;
localNumber = o;
pullAttributes();
}
// Section: accessors
/**
* Returns triangle tied to this edge.
*
* @return triangle tied to this edge
*/
@Override
public final Triangle getTri()
{
return tri;
}
/**
* Returns edge local number.
*
* @return edge local number
*/
@Override
public final int getLocalNumber()
{
return localNumber;
}
/**
* Sets triangle tied to this object, and resets localNumber.
*
* @param t triangle tied to this object
*/
public final void bind(TriangleVH t)
{
tri = t;
localNumber = 0;
pullAttributes();
}
/**
* Sets the triangle tied to this object, and the localNumber.
*
* @param t triangle tied to this object
* @param l local number
*/
public final void bind(TriangleVH t, int l)
{
tri = t;
localNumber = l;
pullAttributes();
}
// Section: attributes handling
/**
* Checks if some attributes of this edge are set.
*
* @param attr attributes to check
* @return <code>true</code> if this VirtualHalfEdge has one of
* these attributes set, <code>false</code> otherwise
*/
@Override
public final boolean hasAttributes(int attr)
{
return (attributes & attr) != 0;
}
/**
* Sets attributes of this edge.
*
* @param attr attributes of this edge
*/
@Override
public final void setAttributes(int attr)
{
attributes |= attr;
pushAttributes();
}
/**
* Resets attributes of this edge.
*
* @param attr attributes of this edge to clear out
*/
@Override
public final void clearAttributes(int attr)
{
attributes &= ~attr;
pushAttributes();
}
// Adjust tri.adjPos after attributes is modified.
protected final void pushAttributes()
{
tri.setEdgeAttributes(localNumber, attributes);
}
// Adjust attributes after tri.adjPos is modified.
private void pullAttributes()
{
attributes = tri.getEdgeAttributes(localNumber);
}
/**
* Checks whether an edge can be modified.
*
* @return <code>false</code> if edge is a boundary or outside the mesh,
* <code>true</code> otherwise.
*/
public final boolean isMutable()
{
return !hasAttributes(BOUNDARY | NONMANIFOLD | SHARP | OUTER);
}
// Section: geometrical primitives
// These geometrical primitives have 3 signatures:
// fct() transforms current object.
// fct(that) copies current instance into 'that' and transforms it
// fct(this, that) applies fct to 'this' and stores result
// in an already allocated object 'that'.
// This is definitely not an OO approach, but it is much more
// efficient by preventing useless memory allocations.
// They do not return any value to make clear that calling
// these routines requires extra care.
/**
* Moves to symmetric edge.
* @return current instance after its transformation
*/
@Override
public final VirtualHalfEdge sym()
{
int neworient = tri.getAdjLocalNumber(localNumber);
tri = tri.getAdj(localNumber);
localNumber = neworient;
pullAttributes();
return this;
}
/**
* 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
*/
@Override
public final VirtualHalfEdge sym(AbstractHalfEdge that)
{
VirtualHalfEdge dest = (VirtualHalfEdge) that;
dest.tri = tri.getAdj(localNumber);
dest.localNumber = tri.getAdjLocalNumber(localNumber);
dest.pullAttributes();
return dest;
}
/**
* Moves counterclockwise to following edge.
* @return current instance after its transformation
*/
@Override
public final VirtualHalfEdge next()
{
localNumber = next3[localNumber];
pullAttributes();
return this;
}
/**
* 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
*/
@Override
public final VirtualHalfEdge next(AbstractHalfEdge that)
{
VirtualHalfEdge dest = (VirtualHalfEdge) that;
dest.tri = tri;
dest.localNumber = next3[localNumber];
dest.pullAttributes();
return dest;
}
/**
* Moves counterclockwise to previous edge.
* @return current instance after its transformation
*/
@Override
public final VirtualHalfEdge prev()
{
localNumber = prev3[localNumber];
pullAttributes();
return this;
}
/**
* 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
*/
@Override
public final VirtualHalfEdge prev(AbstractHalfEdge that)
{
VirtualHalfEdge dest = (VirtualHalfEdge) that;
dest.tri = tri;
dest.localNumber = prev3[localNumber];
dest.pullAttributes();
return dest;
}
/**
* Moves counterclockwise to the following edge which has the same origin.
* @return current instance after its transformation
*/
@Override
public final VirtualHalfEdge nextOrigin()
{
return prev().sym();
}
/**
* 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
*/
@Override
public final VirtualHalfEdge nextOrigin(AbstractHalfEdge that)
{
return prev(that).sym();
}
/**
* 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.
* Note: outer triangles are taken into account in this loop, because
* this is sometimes needed, as in VirtualHalfEdge2D.removeDegenerated().
* They have to be explicitly filtered out by testing hasAttributes(OUTER).
*/
@Override
public final VirtualHalfEdge nextOriginLoop()
{
if (hasAttributes(OUTER) && hasAttributes(BOUNDARY | NONMANIFOLD))
{
// Loop clockwise to another boundary
// and start again from there.
do
{
sym();
next();
}
while (!hasAttributes(OUTER));
}
else
nextOrigin();
return this;
}
// Static methods for VirtualHalfEdge instances, only the most useful methods are defined
/**
* Copies a <code>VirtualHalfEdge</code> instance into another <code>VirtualHalfEdge</code>
* instance.
*
* @param src <code>VirtualHalfEdge</code> being duplicated
* @param dest already allocated <code>VirtualHalfEdge</code> where data are
* copied
*/
protected static void copyOTri(VirtualHalfEdge src, VirtualHalfEdge dest)
{
dest.tri = src.tri;
dest.localNumber = src.localNumber;
dest.attributes = src.attributes;
}
/**
* Copies a <code>VirtualHalfEdge</code> instance and move to its symmetric edge.
*
* @param o source <code>VirtualHalfEdge</code>
* @param that already allocated <code>VirtualHalfEdge</code> where data are
* copied
*/
protected static void symOTri(VirtualHalfEdge o, VirtualHalfEdge that)
{
that.tri = o.tri.getAdj(o.localNumber);
that.localNumber = o.tri.getAdjLocalNumber(o.localNumber);
that.pullAttributes();
}
/**
* Copies a <code>VirtualHalfEdge</code> instance and move it counterclockwise to
* following edge.
*
* @param o source <code>VirtualHalfEdge</code>
* @param that already allocated <code>VirtualHalfEdge</code> where data are
* copied
*/
protected static void nextOTri(VirtualHalfEdge o, VirtualHalfEdge that)
{
that.tri = o.tri;
that.localNumber = next3[o.localNumber];
that.pullAttributes();
}
/**
* Copies a <code>VirtualHalfEdge</code> instance and move it counterclockwise to
* previous edge.
*
* @param o source <code>VirtualHalfEdge</code>
* @param that already allocated <code>VirtualHalfEdge</code> where data are
* copied
*/
protected static void prevOTri(VirtualHalfEdge o, VirtualHalfEdge that)
{
that.tri = o.tri;
that.localNumber = prev3[o.localNumber];
that.pullAttributes();
}
// Section: vertex handling
/**
* Returns start vertex of this edge.
*
* @return start vertex of this edge
*/
@Override
public final Vertex origin()
{
return tri.getV(next3[localNumber]);
}
/**
* Returns end vertex of this edge.
*
* @return end vertex of this edge
*/
@Override
public final Vertex destination()
{
return tri.getV(prev3[localNumber]);
}
/**
* Returns apex of this edge.
*
* @return apex of this edge
*/
@Override
public final Vertex apex()
{
return tri.getV(localNumber);
}
// The following 3 methods change the underlying triangle.
// So they also modify all VirtualHalfEdge bound to this one.
/**
* Sets start vertex of this edge.
*
* @param v start vertex of this edge
*/
final void setOrigin(Vertex v)
{
tri.setV(next3[localNumber], v);
}
/**
* Sets end vertex of this edge.
*
* @param v end vertex of this edge
*/
public final void setDestination(Vertex v)
{
tri.setV(prev3[localNumber], v);
}
/**
* Sets apex of this edge.
*
* @param v apex of this edge
*/
protected final void setApex(Vertex v)
{
tri.setV(localNumber, v);
}
// Section: adjacency
/**
* Sets adjacency relations between two triangles.
*
* @param sym the edge tied to this object
*/
@Override
public final void glue(AbstractHalfEdge sym)
{
VHglue((VirtualHalfEdge) sym);
}
private void VHglue(VirtualHalfEdge sym)
{
tri.setAdj(localNumber, sym.tri);
tri.setAdjLocalNumber(localNumber, sym.localNumber);
if (sym.tri != null)
{
sym.tri.setAdj(sym.localNumber, tri);
sym.tri.setAdjLocalNumber(sym.localNumber, localNumber);
}
}
/**
* Sets adjacency relation for an edge
*
* @param link the triangle bond to this one if this edge is manifold, or an Object otherwise
*/
private void setAdj(TriangleVH link)
{
tri.setAdj(localNumber, link);
}
/**
* Tells whether edge is connected to a symmetric edge.
*
* @return <code>true</code> if edge has a symmetric edge, <code>false</code> otherwise.
*/
@Override
public final boolean hasSymmetricEdge()
{
return tri.getAdj(localNumber) != null;
}
// Section: 3D geometrical routines
/**
* Computes the normal of an edge, in the triangle plane.
* This vector is not normalized, it has the same length as
* this edge. The result is stored in the tempD temporary array.
* @see #getTempVector
* @return triangle area
* Warning: this method uses tempD, tempD1 and tempD2 temporary arrays.
*/
private double computeNormal3DT()
{
destination().sub(origin(), tempD1);
apex().sub(origin(), tempD);
Matrix3D.prodVect3D(tempD1, tempD, tempD2);
double norm = Matrix3D.norm(tempD2);
if (norm != 0.0)
{
tempD2[0] /= norm;
tempD2[1] /= norm;
tempD2[2] /= norm;
}
Matrix3D.prodVect3D(tempD1, tempD2, tempD);
return 0.5*norm;
}
/**
* Computes the normal of this triangle. The result is stored in
* the tempD temporary array.
* @see #getTempVector
* @return triangle area
* Warning: this method uses tempD, tempD1 and tempD2 temporary arrays.
*/
public final double computeNormal3D()
{
destination().sub(origin(), tempD1);
apex().sub(origin(), tempD2);
Matrix3D.prodVect3D(tempD1, tempD2, tempD);
double norm = Matrix3D.norm(tempD);
if (norm != 0.0)
{
tempD[0] /= norm;
tempD[1] /= norm;
tempD[2] /= norm;
}
return 0.5*norm;
}
/**
* Returns the area of triangle bound to this edge.
*
* @return triangle area
* Warning: this method uses tempD, tempD1 and tempD2 temporary arrays.
*/
@Override
public final double area(Mesh m)
{
destination().sub(origin(), tempD1);
apex().sub(origin(), tempD2);
Matrix3D.prodVect3D(tempD1, tempD2, tempD);
return 0.5 * Matrix3D.norm(tempD);
}
/**
* Returns the temporary array TempD.
*/
public final double [] getTempVector()
{
return tempD;
}
// Section: algorithms
/**
* Checks the dihedral angle of an edge.
*
* @param minCos if the dot product of the normals to adjacent
* triangles is lower than monCos, then <code>-1.0</code> is
* returned.
* @return the minimum quality of the two trianglles generated
* by swapping this edge.
*/
@SuppressWarnings("unused")
private double checkSwap3D(Mesh m, double minCos)
{
return checkSwap3D(m, minCos, 0.0);
}
private double checkSwap3D(Mesh m, double minCos, double maxLength)
{
double invalid = -1.0;
// Do not swap sharp edges
if (hasAttributes(SHARP))
return invalid;
// Check if there is an adjacent edge
if (hasAttributes(OUTER | BOUNDARY | NONMANIFOLD))
return invalid;
// Check for coplanarity
symOTri(this, m.tempVH[0]);
computeNormal3D();
double [] n1 = getTempVector();
m.tempVH[0].computeNormal3D();
double [] n2 = m.tempVH[0].getTempVector();
if (Matrix3D.prodSca(n1, n2) < minCos)
return invalid;
// Check for quality improvement
Vertex o = origin();
Vertex d = destination();
Vertex a = apex();
Vertex n = m.tempVH[0].apex();
if (maxLength > 0.0 && a.sqrDistance3D(n) > maxLength)
return invalid;
// Do not create an edge which will be difficult to modify later
if (a.getRef() != 0 && n.getRef() != 0 && (o.getRef() == 0 || d.getRef() == 0))
return invalid;
// Check for inverted triangles
o.outer3D(n, a, tempD1, tempD2, n2);
double s3 = 0.5 * Matrix3D.prodSca(n1, n2);
if (s3 <= 0.0)
return invalid;
d.outer3D(a, n, tempD1, tempD2, n2);
double s4 = 0.5 * Matrix3D.prodSca(n1, n2);
if (s4 <= 0.0)
return invalid;
double p1 = o.distance3D(d) + d.distance3D(a) + a.distance3D(o);
double s1 = area(m);
double p2 = d.distance3D(o) + o.distance3D(n) + n.distance3D(d);
double s2 = m.tempVH[0].area(m);
// No need to multiply by 12.0 * Math.sqrt(3.0)
double Qbefore = Math.min(s1/p1/p1, s2/p2/p2);
double p3 = o.distance3D(n) + n.distance3D(a) + a.distance3D(o);
double p4 = d.distance3D(a) + a.distance3D(n) + n.distance3D(d);
double Qafter = Math.min(s3/p3/p3, s4/p4/p4);
if (Qafter > Qbefore)
return Qafter;
// If both configurations are almost identical, prefer the one
// which does not link two vertices on inner boundaries
if (Qafter > 2.0*Qbefore && (a.getRef() == 0 || n.getRef() == 0) && o.getRef() != 0 && d.getRef() != 0)
return Qafter;
return invalid;
}
/**
* 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
*/
@Override
protected final VirtualHalfEdge swap(Mesh m)
{
VHswap(m);
return this;
}
private void VHswap(Mesh m)
{
if (hasAttributes(SHARP | OUTER | BOUNDARY | NONMANIFOLD))
throw new IllegalArgumentException("Cannot swap "+this);
Vertex o = origin();
Vertex d = destination();
Vertex a = apex();
/*
* d d
* . .
* /|\ / \
* a1 / | \ a4 a1 / \ a4
* / | \ / \
* a + | + n ---> a +-------+ n
* \ | / \ /
* a2 \ | / a3 a2 \ / a3
* \|/ \ /
* ' '
* o o
*/
// T1 = (oda) --> (ona)
// T2 = (don) --> (dan)
copyOTri(this, m.tempVH[0]); // (oda)
symOTri(this, m.tempVH[1]); // (don)
symOTri(this, m.tempVH[2]); // (don)
Vertex n = m.tempVH[1].apex();
// Clear SWAPPED flag for all edges of the 2 triangles
for (int i = 0; i < 3; i++)
{
m.tempVH[0].clearAttributes(SWAPPED);
m.tempVH[1].clearAttributes(SWAPPED);
m.tempVH[0].next();
m.tempVH[1].next();
}
m.tempVH[1].next(); // (ond)
int attr3 = m.tempVH[1].attributes;
m.tempVH[1].sym(); // a3 = (no*)
m.tempVH[1].VHglue(m.tempVH[0]);
m.tempVH[0].attributes = attr3;
m.tempVH[0].pushAttributes();
m.tempVH[0].next(); // (dao)
copyOTri(m.tempVH[0], m.tempVH[1]); // (dao)
int attr1 = m.tempVH[1].attributes;
m.tempVH[0].sym(); // a1 = (ad*)
m.tempVH[2].VHglue(m.tempVH[0]);
m.tempVH[2].attributes = attr1;
m.tempVH[2].pushAttributes();
m.tempVH[2].next(); // (ond)
m.tempVH[2].VHglue(m.tempVH[1]);
// Mark new edge
m.tempVH[1].attributes = 0;
m.tempVH[2].attributes = 0;
m.tempVH[1].setAttributes(SWAPPED);
m.tempVH[2].setAttributes(SWAPPED);
// Adjust vertices
m.tempVH[2].setOrigin(a); // (and)
m.tempVH[1].setOrigin(n); // (nao)
// Fix links to triangles
replaceVertexLinks(o, tri, m.tempVH[2].tri, tri);
replaceVertexLinks(d, tri, m.tempVH[2].tri, m.tempVH[2].tri);
pullAttributes();
}
/**
* 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.
* Warning: this method uses m.tempVH[0] and m.tempVH[1] temporary arrays.
*/
@Override
final boolean checkNewRingNormals(Mesh m, Location newpt)
{
Vertex o = origin();
if (o.isManifold())
return checkNewRingNormalsSameFan(m, newpt, null, null);
for (Triangle start: (Triangle []) o.getLink())
{
m.tempVH[1].bind((TriangleVH) start);
if (m.tempVH[1].destination() == o)
m.tempVH[1].next();
else if (m.tempVH[1].apex() == o)
m.tempVH[1].prev();
assert m.tempVH[1].origin() == o;
if (!m.tempVH[1].checkNewRingNormalsSameFan(m, newpt, null, null))
return false;
}
return true;
}
final boolean canMoveOrigin(Mesh m, Location newpt)
{
return checkNewRingNormals(m, newpt);
}
/*
* Warning: this method uses m.tempVH[0] temporary array.
*/
private boolean checkNewRingNormalsSameFan(Mesh m, Location newpt, TriangleVH t1, TriangleVH t2)
{
Vertex d = destination();
copyOTri(this, m.tempVH[0]);
do
{
if (m.tempVH[0].tri != t1 && m.tempVH[0].tri != t2 && !m.tempVH[0].hasAttributes(OUTER))
{
Vertex x1 = m.tempVH[0].destination();
m.tempVH[0].next();
double area = m.tempVH[0].computeNormal3DT();
double [] nu = m.tempVH[0].getTempVector();
m.tempVH[0].prev();
newpt.sub(x1, tempD1);
// Two triangles are removed when an edge is contracted.
// So normally triangle areas should increase. If they
// decrease significantly, there may be a problem.
if (Matrix3D.prodSca(tempD1, nu) >= - area)
return false;
}
m.tempVH[0].nextOriginLoop();
}
while (m.tempVH[0].destination() != d);
return true;
}
/**
* Checks whether an edge can be contracted.
*
* @param n 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
* Warning: this method uses m.tempVH[0], m.tempVH[1] and m.tempVH[2] temporary arrays.
*/
@Override
final boolean canCollapse(Mesh m, Location n)
{
// Be consistent with collapse()
if (hasAttributes(OUTER))
return false;
if (logger.isLoggable(Level.FINE))
logger.fine("can contract? ("+origin()+" "+destination()+") into "+n);
if (origin().isManifold() && destination().isManifold())
{
// Mesh is locally manifold. This is the most common
// case, do not create an HashSet to store only two
// triangles.
TriangleVH t1 = tri;
symOTri(this, m.tempVH[1]);
TriangleVH t2 = m.tempVH[1].tri;
// Check that origin vertex can be moved
if (!checkNewRingNormalsSameFan(m, n, t1, t2))
return false;
// Check that destination vertex can be moved
if (!m.tempVH[1].checkNewRingNormalsSameFan(m, n, t1, t2))
return false;
// Topology check.
return canCollapseTopology(m);
}
// At least one vertex is non manifold. Store all triangles
// which will be removed in an HashSet so that they are
// ignored when checking for degenerated triangles.
Collection<TriangleVH> ignored = new HashSet<TriangleVH>();
for (Iterator<AbstractHalfEdge> it = fanIterator(); it.hasNext(); )
{
VirtualHalfEdge f = (VirtualHalfEdge) it.next();
ignored.add(f.tri);
symOTri(f, m.tempVH[1]);
ignored.add(m.tempVH[1].tri);
}
// Check that origin vertex can be moved
if (!checkNewRingNormalsNonManifoldVertex(m, n, ignored))
return false;
// Check that destination vertex can be moved
symOTri(this, m.tempVH[2]);
if (!m.tempVH[2].checkNewRingNormalsNonManifoldVertex(m, n, ignored))
return false;
ignored.clear();
// Topology check.
// See in AbstractHalfEdgeTest.buildMeshTopo() why this
// check is needed.
// When edge is non manifold, we do not use Vertex.getNeighbourIteratorVertex()
// because checks have to be performed by fans.
for (Iterator<AbstractHalfEdge> it = fanIterator(); it.hasNext(); )
{
VirtualHalfEdge f = (VirtualHalfEdge) it.next();
if (!f.canCollapseTopology(m))
return false;
}
return true;
}
/*
* Warning: this method uses m.tempVH[0] and m.tempVH[1] temporary arrays.
*/
private boolean checkNewRingNormalsNonManifoldVertex(Mesh m, Location newpt, Collection<TriangleVH> ignored)
{
Vertex o = origin();
if (o.isManifold())
return checkNewRingNormalsSameFanNonManifoldVertex(m, newpt, ignored);
for (Triangle start: (Triangle []) o.getLink())
{
m.tempVH[1].bind((TriangleVH) start);
if (m.tempVH[1].destination() == o)
m.tempVH[1].next();
else if (m.tempVH[1].apex() == o)
m.tempVH[1].prev();
assert m.tempVH[1].origin() == o;
if (!m.tempVH[1].checkNewRingNormalsSameFanNonManifoldVertex(m, newpt, ignored))
return false;
}
return true;
}
/*
* Warning: this method uses m.tempVH[0] temporary array.
*/
private boolean checkNewRingNormalsSameFanNonManifoldVertex(Mesh m, Location newpt, Collection<TriangleVH> ignored)
{
// Loop around origin. We need to copy current instance
// into m.tempVH[0] because loop may be interrupted.
copyOTri(this, m.tempVH[0]);
Vertex d = destination();
do
{
if (!ignored.contains(m.tempVH[0].tri) && !m.tempVH[0].hasAttributes(OUTER))
{
Vertex x1 = m.tempVH[0].destination();
m.tempVH[0].next();
double area = m.tempVH[0].computeNormal3DT();
double [] nu = m.tempVH[0].getTempVector();
m.tempVH[0].prev();
newpt.sub(x1, tempD1);
// Two triangles are removed when an edge is contracted.
// So normally triangle areas should increase. If they
// decrease significantly, there may be a problem.
if (Matrix3D.prodSca(tempD1, nu) >= - area)
return false;
}
m.tempVH[0].nextOriginLoop();
}
while (m.tempVH[0].destination() != d);
return true;
}
/**
* Topology check.
* See in AbstractHalfEdgeTest.buildMeshTopo() why this
* check is needed.
* Warning: this method uses m.tempVH[0] temporary array.
*/
private boolean canCollapseTopology(Mesh m)
{
Collection<Vertex> neighbours = new HashSet<Vertex>();
// We need to copy current instance into m.tempVH[0]
// because second loop may be interrupted.
copyOTri(this, m.tempVH[0]);
Vertex d = m.tempVH[0].destination();
do
{
// Warning: mesh.outerVertex is intentionnally not filtered out
neighbours.add(m.tempVH[0].destination());
m.tempVH[0].nextOriginLoop();
}
while (m.tempVH[0].destination() != d);
m.tempVH[0].sym();
int cnt = 0;
d = m.tempVH[0].destination();
do
{
// Warning: mesh.outerVertex is intentionnally not filtered out
if (neighbours.contains(m.tempVH[0].destination()))
{
if (cnt > 1)
return false;
cnt++;
}
m.tempVH[0].nextOriginLoop();
}
while (m.tempVH[0].destination() != d);
return true;
}
/**
* 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
*/
@Override
final VirtualHalfEdge collapse(Mesh m, Vertex v)
{
if (hasAttributes(OUTER))
throw new IllegalArgumentException("Cannot contract "+this);
Vertex o = origin();
Vertex d = destination();
assert o.isWritable() && d.isWritable(): "Cannot contract "+this;
if (logger.isLoggable(Level.FINE))
logger.fine("contract ("+o+" "+d+")");
// Replace o by n in all incident triangles
if (o.isManifold())
replaceEndpointsSameFan(v);
else
replaceEndpointsNonManifold(m, o, v);
// Replace d by n in all incident triangles
symOTri(this, m.tempVH[2]);
if (d.isManifold())
m.tempVH[2].replaceEndpointsSameFan(v);
else
replaceEndpointsNonManifold(m, d, v);
// Set v links
deepCopyVertexLinks(m, o, d, v);
if (logger.isLoggable(Level.FINE))
logger.fine("new point: "+v);
if (m.hasNodes())
{
m.remove(o);
m.remove(d);
m.add(v);
}
if (!hasAttributes(NONMANIFOLD))
{
m.tempVH[2].VHcollapseSameFan(m, v);
return VHcollapseSameFan(m, v);
}
// Edge is non-manifold
assert m.tempVH[2].hasAttributes(OUTER);
// VHcollapseSameFan may modify internal data structure
// used by fanIterator(), we need a copy.
Map<TriangleVH, Integer> copy = new LinkedHashMap<TriangleVH, Integer>();
for (Iterator<AbstractHalfEdge> it = fanIterator(); it.hasNext(); )
{
VirtualHalfEdge h = (VirtualHalfEdge) it.next();
copy.put(h.tri, int3[h.localNumber]);
}
TriangleVH ret = null;
int num = -1;
for (Map.Entry<TriangleVH, Integer> entry: copy.entrySet())
{
TriangleVH t = entry.getKey();
int l = entry.getValue().intValue();
m.tempVH[2].bind(t, l);
assert !m.tempVH[2].hasAttributes(OUTER);
m.tempVH[2].sym();
assert m.tempVH[2].hasAttributes(OUTER);
m.tempVH[2].VHcollapseSameFan(m, v);
m.tempVH[2].bind(t, l);
m.tempVH[2].VHcollapseSameFan(m, v);
if (t == tri)
{
ret = m.tempVH[0].tri;
num = m.tempVH[0].localNumber;
}
}
assert ret != null;
bind(ret, num);
return this;
}
/*
* Warning: this method uses m.tempVH[0] and m.tempVH[1] temporary arrays.
*/
private VirtualHalfEdge VHcollapseSameFan(Mesh m, Vertex n)
{
/*
* V1 V1
* V3+-------+-------+ V4 V3 +------+------+ V4
* \ t3 / \ t4 / \ t3 | t4 /
* \ / \ / ------> \ | /
* \ / t1 \ / \ | /
* o +-------+ d n +
*/
// this = (odV1)
if (hasAttributes(NONMANIFOLD) && hasAttributes(OUTER))
{
// All we have to do here is to remove t1
m.remove(tri);
return null;
}
// Update adjacency links. For clarity, o and d are
// written instead of n.
next(); // (dV1o)
int attr4 = attributes;
VirtualHalfEdge vh4 = (hasSymmetricEdge() ? m.tempVH[0] : null);
if (vh4 != null)
symOTri(this, vh4); // (V1dV4)
next(); // (V1od)
int attr3 = attributes;
VirtualHalfEdge vh3 = (hasSymmetricEdge() ? m.tempVH[1] : null);
if (vh3 != null)
symOTri(this, vh3); // (oV1V3)
if (!hasAttributes(OUTER))
{
TriangleVH t34 = m.tempVH[1].tri;
if (t34.hasAttributes(OUTER))
t34 = m.tempVH[0].tri;
assert !t34.hasAttributes(OUTER) : m.tempVH[0]+"\n"+m.tempVH[1];
// Update links of V1 and n
replaceVertexLinks(origin(), tri, t34);
replaceVertexLinks(n, tri, t34);
}
if (vh3 != null && vh3.hasAttributes(NONMANIFOLD))
vh3.VHglue(vh4);
else if (vh4 != null && vh4.hasAttributes(NONMANIFOLD))
vh4.VHglue(vh3);
else if (vh3 != null)
vh3.VHglue(vh4);
else if (vh4 != null)
vh4.VHglue(vh3);
if (vh3 != null)
{
vh3.attributes |= attr4;
vh3.pushAttributes();
}
if (vh4 != null)
{
vh4.attributes |= attr3;
vh4.pushAttributes();
}
next(); // (odV1)
// Remove t1
m.remove(tri);
// By convention, edge is moved into (dV4V1), but this may change.
// If vh4 is null, edge is outer and return value does not matter
return (vh4 == null ? null : vh4.next());
}
private void replaceEndpointsSameFan(Vertex n)
{
Vertex d = destination();
do
{
setOrigin(n);
nextOriginLoop();
}
while (destination() != d);
}
/*
* Warning: this method uses m.tempVH[0] temporary array.
*/
private static void replaceEndpointsNonManifold(Mesh m, Vertex o, Vertex n)
{
Triangle [] oList = (Triangle []) o.getLink();
for (Triangle t: oList)
{
m.tempVH[0].bind((TriangleVH) t);
if (m.tempVH[0].destination() == o)
m.tempVH[0].next();
else if (m.tempVH[0].apex() == o)
m.tempVH[0].prev();
assert m.tempVH[0].origin() == o : ""+o+" not in "+m.tempVH[0];
m.tempVH[0].replaceEndpointsSameFan(n);
}
}
private static void replaceVertexLinks(Vertex o, TriangleVH oldT1, TriangleVH oldT2, TriangleVH newT)
{
if (o.isManifold())
o.setLink(newT);
else
{
Triangle [] tArray = (Triangle []) o.getLink();
for (int i = 0; i < tArray.length; i++)
{
if (tArray[i] == oldT1 || tArray[i] == oldT2)
{
logger.fine("replaceVertexLinks: "+tArray[i]+" --> "+newT);
tArray[i] = newT;
}
}
}
}
private static void replaceVertexLinks(Vertex o, TriangleVH oldT, TriangleVH newT)
{
if (o.isManifold())
o.setLink(newT);
else
{
Triangle [] tArray = (Triangle []) o.getLink();
for (int i = 0; i < tArray.length; i++)
{
if (tArray[i] == oldT)
{
logger.fine("replaceVertexLinks: "+i+" "+o+" "+tArray[i]);
tArray[i] = newT;
logger.fine(" --> "+newT);
}
}
}
}
/*
* Warning: this method uses m.tempVH[0] and m.tempVH[1] temporary arrays.
*/
private static void deepCopyVertexLinks(Mesh m, Vertex o, Vertex d, Vertex v)
{
boolean ot = o.isManifold();
boolean dt = d.isManifold();
// Prepare vertex links first
if (ot && dt)
{
v.setLink(d.getLink());
}
else if (ot)
{
Triangle [] dList = (Triangle []) d.getLink();
Triangle [] nList = new Triangle[dList.length];
System.arraycopy(dList, 0, nList, 0, dList.length);
v.setLink(nList);
}
else if (dt)
{
Triangle [] oList = (Triangle []) o.getLink();
Triangle [] nList = new Triangle [oList.length];
System.arraycopy(oList, 0, nList, 0, oList.length);
v.setLink(nList);
}
else
{
// Vertex.setLinkFan() cannot be called here because fans from
// o and d have to be merged.
Triangle [] oList = (Triangle []) o.getLink();
Triangle [] dList = (Triangle []) d.getLink();
Triangle [] nList = new Triangle[oList.length+dList.length];
System.arraycopy(oList, 0, nList, 0, oList.length);
System.arraycopy(dList, 0, nList, oList.length, dList.length);
ArrayList<Triangle> res = new ArrayList<Triangle>();
Set<Triangle> allTriangles = new HashSet<Triangle>();
// o and d have already been replaced by v
for (Triangle t: nList)
{
if (!allTriangles.contains(t))
res.add(t);
allTriangles.add(t);
m.tempVH[0].bind((TriangleVH) t);
if (m.tempVH[0].origin() != v)
m.tempVH[0].next();
if (m.tempVH[0].origin() != v)
m.tempVH[0].next();
if (m.tempVH[0].origin() == v)
{
// Add all triangles of the same fan to allTriangles
boolean found = false;
Vertex end = m.tempVH[0].destination();
do
{
m.tempVH[0].nextOriginLoop();
allTriangles.add(m.tempVH[0].tri);
if (m.tempVH[0].destination() == v)
{
found = true;
copyOTri(m.tempVH[0], m.tempVH[1]);
}
}
while (m.tempVH[0].destination() != end);
if (found)
{
m.tempVH[1].next();
end = m.tempVH[1].destination();
do
{
m.tempVH[1].nextOriginLoop();
allTriangles.add(m.tempVH[1].tri);
}
while (m.tempVH[1].destination() != end);
}
}
boolean found = false;
if (m.tempVH[0].destination() == v)
{
found = true;
m.tempVH[0].next();
}
else if (m.tempVH[0].apex() == v)
{
found = true;
m.tempVH[0].prev();
}
if (found)
{
// Add all triangles of the same fan to allTriangles
found = false;
Vertex end = m.tempVH[0].destination();
do
{
m.tempVH[0].nextOriginLoop();
allTriangles.add(m.tempVH[0].tri);
if (m.tempVH[0].destination() == v)
{
found = true;
copyOTri(m.tempVH[0], m.tempVH[1]);
}
}
while (m.tempVH[0].destination() != end);
if (found)
{
m.tempVH[1].next();
end = m.tempVH[1].destination();
do
{
m.tempVH[1].nextOriginLoop();
allTriangles.add(m.tempVH[1].tri);
}
while (m.tempVH[1].destination() != end);
}
}
}
v.setLink(new Triangle[res.size()]);
res.toArray((Triangle[]) v.getLink());
}
}
/**
* Splits an edge. This is the opposite of collapse.
*
* @param m mesh
* @param v vertex being inserted
* @return edge starting from origin and pointing to <code>n</code>
* @see Mesh#vertexSplit
*/
@Override
final VirtualHalfEdge split(Mesh m, Vertex v)
{
if (logger.isLoggable(Level.FINE))
logger.fine("split edge ("+origin()+" "+destination()+") by adding vertex "+v);
if (m.hasNodes())
m.add(v);
if (v.getRef() == 0)
{
if (hasAttributes(BOUNDARY | NONMANIFOLD))
m.setRefVertexOnBoundary(v);
else if (hasAttributes(SHARP))
m.setRefVertexOnInnerBoundary(v);
}
if (!hasAttributes(NONMANIFOLD))
{
v.setLink(tri);
VHsplitSameFan(m, v);
if (m.tempVH[1].hasAttributes(OUTER))
{
// Remove links between t2 and t4
m.tempVH[1].next(); // (nV2d)
symOTri(m.tempVH[1], m.tempVH[3]); // (V2no)
m.tempVH[1].setAdj(null);
m.tempVH[3].setAdj(null);
}
return this;
}
// VHsplitSameFan may modify internal data structure
// used by fanIterator(), we need a copy.
Map<TriangleVH, Integer> copy = new LinkedHashMap<TriangleVH, Integer>();
// Set vertex links
int cnt = 0;
ArrayList<Triangle> link = new ArrayList<Triangle>();
for (Iterator<AbstractHalfEdge> it = fanIterator(); it.hasNext(); )
{
VirtualHalfEdge f = (VirtualHalfEdge) it.next();
link.add(f.tri);
copy.put(f.tri, int3[f.localNumber]);
cnt++;
}
v.setLink(new Triangle[cnt]);
link.toArray((Triangle[]) v.getLink());
link.clear();
// Rebuild circular linked lists.
// TODO: Avoid these allocations.
VirtualHalfEdge [] hOuter = new VirtualHalfEdge[2*cnt];
for (int i = 0; i < hOuter.length; i++)
hOuter[i] = new VirtualHalfEdge();
cnt = 0;
Vertex o = origin();
for (Map.Entry<TriangleVH, Integer> entry: copy.entrySet())
{
TriangleVH t = entry.getKey();
int l = entry.getValue().intValue();
m.tempVH[3].bind(t, l);
m.tempVH[3].VHsplitSameFan(m, v);
if (m.tempVH[3].origin() == o)
{
symOTri(m.tempVH[3], hOuter[2*cnt]);
copyOTri(m.tempVH[1], hOuter[2*cnt+1]);
}
else
{
copyOTri(m.tempVH[1], hOuter[2*cnt]);
symOTri(m.tempVH[3], hOuter[2*cnt+1]);
}
assert hOuter[2*cnt].origin() == o || hOuter[2*cnt].destination() == o;
cnt++;
}
for (int j = 0; j < 2; j++)
{
// Initializes an empty cycle
nextOTri(hOuter[j], m.tempVH[3]);
nextOTri(m.tempVH[3], m.tempVH[0]);
m.tempVH[3].VHglue(m.tempVH[0]);
for (int i = 1; i < cnt; i++)
{
// Store old sym into m.tempVH[0]
symOTri(m.tempVH[3], m.tempVH[0]);
// Adds hOuter[2*i+j] to current cycle
prevOTri(hOuter[2*i+j], m.tempVH[2]);
m.tempVH[3].VHglue(m.tempVH[2]);
m.tempVH[2].prev();
m.tempVH[2].VHglue(m.tempVH[0]);
}
}
return this;
}
/*
* Warning: this method uses m.tempVH[0], m.tempVH[1] and m.tempVH[2] temporary arrays.
*/
private void VHsplitSameFan(Mesh m, Vertex n)
{
if (hasAttributes(OUTER))
throw new IllegalArgumentException("Cannot split "+this);
/*
* V1 V1
* /'\ /|\
* / \ / | \
* / h1 \ / | \
* / \ / n1| h1 \
* / t1 \ / t1 | t3 \
* o +-------------------+ d ---> o +---------+---------+ d
* \ t2 / \ t4 | t2 /
* \ / \ |n2 /
* \h2 / \h2 | /
* \ / \ | /
* \,/ \|/
* V2 V2
*/
splitVertexAddOneTriangle(m, n);
symOTri(this, m.tempVH[0]);
m.tempVH[0].splitVertexAddOneTriangle(m, n);
// Now we must update links:
// Link together t1/t4 and t2/t3.
TriangleVH t1 = tri;
nextOTri(this, m.tempVH[0]); // (nV1o)
m.tempVH[0].sym(); // (V1nd)
m.tempVH[0].next(); // (ndV1)
TriangleVH t3 = m.tempVH[0].tri;
symOTri(this, m.tempVH[1]); // (dnV2)
m.tempVH[0].VHglue(m.tempVH[1]);
TriangleVH t2 = m.tempVH[1].tri;
m.tempVH[1].next(); // (nV2d)
symOTri(m.tempVH[1], m.tempVH[0]); // (V2no)
m.tempVH[0].next(); // (noV2)
VHglue(m.tempVH[0]);
TriangleVH t4 = m.tempVH[0].tri;
m.tempVH[1].prev(); // (dnV2)
TriangleVH t14 = (t1.hasAttributes(OUTER) ? t4 : t1);
TriangleVH t23 = (t2.hasAttributes(OUTER) ? t3 : t2);
// Update vertex links
replaceVertexLinks(n, t1, t2, t14);
replaceVertexLinks(m.tempVH[1].origin(), t1, t2, t23);
replaceVertexLinks(origin(), t1, t2, t14);
}
/*
* Warning: this method uses m.tempVH[1] and m.tempVH[2] temporary arrays.
*/
private void splitVertexAddOneTriangle(Mesh m, Vertex n)
{
/*
* V1 V1
* /'\ /|\
* / \ / | \
* / w1 \ / w1| w2 \
* / \ / | \
* / t1 \ / t1 | t3 \
* o +-------------------+ d ---> o +---------+---------+ d
*/
TriangleVH t1 = tri;
TriangleVH t3 = (TriangleVH) m.createTriangle(t1);
m.add(t3);
if (!hasAttributes(OUTER))
{
nextOTri(this, m.tempVH[2]); // (dV1o)
symOTri(m.tempVH[2], m.tempVH[1]); // (V1d*)
m.tempVH[2].bind(t3, m.tempVH[2].localNumber); // (dV1n)
m.tempVH[1].VHglue(m.tempVH[2]);
}
next(); // (nV1o)
m.tempVH[1].bind(t3, localNumber); // (dV1n)
// Update Triangle links
tri = t1;
m.tempVH[1].tri = t3;
// Update vertices
setOrigin(n);
m.tempVH[1].setApex(n);
// Inner edge
m.tempVH[1].next(); // (V1nd)
VHglue(m.tempVH[1]);
// Clear BOUNDARY and NONMANIFOLD flags on inner edges
m.tempVH[1].clearAttributes(BOUNDARY | NONMANIFOLD);
clearAttributes(BOUNDARY | NONMANIFOLD);
prev(); // (onV1)
}
private Iterator<AbstractHalfEdge> identityFanIterator()
{
final VirtualHalfEdge current = this;
logger.fine("Manifold fan iterator");
return new Iterator<AbstractHalfEdge>()
{
private boolean nextFan = true;
public boolean hasNext()
{
return nextFan;
}
public AbstractHalfEdge next()
{
if (!nextFan)
throw new NoSuchElementException();
nextFan = false;
return current;
}
public void remove()
{
}
};
}
/**
* 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
*/
@Override
public final Iterator<AbstractHalfEdge> fanIterator()
{
if (!hasAttributes(NONMANIFOLD))
return identityFanIterator();
logger.fine("Non manifold fan iterator");
return new Iterator<AbstractHalfEdge>()
{
private final TriangleVH last = tri.getAdj(localNumber);
private final int lastNumber = tri.getAdjLocalNumber(localNumber);
private final VirtualHalfEdge ret = new VirtualHalfEdge();
private final VirtualHalfEdge current = new VirtualHalfEdge();
public boolean hasNext()
{
return last != current.tri;
}
public AbstractHalfEdge next()
{
if (current.tri == null)
{
current.bind(last, lastNumber);
current.prev();
}
current.prev();
current.sym();
copyOTri(current, ret);
ret.next();
ret.sym();
return ret;
}
public void remove()
{
}
};
}
@Override
public final String toString()
{
StringBuilder r = new StringBuilder();
r.append("hashCode: ").append(hashCode());
r.append("\nTri hashcode: ").append(tri.hashCode());
r.append("\nGroup: ").append(tri.getGroupId());
r.append("\nLocal number: ").append(localNumber);
if (hasSymmetricEdge())
r.append("\nSym: ").append(tri.getAdj(localNumber).hashCode()).append("[").append(tri.getAdjLocalNumber(localNumber)).append("]");
r.append("\nAttributes: ").append(Integer.toHexString(tri.getEdgeAttributes(localNumber)));
r.append("\nVertices:");
r.append("\n Origin: ").append(origin());
r.append("\n Destination: ").append(destination());
r.append("\n Apex: ").append(apex());
return r.toString();
}
}