/* 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-2011, 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 org.jcae.mesh.amibe.metrics.Matrix3D;
import java.util.Collection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.NoSuchElementException;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jcae.mesh.amibe.metrics.Location;
/**
* Half-edge data structure. This is a straightforward implementation of
* {@link AbstractHalfEdge}, an half-edge is represented by a local number
* (between 0 and 2) and a triangle. It has a link to the next edge in the
* same triangle, and to its symmetric edge.
*/
public class HalfEdge extends AbstractHalfEdge implements Serializable
{
private static final long serialVersionUID = -2460993797089718106L;
private static final Logger logger=Logger.getLogger(HalfEdge.class.getName());
private TriangleHE tri;
private byte localNumber;
private byte attributes;
private HalfEdge sym;
private HalfEdge next;
private static final int [] next3 = { 1, 2, 0 };
private static final int [] prev3 = { 2, 0, 1 };
HalfEdge (HalfEdgeTraitsBuilder htb, TriangleHE tri, byte localNumber, byte attributes)
{
super(htb);
this.tri = tri;
this.localNumber = localNumber;
this.attributes = attributes;
}
/**
* 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;
}
public final int getAttributes()
{
return attributes;
}
/**
* Sets the edge tied to this object.
*
* @param e the edge tied to this object
*/
@Override
public final void glue(AbstractHalfEdge e)
{
HEglue((HalfEdge) e);
}
private void HEglue(HalfEdge s)
{
sym = s;
if (s != null)
s.sym = this;
}
/**
* 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 sym != null;
}
/**
* Moves to symmetric edge.
* @return current instance after its transformation
*/
@Override
public final HalfEdge sym()
{
return 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
*/
@Override
public final AbstractHalfEdge sym(AbstractHalfEdge that)
{
that = sym;
return that;
}
/**
* Moves counterclockwise to following edge.
* @return current instance after its transformation
*/
@Override
public final HalfEdge next()
{
return 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
*/
@Override
public final AbstractHalfEdge next(AbstractHalfEdge that)
{
that = next;
return that;
}
/**
* Moves counterclockwise to previous edge.
* @return current instance after its transformation
*/
@Override
public final HalfEdge prev()
{
return next.next;
}
/**
* 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 AbstractHalfEdge prev(AbstractHalfEdge that)
{
that = next.next;
return that;
}
/**
* Moves counterclockwise to the following edge which has the same origin.
* @return current instance after its transformation
*/
@Override
public final HalfEdge nextOrigin()
{
return next.next.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 AbstractHalfEdge nextOrigin(AbstractHalfEdge that)
{
that = next.next.sym;
return that;
}
/**
* Moves counterclockwise to the following edge which has the same apex.
* @return current instance after its transformation
*/
private HalfEdge nextApex()
{
return next.sym.next;
}
/**
* Moves counterclockwise to the previous edge which has the same apex.
* @return current instance after its transformation
*/
private HalfEdge prevApex()
{
return next.next.sym.prev();
}
// The following 3 methods change the underlying triangle.
// So they also modify all HalfEdge 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
*/
final void setDestination(Vertex v)
{
tri.setV(prev3[localNumber], v);
}
/**
* Sets apex of this edge.
*
* @param v apex of this edge
*/
final void setApex(Vertex v)
{
tri.setV(localNumber, v);
}
/**
* Sets next link.
*/
public final void setNext(HalfEdge e)
{
next = e;
}
/**
* Checks if some attributes of this edge are set.
*
* @param attr attributes to check
* @return <code>true</code> if this HalfEdge 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;
}
/**
* Resets attributes of this edge.
*
* @param attr attributes of this edge to clear out
*/
@Override
public final void clearAttributes(int attr)
{
attributes &= ~attr;
}
/**
* 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);
}
/**
* 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.
*/
@Override
public final HalfEdge nextOriginLoop()
{
HalfEdge ret = this;
if (ret.hasAttributes(OUTER) && ret.hasAttributes(BOUNDARY | NONMANIFOLD))
{
// Loop clockwise to another boundary
// and start again from there.
do
{
ret = ret.sym.next;
}
while (!ret.hasAttributes(OUTER));
}
else
ret = ret.nextOrigin();
return ret;
}
/**
* Moves counterclockwise to the following edge which has the same apex.
* If a boundary is reached, loop backward until another
* boundary is found and start again from there.
*/
public final HalfEdge nextApexLoop()
{
HalfEdge ret = this;
if (ret.hasAttributes(OUTER) && ret.next.next.hasAttributes(BOUNDARY | NONMANIFOLD))
{
// Loop clockwise to another boundary
// and start again from there.
do
{
ret = ret.prevApex();
}
while (!ret.hasAttributes(OUTER));
}
else
ret = ret.nextApex();
return ret;
}
public final double checkSwap3D(Mesh mesh, double minCos)
{
return checkSwap3D(mesh, minCos, 0.0, 0.0, true, -2.0, -2.0);
}
/**
* Checks the dihedral angle of an edge.
* Warning: this method uses temp[0], temp[1], temp[2] and temp[3] temporary arrays.
*
* @param minCos if the dot product of the normals to adjacent
* triangles is lower than minCos, then <code>-1.0</code> is
* returned.
* @param minQualityFactor If the quality of the generated triangle is not
* at least multiplied by this factor, the returned value is -1
* @param expectInsert Set it to true if later point insertion are expected.
* This is typically the case before and during ReMesh.
* @param minCosAfter if the dot product of the normals to adjacent
* triangles after the swap, is lower than minCos, then <code>-1.0</code>
* is returned.
* @param minCosForceSwap if the dot product of the normals to adjacent
* is lower than minCosForceSwap return 1.0 if swaping would increase the
* angle cosinus, even if quality is not improved.
* @return the minimum quality of the two triangles generated
* by swapping this edge or -1 if the swap must not be done
*/
//TODO: Code factorization with AbstractHalfEdge.Quality
public final double checkSwap3D(Mesh mesh, double minCos, double maxLength,
double minQualityFactor, boolean expectInsert, double minCosAfter,
double minCosForceSwap)
{
double invalid = -1.0;
if (hasAttributes(IMMUTABLE))
return invalid;
// 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
Vertex o = origin();
Vertex d = destination();
Vertex a = apex();
Vertex n = sym.apex();
if (maxLength > 0.0 && a.sqrDistance3D(n) > maxLength)
return invalid;
// Do not create an edge which will be difficult to modify later
if (expectInsert && a.getRef() != 0 && n.getRef() != 0 && (o.getRef() == 0 || d.getRef() == 0))
return invalid;
double[] temp0 = mesh.temp.t3_0;
double[] temp1 = mesh.temp.t3_1;
double[] temp2 = mesh.temp.t3_2;
double[] temp3 = mesh.temp.t3_3;
double[] temp4 = mesh.temp.t3_4;
//normals of current side triangles
double s1 = Matrix3D.computeNormal3D(o, d, a, temp0, temp1, temp2);
double s2 = Matrix3D.computeNormal3D(d, o, n, temp0, temp1, temp3);
double cos = Matrix3D.prodSca(temp2, temp3);
if (cos < minCos)
return invalid;
// Normals between swaped side triangles
double s3 = Matrix3D.computeNormal3D(o, n, a, temp0, temp1, temp2);
double s4 = Matrix3D.computeNormal3D(d, a, n, temp0, temp1, temp3);
if(minCosForceSwap >= -1.0 || minCosAfter >= -1.0)
{
double cosAfter = Matrix3D.prodSca(temp2, temp3);
if(cos < minCosForceSwap && cosAfter > cos)
return 1.0;
if(cosAfter < minCosAfter)
return invalid;
}
// Make sure that edge swap does not create inverted triangles
if (minCos > -1.0)
{
for (int i = 0; i < 4; i++)
{
HalfEdge h = (i == 0 ? next.next : (i == 1 ? sym.next : (i == 2 ? next : sym.next.next)));
if (h.hasAttributes(SHARP | OUTER | BOUNDARY | NONMANIFOLD))
continue;
Matrix3D.computeNormal3D(h.destination(), h.origin(), h.sym().apex(), temp0, temp1, temp4);
if (i < 2)
{
if (Matrix3D.prodSca(temp2, temp4) < minCos)
return invalid;
}
else
{
if (Matrix3D.prodSca(temp3, temp4) < minCos)
return invalid;
}
}
}
double p1 = o.distance3D(d) + d.distance3D(a) + a.distance3D(o);
double p2 = d.distance3D(o) + o.distance3D(n) + n.distance3D(d);
// 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 && Qafter > minQualityFactor*Qbefore)
return Qafter;
// If both configurations are almost identical, prefer the one
// which does not link two vertices on inner boundaries
if (expectInsert && Qafter > 0.5*Qbefore &&
(a.getRef() == 0 || n.getRef() == 0) &&
o.getRef() != 0 && d.getRef() != 0)
return Qafter;
return invalid;
}
public final double checkSwapNormal(Mesh mesh, double coplanarity, double[] normal)
{
return checkSwapNormal(mesh, coplanarity, normal, true);
}
public final double checkSwapNormal(Mesh mesh, double coplanarity, double[] normal, boolean expectInsert)
{
double invalid = -2.0;
if (hasAttributes(IMMUTABLE))
return invalid;
// 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
Vertex o = origin();
Vertex d = destination();
Vertex a = apex();
Vertex n = sym.apex();
// Do not create an edge which will be difficult to modify later
if (expectInsert && a.getRef() != 0 && n.getRef() != 0 && (o.getRef() == 0 || d.getRef() == 0))
return invalid;
double[] temp0 = mesh.temp.t3_0;
double[] temp1 = mesh.temp.t3_1;
double[] temp2 = mesh.temp.t3_2;
double[] temp3 = mesh.temp.t3_3;
double s1 = Matrix3D.computeNormal3D(o, d, a, temp0, temp1, temp2);
double s2 = Matrix3D.computeNormal3D(d, o, n, temp0, temp1, temp3);
double cBefore1 = Matrix3D.prodSca(temp2, normal);
double cBefore2 = Matrix3D.prodSca(temp3, normal);
if (cBefore2 < cBefore1)
cBefore1 = cBefore2;
// Make sure that edge swap does not create inverted triangles
double s3 = Matrix3D.computeNormal3D(o, n, a, temp0, temp1, temp2);
double s4 = Matrix3D.computeNormal3D(d, a, n, temp0, temp1, temp3);
double cAfter1 = Matrix3D.prodSca(temp2, normal);
double cAfter2 = Matrix3D.prodSca(temp3, normal);
if (cAfter2 < cAfter1)
cAfter1 = cAfter2;
if (cAfter1 < coplanarity)
return invalid;
if (cBefore1 < 0.0 && cAfter1 > 0.0)
return - invalid;
return (cAfter1 - cBefore1);
}
/**
* 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
final HalfEdge swap(Mesh mesh)
{
return HEswap(mesh);
}
private HalfEdge HEswap(Mesh mesh)
{
if (hasAttributes(SHARP | OUTER | BOUNDARY | NONMANIFOLD))
throw new IllegalArgumentException("Cannot swap "+this);
Vertex o = origin();
Vertex d = destination();
Vertex a = apex();
if (logger.isLoggable(Level.FINE))
logger.fine("swap edge ("+o+" "+d+")");
/*
* d d
* . .
* /|\ / \
* s0 / | \ s3 s0 / \ s3
* / | \ / T2 \
* a + T1|T2 + n ---> a +-------+ n
* \ | / \ T1 /
* s1 \ | / s2 s1 \ / s2
* \|/ \ /
* ' '
* o o
*/
// T1 = (oda) --> (ona)
// T2 = (don) --> (dan)
HalfEdge [] e = new HalfEdge[6];
e[0] = next;
e[1] = next.next;
e[2] = sym.next;
e[3] = sym.next.next;
e[4] = this;
e[5] = sym;
// Clear SWAPPED flag for all edges of the 2 triangles
for (int i = 0; i < 6; i++)
{
e[i].clearAttributes(SWAPPED);
e[i].sym.clearAttributes(SWAPPED);
}
// Adjust vertices
Vertex n = e[5].apex();
e[4].setDestination(n); // (ona)
e[5].setDestination(a); // (dan)
// T1: e[1] is unchanged
TriangleHE T1 = e[1].tri;
e[1].next = e[2];
e[2].next = e[4];
e[4].next = e[1];
e[2].tri = e[4].tri = T1;
e[2].localNumber = (byte) next3[e[1].localNumber];
e[4].localNumber = (byte) prev3[e[1].localNumber];
// T2: e[3] is unchanged
TriangleHE T2 = e[3].tri;
e[3].next = e[0];
e[0].next = e[5];
e[5].next = e[3];
e[0].tri = e[5].tri = T2;
e[0].localNumber = (byte) next3[e[3].localNumber];
e[5].localNumber = (byte) prev3[e[3].localNumber];
// Adjust edge pointers of triangles
if (e[1].localNumber == 1)
T1.setHalfEdge(e[4]);
else if (e[1].localNumber == 2)
T1.setHalfEdge(e[2]);
if (e[3].localNumber == 1)
T2.setHalfEdge(e[5]);
else if (e[3].localNumber == 2)
T2.setHalfEdge(e[0]);
// Mark new edges
e[4].attributes = 0;
e[5].attributes = 0;
e[4].setAttributes(SWAPPED);
e[5].setAttributes(SWAPPED);
// Fix links to triangles
replaceVertexLinks(o, T1, T2, T1);
replaceVertexLinks(d, T1, T2, T2);
// Be consistent with AbstractHalfEdge.swap()
return e[2];
}
/**
* Returns the area of triangle bound to this edge.
*
* @return triangle area
* Warning: this method uses temp[0], temp[1] and temp[2] temporary arrays.
*/
@Override
public double area(Mesh m)
{
double[] temp0 = m.temp.t3_0;
double[] temp1 = m.temp.t3_1;
double[] temp2 = m.temp.t3_2;
destination().sub(origin(), temp1);
apex().sub(origin(), temp2);
Matrix3D.prodVect3D(temp1, temp2, temp0);
return 0.5 * Matrix3D.norm(temp0);
}
/**
* 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
*/
@Override
final boolean canCollapse(Mesh mesh, Location v)
{
// Be consistent with collapse()
if (hasAttributes(IMMUTABLE | OUTER))
return false;
if (!origin().isMutable() && !destination().isMutable())
return false;
if (!canCollapseBoundaries())
return false;
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.
Triangle t1 = tri;
Triangle t2 = sym.tri;
// Check that origin vertex can be moved
if (!checkNewRingNormalsSameFan(mesh, v, t1, t2))
return false;
// Check that destination vertex can be moved
if (!sym.checkNewRingNormalsSameFan(mesh, v, t1, t2))
return false;
// Topology check.
return canCollapseTopology();
}
// 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<Triangle> ignored = new HashSet<Triangle>();
for (Iterator<AbstractHalfEdge> it = fanIterator(); it.hasNext(); )
{
HalfEdge f = (HalfEdge) it.next();
ignored.add(f.tri);
ignored.add(f.sym.tri);
}
// Check that origin vertex can be moved
if (!checkNewRingNormalsNonManifoldVertex(mesh, v, ignored))
return false;
// Check that destination vertex can be moved
if (!sym.checkNewRingNormalsNonManifoldVertex(mesh, v, ignored))
return false;
ignored.clear();
if(hasAttributes(NONMANIFOLD))
{
// 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(); )
{
HalfEdge f = (HalfEdge) it.next();
if (!f.canCollapseTopology())
return false;
}
return true;
}
else
return canCollapseTopology2(mesh);
}
/**
* Same as canCollapseTopology but for manifold edges with non-manifold
* vertices.
*/
private boolean canCollapseTopology2(Mesh mesh)
{
Vertex origin = origin();
Vertex destination = destination();
Iterator<Vertex> it = origin.getNeighbourIteratorVertex();
Collection<Vertex> neighbours = new HashSet<Vertex>();
while(it.hasNext())
{
Vertex v = it.next();
if(v != mesh.outerVertex && v != destination)
neighbours.add(v);
}
it = destination.getNeighbourIteratorVertex();
int cnt = 0;
while(it.hasNext())
{
Vertex v = it.next();
if(v != origin && mesh.outerVertex != v && neighbours.remove(v))
{
if(cnt > 1)
return false;
cnt ++;
}
}
return true;
}
// Do not collapse if both previous and next edges are either
// boundary or non-manifold
private boolean canCollapseBoundaries()
{
if (!hasAttributes(NONMANIFOLD))
{
if (next == null || next.next == null)
return false;
if (next.hasAttributes(BOUNDARY | NONMANIFOLD) && next.next.hasAttributes(BOUNDARY | NONMANIFOLD))
return false;
if (sym == null || sym.next == null || sym.next.next == null)
return false;
if (sym.next.hasAttributes(BOUNDARY | NONMANIFOLD) && sym.next.next.hasAttributes(BOUNDARY | NONMANIFOLD))
return false;
return true;
}
for (Iterator<AbstractHalfEdge> it = fanIterator(); it.hasNext(); )
{
HalfEdge h = (HalfEdge) it.next();
if (h.next == null || h.next.next == null)
return false;
if (h.next.hasAttributes(BOUNDARY | NONMANIFOLD) && h.next.next.hasAttributes(BOUNDARY | NONMANIFOLD))
return false;
if (h.sym == null || h.sym.next == null || h.sym.next.next == null)
return false;
if (h.sym.next.hasAttributes(BOUNDARY | NONMANIFOLD) && h.sym.next.next.hasAttributes(BOUNDARY | NONMANIFOLD))
return false;
}
return true;
}
/**
* Topology check.
* See in AbstractHalfEdgeTest.buildMeshTopo() why this
* check is needed.
*/
private boolean canCollapseTopology()
{
Collection<Vertex> neighbours = new HashSet<Vertex>();
AbstractHalfEdge ot = this;
Vertex d = ot.destination();
do
{
// Warning: mesh.outerVertex is intentionnally not filtered out
neighbours.add(ot.destination());
ot = ot.nextOriginLoop();
}
while (ot.destination() != d);
ot = ot.sym();
int cnt = 0;
d = ot.destination();
do
{
// Warning: mesh.outerVertex is intentionnally not filtered out
if (neighbours.contains(ot.destination()))
{
if (cnt > 1)
return false;
cnt++;
}
ot = ot.nextOriginLoop();
}
while (ot.destination() != d);
return true;
}
/**
* Check that swapped edge does not already exists
*/
public boolean canSwapTopology()
{
Vertex a1 = apex();
Vertex a2 = sym.apex();
Iterator<Vertex> it = a1.getNeighbourIteratorVertex();
while(it.hasNext())
if(it.next() == a2)
return false;
return true;
}
final boolean canMoveOrigin(Mesh mesh, Location newpt)
{
assert origin().isManifold() && origin().isMutable();
Vertex d = destination();
HalfEdge f = this;
double [] temp0 = mesh.temp.t3_0;
double [] temp1 = mesh.temp.t3_1;
double [] temp2 = mesh.temp.t3_2;
double [] temp3 = mesh.temp.t3_3;
// Loop around origin
do
{
if (!f.hasAttributes(OUTER | BOUNDARY | NONMANIFOLD | SHARP))
{
Vertex x1 = f.destination();
double area1 = Matrix3D.computeNormal3D(newpt, x1, f.apex(), temp0, temp1, temp2);
double area2 = Matrix3D.computeNormal3D(x1, newpt, f.sym().apex(), temp0, temp1, temp3);
if (area1 == 0.0 || area2 == 0.0 || Matrix3D.prodSca(temp3, temp2) < -0.4)
return false;
}
f = f.nextOriginLoop();
}
while (f.destination() != d);
return true;
}
/**
* 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 temp[0], temp[1], temp[2] and temp[3] temporary arrays.
*/
@Override
final boolean checkNewRingNormals(Mesh mesh, Location newpt)
{
if (hasAttributes(IMMUTABLE))
return false;
Vertex o = origin();
if (o.isManifold())
return checkNewRingNormalsSameFan(mesh, newpt, null, null);
for (Triangle start: (Triangle []) o.getLink())
{
HalfEdge f = (HalfEdge) start.getAbstractHalfEdge();
if (f.destination() == o)
f = f.next;
else if (f.apex() == o)
f = f.next.next;
assert f.origin() == o;
if (!f.checkNewRingNormalsSameFan(mesh, newpt, null, null))
return false;
}
return true;
}
private boolean checkNewRingNormalsSameFan(Mesh mesh, Location newpt, Triangle t1, Triangle t2)
{
// Loop around origin
HalfEdge f = this;
double [] temp0 = mesh.temp.t3_0;
double [] temp1 = mesh.temp.t3_1;
double [] temp2 = mesh.temp.t3_2;
double [] temp3 = mesh.temp.t3_3;
Vertex d = f.destination();
do
{
if (f.tri != t1 && f.tri != t2 && !f.hasAttributes(OUTER))
{
Vertex x1 = f.destination();
double area = Matrix3D.computeNormal3DT(x1, f.apex(), origin(), temp0, temp1, temp2);
newpt.sub(x1, temp3);
// 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 (area == 0.0 || Matrix3D.prodSca(temp3, temp2) >= - area)
return false;
}
f = f.nextOriginLoop();
}
while (f.destination() != d);
return true;
}
private boolean checkNewRingNormalsNonManifoldVertex(Mesh mesh, Location newpt, Collection<Triangle> ignored)
{
Vertex o = origin();
if (o.isManifold())
return checkNewRingNormalsSameFanNonManifoldVertex(mesh, newpt, ignored);
for (Triangle start: (Triangle []) o.getLink())
{
HalfEdge f = (HalfEdge) start.getAbstractHalfEdge();
if (f.destination() == o)
f = f.next;
else if (f.apex() == o)
f = f.next.next;
assert f.origin() == o: f.origin()+" not the same as "+o;
if (!f.checkNewRingNormalsSameFanNonManifoldVertex(mesh, newpt, ignored))
return false;
}
return true;
}
private boolean checkNewRingNormalsSameFanNonManifoldVertex(Mesh mesh, Location newpt, Collection<Triangle> ignored)
{
// Loop around origin
HalfEdge f = this;
double [] temp0 = mesh.temp.t3_0;
double [] temp1 = mesh.temp.t3_1;
double [] temp2 = mesh.temp.t3_2;
double [] temp3 = mesh.temp.t3_3;
Vertex d = f.destination();
do
{
if (!ignored.contains(f.tri) && !f.hasAttributes(OUTER))
{
Vertex x1 = f.destination();
double area = Matrix3D.computeNormal3DT(x1, f.apex(), origin(), temp0, temp1, temp2);
newpt.sub(x1, temp3);
// 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(temp3, temp2) >= - area)
return false;
}
f = f.nextOriginLoop();
}
while (f.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 HalfEdge collapse(Mesh m, Vertex v)
{
if (hasAttributes(IMMUTABLE | 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+")");
if (o.isManifold())
replaceEndpointsSameFan(v);
else
replaceEndpointsNonManifold(o, v);
HalfEdge e = sym;
if (d.isManifold())
e.replaceEndpointsSameFan(v);
else
replaceEndpointsNonManifold(d, v);
deepCopyVertexLinks(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))
{
e.HEcollapseSameFan(m, v);
return HEcollapseSameFan(m, v);
}
// Edge is non-manifold
assert e.hasAttributes(OUTER);
HalfEdge ret = null;
// HEcollapseSameFan may modify internal data structure
// used by fanIterator(), we need a copy.
ArrayList<AbstractHalfEdge> copy = new ArrayList<AbstractHalfEdge>();
for (Iterator<AbstractHalfEdge> it = fanIterator(); it.hasNext(); )
copy.add(it.next());
for (AbstractHalfEdge ah: copy)
{
HalfEdge h = (HalfEdge) ah;
assert !h.hasAttributes(OUTER);
h.sym.HEcollapseSameFan(m, v);
if (h == this)
ret = h.HEcollapseSameFan(m, v);
else
h.HEcollapseSameFan(m, v);
}
assert ret != null;
return ret;
}
private HalfEdge HEcollapseSameFan(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.
HalfEdge e, f, s;
e = next; // (dV1o)
int attr4 = e.attributes;
s = e.sym; // (V1dV4)
e = e.next; // (V1od)
int attr3 = e.attributes;
f = e.sym; // (oV1V3)
Triangle t34 = (f == null ? ( s == null ? null : s.tri ) : f.tri);
if (t34 != null)
{
if (t34.hasAttributes(OUTER) && s != null)
t34 = s.tri;
if (!t34.hasAttributes(OUTER))
{
replaceVertexLinks(apex(), tri, t34);
replaceVertexLinks(n, tri, t34);
}
}
if (f != null && f.hasAttributes(NONMANIFOLD))
f.HEglue(s);
else if (s != null && s.hasAttributes(NONMANIFOLD))
s.HEglue(f);
else if (f != null)
f.HEglue(s);
else if (s != null)
s.sym = null;
if (f != null)
f.attributes |= attr4;
if (s != null)
s.attributes |= attr3;
// Remove t1
m.remove(tri);
// If t3 and t4 are outer, they are discarded
if (f != null && s!= null && f.hasAttributes(OUTER) && s.hasAttributes(OUTER))
{
m.remove(s.tri);
m.remove(f.tri);
if (m.hasNodes())
{
Vertex a = apex();
if (a.isManifold())
m.remove(a);
else
{
Triangle [] tArray = (Triangle []) a.getLink();
ArrayList<Triangle> res = new ArrayList<Triangle>(tArray.length - 1);
for (Triangle T : tArray)
{
if (T != tri)
res.add(T);
}
Triangle [] nList = new Triangle[res.size()];
res.toArray(nList);
a.setLink(nList);
}
}
}
// By convention, edge is moved into (dV4V1)
// If s is null, edge is outer and return value does not matter
return (s == null ? null : s.next);
}
private void replaceEndpointsSameFan(Vertex n)
{
HalfEdge e = this;
Vertex d = destination();
do
{
e.setOrigin(n);
e = e.nextOriginLoop();
}
while (e.destination() != d);
}
private static void replaceEndpointsNonManifold(Vertex o, Vertex n)
{
Triangle [] oList = (Triangle []) o.getLink();
for (Triangle t: oList)
{
TriangleHE tHE = (TriangleHE) t;
HalfEdge f = tHE.getAbstractHalfEdge();
if (f.destination() == o)
f = f.next;
else if (f.apex() == o)
f = f.next.next;
assert f.origin() == o : ""+o+" not in "+f;
f.replaceEndpointsSameFan(n);
}
}
private static void replaceVertexLinks(Vertex o, Triangle oldT1, Triangle oldT2, Triangle 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)
{
if(logger.isLoggable(Level.FINE))
logger.fine("replaceVertexLinks: "+i+" "+o+" "+tArray[i]);
tArray[i] = newT;
if(logger.isLoggable(Level.FINE))
logger.fine(" --> "+newT);
}
}
}
}
private static void replaceVertexLinks(Vertex o, Triangle oldT, Triangle newT)
{
if (o.isManifold())
{
if (o.getLink() == oldT)
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);
}
}
}
}
private static void deepCopyVertexLinks(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 LinkedHashSet<Triangle>();
// o and d have already been replaced by v
for (Triangle t: nList)
{
if (!allTriangles.contains(t))
res.add(t);
allTriangles.add(t);
AbstractHalfEdge h = t.getAbstractHalfEdge();
if (h.origin() != v)
h = h.next();
if (h.origin() != v)
h = h.next();
if (h.origin() == v)
{
// Add all triangles of the same fan to allTriangles
AbstractHalfEdge both = null;
Vertex end = h.destination();
do
{
h = h.nextOriginLoop();
allTriangles.add(h.getTri());
if (h.destination() == v)
both = h;
}
while (h.destination() != end);
if (both != null)
{
both = both.next();
end = both.destination();
do
{
both = both.nextOriginLoop();
allTriangles.add(both.getTri());
}
while (both.destination() != end);
}
}
boolean found = false;
if (h.destination() == v)
{
h = h.next();
found = true;
}
else if (h.apex() == v)
{
h = h.prev();
found = true;
}
if (found)
{
// Add all triangles of the same fan to allTriangles
AbstractHalfEdge both = null;
Vertex end = h.destination();
do
{
h = h.nextOriginLoop();
allTriangles.add(h.getTri());
if (h.destination() == v)
both = h;
}
while (h.destination() != end);
if (both != null)
{
both = both.next();
end = both.destination();
do
{
both = both.nextOriginLoop();
allTriangles.add(both.getTri());
}
while (both.destination() != end);
}
}
}
v.setLink(new Triangle[res.size()]);
res.toArray((Triangle[]) v.getLink());
}
}
/**
* Splits an edge. This is the opposite of {@link #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 HalfEdge 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);
HalfEdge g = HEsplitSameFan(m, v);
if (g.hasAttributes(OUTER))
{
// Remove links between t2 and t4
g = g.next; // (nV2d)
HalfEdge f = g.sym; // (V2no)
f.sym = null;
g.sym = null;
}
return this;
}
// HEsplitSameFan may modify internal data structure
// used by fanIterator(), we need a copy.
ArrayList<AbstractHalfEdge> copy = new ArrayList<AbstractHalfEdge>();
// Set vertex links
ArrayList<Triangle> link = new ArrayList<Triangle>();
int cnt = 0;
for (Iterator<AbstractHalfEdge> it = fanIterator(); it.hasNext(); )
{
HalfEdge f = (HalfEdge) it.next();
link.add(f.tri);
copy.add(f);
cnt++;
}
v.setLink(new Triangle[cnt]);
link.toArray((Triangle[]) v.getLink());
link.clear();
// Rebuild circular linked lists.
// TODO: Use a better algorithm to avoid array allocation
HalfEdge [] hOuter = new HalfEdge[2*cnt];
cnt = 0;
Vertex o = origin();
for (AbstractHalfEdge ah: copy)
{
HalfEdge f = (HalfEdge) ah;
HalfEdge g = f.HEsplitSameFan(m, v);
if (f.origin() == o)
{
hOuter[2*cnt] = f.sym;
hOuter[2*cnt+1] = g;
}
else
{
hOuter[2*cnt] = g;
hOuter[2*cnt+1] = f.sym;
}
assert hOuter[2*cnt].origin() == o || hOuter[2*cnt].destination() == o;
cnt++;
}
for (int j = 0; j < 2; j++)
{
// Initializes an empty cycle
HalfEdge head = hOuter[j].next;
head.HEglue(head.next);
for (int i = 1; i < cnt; i++)
{
// Adds hOuter[2*i+j] to current cycle
HalfEdge oldSym = head.sym;
head.HEglue(hOuter[2*i+j].next.next);
hOuter[2*i+j].next.HEglue(oldSym);
}
}
return this;
}
private HalfEdge HEsplitSameFan(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);
sym.splitVertexAddOneTriangle(m, n);
// Now we must update links:
// 1. Link together t1/t4 and t2/t3.
Triangle t1 = tri;
HalfEdge f = next; // (nV1o)
f = f.sym.next; // (ndV1)
Triangle t3 = f.tri;
HalfEdge g = sym; // (dnV2)
f.HEglue(g);
Triangle t2 = g.tri;
f = g.next.sym.next; // (noV2)
HEglue(f);
Triangle t4 = f.tri;
Triangle t14 = (t1.hasAttributes(OUTER) ? t4 : t1);
Triangle t23 = (t2.hasAttributes(OUTER) ? t3 : t2);
// Update vertex links
replaceVertexLinks(n, t1, t2, t14);
replaceVertexLinks(g.origin(), t1, t2, t23);
replaceVertexLinks(origin(), t1, t2, t14);
return g;
}
private void splitVertexAddOneTriangle(Mesh m, Vertex n)
{
/*
* V1 V1
* /'\ /|\
* / \ / | \
* / h1 \ / n1| h1 \
* / \ / | \
* / t1 \ / t1 | t3 \
* o +-------------------+ d ---> o +---------+---------+ d
*/
HalfEdge h1 = next; // (dV1o)
TriangleHE t1 = tri;
TriangleHE t3 = (TriangleHE) m.createTriangle(t1);
m.add(t3);
// (dV1) is not modified by this operation, so we move
// h1 into t3 so that it does not need to be updated by
// the caller.
HalfEdge n1 = t3.getAbstractHalfEdge();
boolean updateRefHalfEdge = false;
if (h1.localNumber == 1)
n1 = n1.next;
else if (h1.localNumber == 2)
n1 = n1.next.next;
else
updateRefHalfEdge = true;
assert h1.localNumber == n1.localNumber : "Wrong local numbers: "+n1+"\n"+h1;
// Update forward links
HalfEdge h1next = h1.next;
h1.next = n1.next;
h1.next.next.next = h1;
n1.next = h1next;
n1.next.next.next = n1;
if (updateRefHalfEdge)
{
t1.setHalfEdge(n1);
t3.setHalfEdge(h1);
}
// Update Triangle links
n1.tri = t1;
h1.tri = t3;
// Update vertices
n1.setOrigin(n);
h1.setApex(n);
// Inner edge
h1.next.HEglue(n1);
// Clear all flags but OUTER on inner edges
byte isOuter = n1.hasAttributes(OUTER) ? (byte) OUTER : 0;
h1.next.attributes = isOuter;
n1.attributes = isOuter;
}
private Iterator<AbstractHalfEdge> identityFanIterator()
{
final HalfEdge current = this;
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 HalfEdge last = (hasAttributes(OUTER) ? next.next : sym.next.next);
private HalfEdge current = null;
public boolean hasNext()
{
return last != current;
}
public AbstractHalfEdge next()
{
if (current == null)
current = last;
current = current.next.next.sym;
return current.next.sym;
}
public void remove()
{
}
};
}
@Override
public final String toString()
{
StringBuilder r = new StringBuilder();
r.append("hashCode: ").append(hashCode());
r.append("\nTriangle: ").append(tri.hashCode());
r.append("\nGroup: ").append(tri.getGroupId());
r.append("\nLocal number: ").append(localNumber);
if (sym != null)
r.append("\nSym: ").append(sym.hashCode()).append(" T=").append(sym.tri.hashCode()).append("[").append(sym.localNumber).append("]");
r.append("\nAttributes: ").append(attributes);
r.append("\nVertices:");
r.append("\n Origin: ").append(origin());
r.append("\n Destination: ").append(destination());
r.append("\n Apex: ").append(apex());
return r.toString();
}
/**
* Methods needed by AdjacencyWrapper.
*/
private void copyFields(HalfEdge src)
{
// Do not override tri!
localNumber = src.localNumber;
attributes = src.attributes;
sym = src.sym;
}
final void copy(HalfEdge that)
{
HalfEdge to = this;
for (int i = 0; i < 3; i++)
{
to.copyFields(that);
to = to.next;
that = that.next;
}
}
}